Rozdeleni editace letu do samostatnych views

This commit is contained in:
Michal Kváček
2024-10-03 22:41:14 +02:00
parent 86e8dbf8e7
commit bffdd96be2
19 changed files with 454 additions and 359 deletions
@@ -19,42 +19,11 @@ const formData = reactive({
copilots: [], copilots: [],
}) })
const solo = ref(false);
watch(() => solo.value, (newVal) => {
if (!newVal) {
formData.copilots = [];
}
});
watch(() => props.flight, (newVal) => {
if (newVal) {
formData.copilots = newVal.copilots.map(copilot => ({id: copilot.id, name: copilot.name}));
solo.value = formData.copilots.length === 0;
}
}, {deep: true, immediate: true});
</script> </script>
<template> <template>
<FormWrapper v-model="formData"> <FormWrapper v-model="formData">
<q-btn-toggle
spread
no-caps
v-model="solo"
toggle-color="primary"
:options="[{label: 'Solo', value: true}, {label: 'S někým', value: false}]"
/>
<div v-if="!solo">
<ComboboxInput
v-for="i in flight.aircraft.seats - 1"
:key="i"
v-bind="commonProps"
:options="copilots"
v-model="formData.copilots[i-1]"
:clearable="true"
:label="i === 1 ? 'Kopilot' : 'Pasažér'"
/>
</div>
</FormWrapper> </FormWrapper>
</template> </template>
@@ -19,6 +19,10 @@ const props = defineProps({
type: Array, type: Array,
required: false, required: false,
}, },
copilots: {
type: Array,
required: false,
},
isRequestPending: { isRequestPending: {
type: Boolean, type: Boolean,
required: false, required: false,
@@ -32,7 +36,8 @@ const formData = reactive({
description: "", description: "",
debriefingNotes: "", debriefingNotes: "",
urlSlug: "", urlSlug: "",
isPublic: null isPublic: null,
copilots: []
}) })
@@ -48,6 +53,20 @@ watch(() => props.flight, (newVal) => {
} }
}, {immediate: true}); }, {immediate: true});
const solo = ref(false);
watch(() => solo.value, (newVal) => {
if (!newVal) {
formData.copilots = [];
}
});
watch(() => props.flight, (newVal) => {
if (newVal) {
formData.copilots = newVal.copilots.map(copilot => ({id: copilot.id, name: copilot.name}));
solo.value = formData.copilots.length === 0;
}
}, {deep: true, immediate: true});
const descriptionTab = ref("description"); const descriptionTab = ref("description");
</script> </script>
@@ -67,6 +86,32 @@ const descriptionTab = ref("description");
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/> <q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
</template> </template>
<div>
Letel jsi
<q-radio
v-model="solo"
:val="true"
label="Sólo"
/>
<q-radio
v-model="solo"
:val="false"
label="S někým"
/>
</div>
<div v-if="!solo">
<ComboboxInput
v-for="i in flight.aircraft.seats - 1"
:key="i"
v-bind="commonProps"
:options="copilots"
v-model="formData.copilots[i-1]"
:clearable="true"
:label="i === 1 ? 'Kopilot' : 'Pasažér'"
/>
</div>
<q-tabs v-model="descriptionTab" active-color="primary"> <q-tabs v-model="descriptionTab" active-color="primary">
<q-tab name="description">Popis</q-tab> <q-tab name="description">Popis</q-tab>
<q-tab name="debriefing">Debriefing (soukromé poznámky)</q-tab> <q-tab name="debriefing">Debriefing (soukromé poznámky)</q-tab>
@@ -75,8 +120,8 @@ const descriptionTab = ref("description");
<q-tab-panel name="description"> <q-tab-panel name="description">
<RichTextEditor v-model="formData.description" v-bind="commonProps" height="30vh"/> <RichTextEditor v-model="formData.description" v-bind="commonProps" height="30vh"/>
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="debriefing">
<q-tab-panel name="debriefing">
<BasicInfoBanner> <BasicInfoBanner>
Debriefing by měl obsahovat minimálně následující body: Debriefing by měl obsahovat minimálně následující body:
<ul> <ul>
@@ -85,9 +130,7 @@ const descriptionTab = ref("description");
<li><strong>Stav letadla:</strong> Funkčnost přístrojů, případné problémy.</li> <li><strong>Stav letadla:</strong> Funkčnost přístrojů, případné problémy.</li>
<li><strong>Bezpečnostní postupy:</strong> Dodržení bezpečnostních pravidel, řešení neočekávaných situací.</li> <li><strong>Bezpečnostní postupy:</strong> Dodržení bezpečnostních pravidel, řešení neočekávaných situací.</li>
<li><strong>Zpětná vazba:</strong> Pocity z letu, spokojenost pasažéra, návrhy na zlepšení.</li> <li><strong>Zpětná vazba:</strong> Pocity z letu, spokojenost pasažéra, návrhy na zlepšení.</li>
<li><strong>Dokumentace:</strong> Zápis do deníku, technická kontrola, záznamy o počasí.</li>
</ul> </ul>
</BasicInfoBanner> </BasicInfoBanner>
<RichTextEditor v-model="formData.debriefingNotes" v-bind="commonProps" height="20vh"/> <RichTextEditor v-model="formData.debriefingNotes" v-bind="commonProps" height="20vh"/>
@@ -25,12 +25,11 @@ onMounted(() => {
}) })
const steps = [ const steps = [
{name: "basic", icon: "flight_takeoff", label: "Základní informace"}, {name: "editFlightBasic", icon: "flight_takeoff", label: "Základní informace"},
{name: "copilot", icon: "person_add", label: "Spolucestující"}, {name: "editFlightTrack", icon: "route", label: "Trasa"},
{name: "track", icon: "route", label: "Trasa"}, {name: "editFlightPhotos", icon: "add_a_photo", label: "Fotky"},
{name: "photos", icon: "add_a_photo", label: "Fotky"}, {name: "editFlightNotes", icon: "edit_note", label: "Poznámky"},
{name: "notes", icon: "edit_note", label: "Poznámky"}, {name: "editFlightSetPlan", icon: "pin_drop", label: "Plán"},
{name: "plan", icon: "pin_drop", label: "Plán"},
]; ];
const emit = defineEmits(["update:modelValue"]) const emit = defineEmits(["update:modelValue"])
@@ -38,14 +37,20 @@ watch(currentStep, (val) => emit("update:modelValue", val));
</script> </script>
<template> <template>
<q-tabs v-if="routerEditMode" active-color="primary" class="q-mb-lg"> <q-tabs
v-if="routerEditMode"
active-color="primary"
class="q-mb-lg"
left-icon="chevron_left"
right-icon="chevron_right"
>
<q-route-tab <q-route-tab
v-for="step in steps" v-for="step in steps"
:key="step.name" :key="step.name"
:icon="step.icon" :icon="step.icon"
:label="step.label" :label="step.label"
replace replace
:to="{name: 'flightEdit', params: {id: flightId, step: step.name}}" :to="{name: step.name, params: {id: flightId}}"
:disable="disabled.includes(step.name)" :disable="disabled.includes(step.name)"
exact exact
/> />
@@ -61,7 +66,6 @@ watch(currentStep, (val) => emit("update:modelValue", val));
active-color="primary" active-color="primary"
class="q-mb-lg" class="q-mb-lg"
> >
<q-tab v-for="step in steps" :key="step.name" v-bind="step" :disable="disabled.includes(step.name)"/> <q-tab v-for="step in steps" :key="step.name" v-bind="step" :disable="disabled.includes(step.name)"/>
</q-tabs> </q-tabs>
</template> </template>
@@ -0,0 +1,23 @@
<script setup></script>`
<template>
<div class="row">
<div class="col">
<q-card class="q-my-sm">
<q-card-section>
<q-skeleton type="QInput" class="q-my-sm" v-for="i in 2" :key="i"/>
</q-card-section>
</q-card>
</div>
</div>
<div class="row">
<div class="col col-12 col-md-6" v-for="i in 2" :key="i">
<q-card class="q-mr-md-sm q-my-sm">
<q-card-section>
<q-skeleton type="QInput" class="q-my-sm" v-for="i in 2" :key="i"/>
</q-card-section>
</q-card>
</div>
</div>
</template>
@@ -0,0 +1,6 @@
<script setup></script>
<template>
<q-skeleton type="QInput" class="q-my-sm" v-for="i in 2" :key="i"/>
<q-skeleton type="rect" width="100%" height="500px"/>
</template>
@@ -1,77 +0,0 @@
<script setup>
import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
import {randomInt} from "src/composables/random";
const props = defineProps({
step: {
type: String,
required: true
},
})
</script>
<template>
<template v-if="step === 'basic'">
<div class="row">
<div class="col">
<q-card class="q-my-sm">
<q-card-section>
<q-skeleton type="QInput" class="q-my-sm" v-for="i in 2" :key="i"/>
</q-card-section>
</q-card>
</div>
</div>
<div class="row">
<div class="col col-12 col-md-6" v-for="i in 2" :key="i">
<q-card class="q-mr-md-sm q-my-sm">
<q-card-section>
<q-skeleton type="QInput" class="q-my-sm" v-for="i in 2" :key="i"/>
</q-card-section>
</q-card>
</div>
</div>
</template>
<q-card v-else-if="step === 'copilot'">
<q-card class="q-mr-md-sm q-my-sm">
<q-card-section>
<q-skeleton type="QInput" class="q-my-sm"/>
</q-card-section>
</q-card>
</q-card>
<template v-else-if="step === 'track'">
<q-card>
<q-card-section>
<q-skeleton type="QInput" class="q-my-sm" v-for="i in 2" :key="i"/>
</q-card-section>
</q-card>
<BasicListSkeleton :items-count-min="2" :items-count-max="6"/>
</template>
<template v-else-if="step === 'photos'">
<q-card class="q-my-lg">
<q-card-section>
<q-skeleton type="QInput" class="q-my-sm"/>
</q-card-section>
</q-card>
<div class="row">
<div class="col-6 col-sm-4 col-md-4 col-lg-3" v-for="i in randomInt(3, 10)" :key="i">
<q-card class="q-ma-md">
<q-skeleton height="300px" type="rect"/>
</q-card>
</div>
</div>
</template>
<div v-else-if="step === 'notes'">
<q-skeleton type="QInput" class="q-my-sm" v-for="i in 2" :key="i"/>
<q-skeleton type="rect" width="100%" height="500px"/>
</div>
</template>
@@ -0,0 +1,13 @@
<script setup>
import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
</script>
<template>
<q-card>
<q-card-section>
<q-skeleton type="QInput" class="q-my-sm" v-for="i in 2" :key="i"/>
</q-card-section>
</q-card>
<BasicListSkeleton :items-count-min="2" :items-count-max="6"/>
</template>
@@ -19,6 +19,9 @@ const props = defineProps({
copilots: { copilots: {
type: Array type: Array
}, },
loading: {
type: Boolean
}
}); });
const formData = ref({ const formData = ref({
@@ -44,7 +47,12 @@ const setup = () => {
<q-card-section class="text-center"> <q-card-section class="text-center">
<q-img :src="photo.thumbnailUrl" width="80px" class="q-ma-sm" v-for="photo in photos" :key="photo.thumbnailUrl"/> <q-img :src="photo.thumbnailUrl" width="80px" class="q-ma-sm" v-for="photo in photos" :key="photo.thumbnailUrl"/>
</q-card-section> </q-card-section>
<q-card-section>
<q-card-section v-if="loading">
<q-spinner />
</q-card-section>
<q-card-section v-else>
<FormWrapper :save-button="false"> <FormWrapper :save-button="false">
<ComboboxInput <ComboboxInput
:options="locations" :options="locations"
+7 -1
View File
@@ -18,6 +18,9 @@ const props = defineProps({
}, },
copilots: { copilots: {
type: Array type: Array
},
loading: {
type: Boolean
} }
}); });
@@ -38,7 +41,10 @@ const setup = (() => {
<template> <template>
<DialogWrapper :formData="formData" :object-id="photoId" @show="setup"> <DialogWrapper :formData="formData" :object-id="photoId" @show="setup">
<q-img :src="photo.thumbnailUrl"/> <q-img :src="photo.thumbnailUrl"/>
<q-card-section> <q-card-section v-if="loading">
<q-spinner />
</q-card-section>
<q-card-section v-else>
<FormWrapper :save-button="false"> <FormWrapper :save-button="false">
<ComboboxInput <ComboboxInput
:options="locations" :options="locations"
+1 -1
View File
@@ -86,7 +86,7 @@ const showLarge = (displayedPhoto) => {
</div> </div>
<q-btn flat icon="edit" color="blue" @click="openEdit(photo)"/> <q-btn flat icon="edit" color="blue" @click="openEdit(photo)"/>
<q-btn flat icon="tune" color="black" :to="{name: 'photoEditor', params: {id: photo.id}}"/> <q-btn flat icon="tune" color="black" :to="{name: 'photoEditor', params: {id: photo.id}}"/>
<q-btn flat icon="delete" color="red" @click="confirmPhotoDelete(photo.id)"/> <q-btn flat icon="delete" color="red" :disabled="titlePhoto?.id === photo.id" @click="confirmPhotoDelete(photo.id)"/>
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</div> </div>
+6 -214
View File
@@ -7,53 +7,19 @@ export default {
return useFlightsStore().fetchDetail(flightId); return useFlightsStore().fetchDetail(flightId);
} }
}; };
</script> </script>
<script setup> <script setup>
import FlightFormSteps from "components/flights/form/FlightFormSteps.vue"; import FlightFormSteps from "components/flights/form/FlightFormSteps.vue";
import {onMounted, ref, toRefs, watch} from "vue";
import {useRoute} from "vue-router"; import {useRoute} from "vue-router";
import FlightFormBasic from "components/flights/form/FlightFormBasic.vue";
import {useAircraftsStore} from "stores/Aircrafts";
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
import {useAirportsStore} from "stores/Airports";
import {useQuasar} from "quasar"; import {useQuasar} from "quasar";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
import FlightFormCopilot from "components/flights/form/FlightFormCopilot.vue"; import {successObject} from "src/composables/dialog";
import {useCopilotsStore} from "stores/Copilots";
import FlightFormNotes from "components/flights/form/FlightFormNotes.vue";
import FlightFormPhotos from "components/flights/form/FlightFormPhotos.vue";
import PhotosEditorList from "components/photos/PhotosEditorList.vue";
import {errorObject, successObject, warningObject} from "src/composables/dialog";
import PhotoFormDialog from "components/photos/PhotoFormDialog.vue";
import FlightFormTrack from "components/flights/form/FlightFormTrack.vue";
import FlightEditSkeleton from "components/flights/skeletons/FlightEditSkeleton.vue";
import BatchPhotoFormDialog from "components/photos/BatchPhotoFormDialog.vue";
import {useEventsStore} from "stores/Events";
import FlightEditCopilotSkeleton from "components/flights/skeletons/FlightEditCopilotSkeleton.vue";
import {useBasicPhotoOperations} from "src/composables/photos/basicOperations";
import {usePhotosStore} from "stores/Photos";
import {flightName} from "src/composables/flight"; import {flightName} from "src/composables/flight";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import {useFlightPlansStore} from "stores/FlightPlans";
const $q = useQuasar();
const $route = useRoute();
const flightId = parseInt($route.params.id);
const {aircrafts} = storeToRefs(useAircraftsStore());
const {pointsOfInterest} = storeToRefs(usePointsOfInterestStore());
const {airports} = storeToRefs(useAirportsStore());
const {copilots} = storeToRefs(useCopilotsStore());
const {events} = storeToRefs(useEventsStore());
const {photos} = storeToRefs(usePhotosStore());
const {flightPlan, flightPlans} = storeToRefs(useFlightPlansStore());
const {flight, isQueryRunning, isRequestPending: isMutationRunning} = storeToRefs(useFlightsStore()); const {flight, isQueryRunning, isRequestPending: isMutationRunning} = storeToRefs(useFlightsStore());
const formStep = ref("basic");
watch(() => $route.params.step, (val) => formStep.value = val);
const flightNameFormatted = flightName(flight.value); const flightNameFormatted = flightName(flight.value);
useAppStore().setPageTitle(`Editace - ${flightNameFormatted}`); useAppStore().setPageTitle(`Editace - ${flightNameFormatted}`);
useAppStore().setBreadcrumbs([ useAppStore().setBreadcrumbs([
@@ -62,192 +28,18 @@ useAppStore().setBreadcrumbs([
{label: "Editace"} {label: "Editace"}
]); ]);
const $q = useQuasar();
onMounted(() => { const $route = useRoute();
photos.value = flight.value.photos;
formStep.value = $route.params.step || "basic";
useAircraftsStore().fetch({});
usePointsOfInterestStore().fetch({});
useAirportsStore().fetch();
useCopilotsStore().fetch();
useEventsStore().fetch({});
useFlightPlansStore().fetch();
if (flight.value.flightPlanId) {
useFlightPlansStore().fetchDetail(flight.value.flightPlanId);
}
});
const handleFlightEdit = (data) => { const handleFlightEdit = (data) => {
const flightId = parseInt($route.params.id);
useFlightsStore().update(flightId, data).then((data) => { useFlightsStore().update(flightId, data).then((data) => {
$q.notify(successObject("Úspěšně upraveno")); $q.notify(successObject("Úspěšně upraveno"));
}) })
} }
const {
uploadInProgress, handlePhotosUpload, batchRotatePhotos, deletePhoto, updateSinglePhoto
} = useBasicPhotoOperations({});
const setTitlePhoto = (photo) => {
useFlightsStore().update(flight.value.id, {titlePhotoId: photo.id}).then(f => {
$q.notify(successObject("Úvodní fotografie úspěšně nastavena"));
});
}
const handlePhotoUpdate = (flightId, formData) => {
const photoId = formData.id;
delete formData.id;
return updateSinglePhoto(photoId, formData);
};
const openEdit = ref(false);
const editPhoto = ref(null);
const openPhotoEdit = (photo) => {
openEdit.value = true;
editPhoto.value = photo;
};
// batch edit
const openBatchEdit = ref(false);
const editBatchPhotos = ref([]);
const openBatchPhotoForm = (photos) => {
openBatchEdit.value = true;
editBatchPhotos.value = photos;
}
const handleBatchPhotoEdit = async (data) => {
const photoIds = data.photoIds;
delete data.photoIds;
delete data.id;
if (data.pointOfInterest && !data.pointOfInterest.id) {
const poiData = {
...data.pointOfInterest,
isPublic: false,
description: "",
urlSlug: "",
}
await usePointsOfInterestStore().create(poiData).then(poi => data.pointOfInterest = {id: poi.id, name: poi.name});
}
const promises = [];
for (const id of photoIds) {
const updateData = {
isAircraft: data.isAircraft,
copilots: data.copilots.map(c => ({id: c.id, name: c.name}))
};
if (data.name !== '') updateData.name = data.name;
if (data.description !== '') updateData.description = data.description;
if (data.pointOfInterest) updateData.pointOfInterest = data.pointOfInterest;
promises.push(updateSinglePhoto(id, data));
}
let success = 0;
Promise.allSettled(promises).then(result => {
for (const item of result) {
if (item.status === 'fulfilled') success++;
}
const error = result.length - success;
if (success > 0) $q.notify(successObject("Upraveno " + success + " fotografií"));
if (error > 0) $q.notify(errorObject(`Nepovedlo se uložit ${error} fotografií`))
});
}
</script> </script>
<template> <template>
<FlightFormSteps :router-edit-mode="true" :flightId="flightId"/> <FlightFormSteps :router-edit-mode="true" :flightId="flight.id"/>
<q-tab-panels v-model="formStep">
<q-tab-panel name="basic">
<FlightEditSkeleton :step="formStep" v-if="isQueryRunning || !flight"/>
<FlightFormBasic
v-else
@save="handleFlightEdit"
:flight="flight"
:locations="pointsOfInterest"
:airports="airports"
:aircrafts="aircrafts"
/>
</q-tab-panel>
<q-tab-panel name="copilot">
<FlightEditCopilotSkeleton v-if="isQueryRunning || !flight"/>
<FlightFormCopilot
v-else
@save="handleFlightEdit"
:flight="flight"
:copilots="copilots"/>
</q-tab-panel>
<q-tab-panel name="track">
<FlightEditSkeleton :step="formStep" v-if="isQueryRunning || !flight"/>
<FlightFormTrack
v-if="flight && pointsOfInterest"
:flight="flight"
:locations="pointsOfInterest"
:airports="airports"
@save="handleFlightEdit"
/>
</q-tab-panel>
<q-tab-panel name="photos">
<FlightFormPhotos
:flight="flight"
:is-request-pending="uploadInProgress"
@save="handlePhotosUpload"
/>
<template v-if="flight">
<PhotosEditorList
:title-photo="flight.titlePhoto"
:photos="photos"
:show-actions="true"
@edit="openPhotoEdit"
@batch-edit="openBatchPhotoForm"
@batch-rotate="batchRotatePhotos"
@delete="deletePhoto"
@set-title-photo="setTitlePhoto"
/>
<!-- Dialogs for photo edit -->
<PhotoFormDialog
v-model="openEdit"
:copilots="flight.copilots"
:aircrafts="aircrafts"
:locations="pointsOfInterest"
:photo="editPhoto"
@save="handlePhotoUpdate(flightId, $event)"
/>
<BatchPhotoFormDialog
v-model="openBatchEdit"
:locations="pointsOfInterest"
:photos="editBatchPhotos"
:copilots="flight.copilots"
:aircrafts="aircrafts"
@save="handleBatchPhotoEdit"
@delete="deletePhoto"
/>
</template> <router-view :flight="flight" @save="handleFlightEdit"/>
</q-tab-panel>
<q-tab-panel name="notes">
<FlightFormNotes @save="handleFlightEdit" :events="events" :flight="flight"/>
</q-tab-panel>
<q-tab-panel name="plan">
<template v-if="flight.flightPlanId">
{{ flightPlan.name }}
</template>
<template v-else>
Bez planu
</template>
</q-tab-panel>
</q-tab-panels>
</template> </template>
@@ -101,7 +101,7 @@ const togglePublicVisibility = (variables) => toggleVisibility({
<template #content> <template #content>
<q-card-section> <q-card-section>
<ContentCardEditButton <ContentCardEditButton
:to="{name: 'flightEdit', params: {id: flightId, step: 'basic'}}" :to="{name: 'editFlightBasic', params: {id: flightId}}"
label="Upravit informace" label="Upravit informace"
/> />
<FlightBasicInfo <FlightBasicInfo
@@ -114,7 +114,7 @@ const togglePublicVisibility = (variables) => toggleVisibility({
/> />
<ContentCardEditButton <ContentCardEditButton
:to="{name: 'flightEdit', params: {id: flightId, step: 'notes'}}" :to="{name: 'editFlightNotes', params: {id: flightId}}"
label="Upravit text" label="Upravit text"
/> />
<article class="q-my-lg" v-html="flight.description"/> <article class="q-my-lg" v-html="flight.description"/>
@@ -129,12 +129,12 @@ const togglePublicVisibility = (variables) => toggleVisibility({
<q-separator/> <q-separator/>
<ContentCardEditButton <ContentCardEditButton
:to="{name: 'flightEdit', params: {id: flightId, step: 'photos'}}" :to="{name: 'editFlightPhotos', params: {id: flightId}}"
label="Upravit fotografie" label="Upravit fotografie"
/> />
<BasicInfoBanner <BasicInfoBanner
v-if="flight.photos.length === 0" v-if="flight.photos.length === 0"
:route-add="{name: 'flightEdit', params: {id: flightId, step: 'photos'}}" :route-add="{name: 'editFlightPhotos', params: {id: flightId,}}"
> >
U tohoto letu nemáš nahrané žádné fotografie. U tohoto letu nemáš nahrané žádné fotografie.
</BasicInfoBanner> </BasicInfoBanner>
@@ -145,12 +145,12 @@ const togglePublicVisibility = (variables) => toggleVisibility({
<q-separator/> <q-separator/>
<ContentCardEditButton <ContentCardEditButton
:to="{name: 'flightEdit', params: {id: flightId, step: 'track'}}" :to="{name: 'editFlightTrack', params: {id: flightId}}"
label="Upravit trasu" label="Upravit trasu"
/> />
<BasicInfoBanner <BasicInfoBanner
v-if="flight.turnPoints.length === 0 && !flight.track" v-if="flight.turnPoints.length === 0 && !flight.track"
:route-add="{name: 'flightEdit', params: {id: flightId, step: 'track'}}" :route-add="{name: 'editFlightTrack', params: {id: flightId}}"
> >
Nemáš nastavenou žádnou trasu. Nemáš nastavenou žádnou trasu.
</BasicInfoBanner> </BasicInfoBanner>
+4 -5
View File
@@ -32,10 +32,10 @@ const loading = computed(() => [
useAircraftsStore().isQueryRunning, usePointsOfInterestStore().isQueryRunning, useAirportsStore().isQueryRunning useAircraftsStore().isQueryRunning, usePointsOfInterestStore().isQueryRunning, useAirportsStore().isQueryRunning
].some(i => i.value)) ].some(i => i.value))
const router = useRouter(); const $router = useRouter();
const handleSave = (formData) => { const handleSave = (formData) => {
useFlightsStore().create(formData).then((flight) => { useFlightsStore().create(formData).then((flight) => {
router.push({name: "flightEdit", params: {id: flight.id, step: "copilot"}}); $router.push({name: "flightEdit", params: {id: flight.id, step: "copilot"}});
}); });
} }
</script> </script>
@@ -44,13 +44,12 @@ const handleSave = (formData) => {
<FlightFormSteps :disabled="['copilot', 'track', 'photos', 'notes']"/> <FlightFormSteps :disabled="['copilot', 'track', 'photos', 'notes']"/>
<FlightEditSkeleton step="basic" v-if="loading"/> <FlightEditSkeleton step="basic" v-if="loading"/>
<template v-else>
<FlightFormBasic <FlightFormBasic
@save="handleSave" v-else
:is-request-pending="isMutationRunning" :is-request-pending="isMutationRunning"
:aircrafts="aircrafts" :aircrafts="aircrafts"
:airports="airports" :airports="airports"
:locations="pointsOfInterest" :locations="pointsOfInterest"
@save="handleSave"
/> />
</template> </template>
</template>
@@ -0,0 +1,40 @@
<script setup>
import FlightFormBasic from "components/flights/form/FlightFormBasic.vue";
import {onMounted} from "vue";
import {useAircraftsStore} from "stores/Aircrafts";
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
import {useAirportsStore} from "stores/Airports";
import {storeToRefs} from "pinia";
import {useFlightsStore} from "stores/Flights";
import FlightEditBasicInfoSkeleton from "components/flights/skeletons/FlightEditBasicInfoSkeleton.vue";
const props = defineProps({
flight: {
type: Object,
required: true,
}
});
onMounted(() => {
useAircraftsStore().fetch({});
usePointsOfInterestStore().fetch({});
useAirportsStore().fetch();
});
const emits = defineEmits(["save"]);
const {aircrafts} = storeToRefs(useAircraftsStore());
const {pointsOfInterest} = storeToRefs(usePointsOfInterestStore());
const {airports} = storeToRefs(useAirportsStore());
const {isQueryRunning, isRequestPending: isMutationRunning} = storeToRefs(useFlightsStore());
</script>
<template>
<FlightEditBasicInfoSkeleton v-if="isQueryRunning || !flight"/>
<FlightFormBasic
v-else
:flight="flight"
:locations="pointsOfInterest"
:airports="airports"
:aircrafts="aircrafts"
@save="emits('save', $event)"
/>
</template>
@@ -0,0 +1,38 @@
<script setup>
import FlightFormNotes from "components/flights/form/FlightFormNotes.vue";
import {computed, onMounted} from "vue";
import {useEventsStore} from "stores/Events";
import {storeToRefs} from "pinia";
import {useCopilotsStore} from "stores/Copilots";
import FlightEditExtraInfoSkeleton from "components/flights/skeletons/FlightEditExtraInfoSkeleton.vue";
const props = defineProps({
flight: {
type: Object,
required: true,
}
});
const emits = defineEmits(["save"]);
onMounted(() => {
useEventsStore().fetch({});
useCopilotsStore().fetch();
});
const {events, isQueryRunning: loadingEvents} = storeToRefs(useEventsStore());
const {copilots, isQueryRunning: loadingCopilots} = storeToRefs(useCopilotsStore())
const isLoading = computed(() => loadingCopilots.value || loadingEvents.value);
</script>
<template>
<FlightEditExtraInfoSkeleton v-if="isLoading" />
<FlightFormNotes
v-else
:events="events"
:flight="flight"
:copilots="copilots"
@save="emits('save', $event)"
/>
</template>
@@ -0,0 +1,156 @@
<script setup>
import BatchPhotoFormDialog from "components/photos/BatchPhotoFormDialog.vue";
import PhotoFormDialog from "components/photos/PhotoFormDialog.vue";
import FlightFormPhotos from "components/flights/form/FlightFormPhotos.vue";
import PhotosEditorList from "components/photos/PhotosEditorList.vue";
import {errorObject, successObject} from "src/composables/dialog";
import {computed, onMounted, ref, watch} from "vue";
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
import {useFlightsStore} from "stores/Flights";
import {useBasicPhotoOperations} from "src/composables/photos/basicOperations";
import {storeToRefs} from "pinia";
import {useAircraftsStore} from "stores/Aircrafts";
import {usePhotosStore} from "stores/Photos";
const props = defineProps({
flight: {
type: Object,
required: true,
}
});
const emits = defineEmits(["save"]);
onMounted(() => {
photos.value = props.flight.photos;
})
const {aircrafts, isQueryRunning: aircraftsLoading} = storeToRefs(useAircraftsStore());
const {pointsOfInterest, isQueryRunning: poisLoading} = storeToRefs(usePointsOfInterestStore());
const {photos, isQueryRunning: photosLoading} = storeToRefs(usePhotosStore());
const isLoading = computed(() => aircraftsLoading.value || poisLoading.value || photosLoading.value);
const setTitlePhoto = (photo) => {
useFlightsStore().update(props.flight.id, {titlePhotoId: photo.id}).then(f => {
$q.notify(successObject("Úvodní fotografie úspěšně nastavena"));
});
}
const handlePhotoUpdate = (flightId, formData) => {
const photoId = formData.id;
delete formData.id;
return updateSinglePhoto(photoId, formData);
};
const openEdit = ref(false);
const editPhoto = ref(null);
const openPhotoEdit = (photo) => {
openEdit.value = true;
editPhoto.value = photo;
};
const {
uploadInProgress, handlePhotosUpload, batchRotatePhotos, deletePhoto, updateSinglePhoto
} = useBasicPhotoOperations({});
// batch edit
const openBatchEdit = ref(false);
const editBatchPhotos = ref([]);
const openBatchPhotoForm = (photos) => {
openBatchEdit.value = true;
editBatchPhotos.value = photos;
}
const handleBatchPhotoEdit = async (data) => {
const photoIds = data.photoIds;
delete data.photoIds;
delete data.id;
if (data.pointOfInterest && !data.pointOfInterest.id) {
const poiData = {
...data.pointOfInterest,
isPublic: false,
description: "",
urlSlug: "",
}
await usePointsOfInterestStore().create(poiData).then(poi => data.pointOfInterest = {id: poi.id, name: poi.name});
}
const promises = [];
for (const id of photoIds) {
const updateData = {
isAircraft: data.isAircraft,
copilots: data.copilots.map(c => ({id: c.id, name: c.name}))
};
if (data.name !== '') updateData.name = data.name;
if (data.description !== '') updateData.description = data.description;
if (data.pointOfInterest) updateData.pointOfInterest = data.pointOfInterest;
promises.push(updateSinglePhoto(id, data));
}
let success = 0;
Promise.allSettled(promises).then(result => {
for (const item of result) {
if (item.status === 'fulfilled') success++;
}
const error = result.length - success;
if (success > 0) $q.notify(successObject("Upraveno " + success + " fotografií"));
if (error > 0) $q.notify(errorObject(`Nepovedlo se uložit ${error} fotografií`))
});
}
watch(() => [openEdit.value, openBatchEdit.value], (newVal) => {
if (newVal) {
// load new data on each dialog open
useAircraftsStore().fetch({});
usePointsOfInterestStore().fetch({});
}
});
</script>
<template>
<FlightFormPhotos
:flight="flight"
:is-request-pending="uploadInProgress"
@save="handlePhotosUpload"
/>
<PhotosEditorList
:title-photo="flight.titlePhoto"
:photos="flight.photos"
:show-actions="true"
@edit="openPhotoEdit"
@batch-edit="openBatchPhotoForm"
@batch-rotate="batchRotatePhotos"
@delete="deletePhoto"
@set-title-photo="setTitlePhoto"
/>
<PhotoFormDialog
v-model="openEdit"
:copilots="flight.copilots"
:loading="isLoading"
:aircrafts="aircrafts"
:locations="pointsOfInterest"
:photo="editPhoto"
@save="handlePhotoUpdate(flight.id, $event)"
/>
<BatchPhotoFormDialog
v-model="openBatchEdit"
:loading="isLoading"
:locations="pointsOfInterest"
:photos="editBatchPhotos"
:copilots="flight.copilots"
:aircrafts="aircrafts"
@save="handleBatchPhotoEdit"
@delete="deletePhoto"
/>
</template>
@@ -0,0 +1,31 @@
<script setup>
import {onMounted} from "vue";
import {useFlightPlansStore} from "stores/FlightPlans";
import {storeToRefs} from "pinia";
const props = defineProps({
flight: {
type: Object,
required: true,
}
});
const emits = defineEmits(["save"]);
onMounted(() => {
if (props.flight.flightPlanId) {
useFlightPlansStore().fetchDetail(props.flight.flightPlanId);
}
});
const {flightPlan} = storeToRefs(useFlightPlansStore());
</script>
<template>
<template v-if="flight.flightPlanId">
{{ flightPlan.name }}
</template>
<template v-else>
Bez planu
</template>
</template>
@@ -0,0 +1,37 @@
<script setup>
import FlightFormTrack from "components/flights/form/FlightFormTrack.vue";
import {storeToRefs} from "pinia";
import {computed, onMounted} from "vue";
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
import {useAirportsStore} from "stores/Airports";
import FlightEditTrackSkeleton from "components/flights/skeletons/FlightEditTrackSkeleton.vue";
const props = defineProps({
flight: {
type: Object,
required: true,
}
});
const emits = defineEmits(["save"]);
onMounted(() => {
usePointsOfInterestStore().fetch({});
useAirportsStore().fetch();
});
const {pointsOfInterest, isQueryRunning: poisLoading} = storeToRefs(usePointsOfInterestStore());
const {airports, isQueryRunning: airportsLoading} = storeToRefs(useAirportsStore());
const isLoading = computed(() => poisLoading.value || airportsLoading.value);
</script>
<template>
<FlightEditTrackSkeleton v-if="isLoading"/>
<FlightFormTrack
v-else
:flight="flight"
:locations="pointsOfInterest"
:airports="airports"
@save="emits('save', $event)"
/>
</template>
+10 -3
View File
@@ -53,10 +53,17 @@ const routes = [
{path: '', name: "myFlights", component: () => import('pages/profile/flight/FlightsPage.vue')}, {path: '', name: "myFlights", component: () => import('pages/profile/flight/FlightsPage.vue')},
{path: 'lety/novy', name: "addFlight", component: () => import('pages/profile/flight/NewFlightPage.vue')}, {path: 'lety/novy', name: "addFlight", component: () => import('pages/profile/flight/NewFlightPage.vue')},
{path: 'lety/:id', name: "flightDetail", component: () => import('pages/profile/flight/FlightDetailPage.vue')}, {path: 'lety/:id', name: "flightDetail", component: () => import('pages/profile/flight/FlightDetailPage.vue')},
{path: 'lety/:id/edit/:step?', name: "flightEdit", component: () => import('pages/profile/flight/EditFlightPage.vue')}, {
path: 'lety/:id/edit',
component: () => import('pages/profile/flight/EditFlightPage.vue'),
children: [
{path: '', name: "editFlightBasic", component: () => import('pages/profile/flight/edit/EditFlightBasicInfoPage.vue')},
{path: 'trasa', name: "editFlightTrack", component: () => import('pages/profile/flight/edit/EditFlightTrackPage.vue')},
{path: 'foto', name: "editFlightPhotos", component: () => import('pages/profile/flight/edit/EditFlightPhotosPage.vue')},
{path: 'poznamky', name: "editFlightNotes", component: () => import('pages/profile/flight/edit/EditFlightExtraInfoPage.vue')},
{path: 'plan', name: "editFlightSetPlan", component: () => import('pages/profile/flight/edit/EditFlightPlanPage.vue')},
]},
{path: 'nastaveni', name: "profile", component: () => import('pages/profile/ProfilePage.vue')}, {path: 'nastaveni', name: "profile", component: () => import('pages/profile/ProfilePage.vue')},
{path: 'plany', name: "flightPlans", component: () => import('pages/profile/flightPlanner/FlightPlansPage.vue')}, {path: 'plany', name: "flightPlans", component: () => import('pages/profile/flightPlanner/FlightPlansPage.vue')},
{path: 'plany/novy', name: "newFlightPlan", component: () => import('pages/profile/flightPlanner/NewFlightPlanPage.vue')}, {path: 'plany/novy', name: "newFlightPlan", component: () => import('pages/profile/flightPlanner/NewFlightPlanPage.vue')},
{ {