Rozdeleni editace letu do samostatnych views
This commit is contained in:
@@ -19,42 +19,11 @@ const formData = reactive({
|
||||
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>
|
||||
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@@ -19,6 +19,10 @@ const props = defineProps({
|
||||
type: Array,
|
||||
required: false,
|
||||
},
|
||||
copilots: {
|
||||
type: Array,
|
||||
required: false,
|
||||
},
|
||||
isRequestPending: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -32,7 +36,8 @@ const formData = reactive({
|
||||
description: "",
|
||||
debriefingNotes: "",
|
||||
urlSlug: "",
|
||||
isPublic: null
|
||||
isPublic: null,
|
||||
copilots: []
|
||||
})
|
||||
|
||||
|
||||
@@ -48,6 +53,20 @@ watch(() => props.flight, (newVal) => {
|
||||
}
|
||||
}, {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");
|
||||
</script>
|
||||
|
||||
@@ -67,6 +86,32 @@ const descriptionTab = ref("description");
|
||||
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
|
||||
</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-tab name="description">Popis</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">
|
||||
<RichTextEditor v-model="formData.description" v-bind="commonProps" height="30vh"/>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="debriefing">
|
||||
|
||||
<q-tab-panel name="debriefing">
|
||||
<BasicInfoBanner>
|
||||
Debriefing by měl obsahovat minimálně následující body:
|
||||
<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>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>Dokumentace:</strong> Zápis do deníku, technická kontrola, záznamy o počasí.</li>
|
||||
</ul>
|
||||
|
||||
</BasicInfoBanner>
|
||||
|
||||
<RichTextEditor v-model="formData.debriefingNotes" v-bind="commonProps" height="20vh"/>
|
||||
|
||||
@@ -25,12 +25,11 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
const steps = [
|
||||
{name: "basic", icon: "flight_takeoff", label: "Základní informace"},
|
||||
{name: "copilot", icon: "person_add", label: "Spolucestující"},
|
||||
{name: "track", icon: "route", label: "Trasa"},
|
||||
{name: "photos", icon: "add_a_photo", label: "Fotky"},
|
||||
{name: "notes", icon: "edit_note", label: "Poznámky"},
|
||||
{name: "plan", icon: "pin_drop", label: "Plán"},
|
||||
{name: "editFlightBasic", icon: "flight_takeoff", label: "Základní informace"},
|
||||
{name: "editFlightTrack", icon: "route", label: "Trasa"},
|
||||
{name: "editFlightPhotos", icon: "add_a_photo", label: "Fotky"},
|
||||
{name: "editFlightNotes", icon: "edit_note", label: "Poznámky"},
|
||||
{name: "editFlightSetPlan", icon: "pin_drop", label: "Plán"},
|
||||
];
|
||||
|
||||
const emit = defineEmits(["update:modelValue"])
|
||||
@@ -38,14 +37,20 @@ watch(currentStep, (val) => emit("update:modelValue", val));
|
||||
</script>
|
||||
|
||||
<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
|
||||
v-for="step in steps"
|
||||
:key="step.name"
|
||||
:icon="step.icon"
|
||||
:label="step.label"
|
||||
replace
|
||||
:to="{name: 'flightEdit', params: {id: flightId, step: step.name}}"
|
||||
:to="{name: step.name, params: {id: flightId}}"
|
||||
:disable="disabled.includes(step.name)"
|
||||
exact
|
||||
/>
|
||||
@@ -61,7 +66,6 @@ watch(currentStep, (val) => emit("update:modelValue", val));
|
||||
active-color="primary"
|
||||
class="q-mb-lg"
|
||||
>
|
||||
|
||||
<q-tab v-for="step in steps" :key="step.name" v-bind="step" :disable="disabled.includes(step.name)"/>
|
||||
</q-tabs>
|
||||
</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: {
|
||||
type: Array
|
||||
},
|
||||
loading: {
|
||||
type: Boolean
|
||||
}
|
||||
});
|
||||
|
||||
const formData = ref({
|
||||
@@ -44,7 +47,12 @@ const setup = () => {
|
||||
<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-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">
|
||||
<ComboboxInput
|
||||
:options="locations"
|
||||
|
||||
@@ -18,6 +18,9 @@ const props = defineProps({
|
||||
},
|
||||
copilots: {
|
||||
type: Array
|
||||
},
|
||||
loading: {
|
||||
type: Boolean
|
||||
}
|
||||
});
|
||||
|
||||
@@ -38,7 +41,10 @@ const setup = (() => {
|
||||
<template>
|
||||
<DialogWrapper :formData="formData" :object-id="photoId" @show="setup">
|
||||
<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">
|
||||
<ComboboxInput
|
||||
:options="locations"
|
||||
|
||||
@@ -86,7 +86,7 @@ const showLarge = (displayedPhoto) => {
|
||||
</div>
|
||||
<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="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>
|
||||
</div>
|
||||
|
||||
@@ -7,53 +7,19 @@ export default {
|
||||
return useFlightsStore().fetchDetail(flightId);
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import FlightFormSteps from "components/flights/form/FlightFormSteps.vue";
|
||||
import {onMounted, ref, toRefs, watch} from "vue";
|
||||
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 {storeToRefs} from "pinia";
|
||||
import FlightFormCopilot from "components/flights/form/FlightFormCopilot.vue";
|
||||
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 {successObject} from "src/composables/dialog";
|
||||
import {flightName} from "src/composables/flight";
|
||||
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 formStep = ref("basic");
|
||||
watch(() => $route.params.step, (val) => formStep.value = val);
|
||||
|
||||
const flightNameFormatted = flightName(flight.value);
|
||||
useAppStore().setPageTitle(`Editace - ${flightNameFormatted}`);
|
||||
useAppStore().setBreadcrumbs([
|
||||
@@ -62,192 +28,18 @@ useAppStore().setBreadcrumbs([
|
||||
{label: "Editace"}
|
||||
]);
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
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 $q = useQuasar();
|
||||
const $route = useRoute();
|
||||
const handleFlightEdit = (data) => {
|
||||
const flightId = parseInt($route.params.id);
|
||||
useFlightsStore().update(flightId, data).then((data) => {
|
||||
$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>
|
||||
|
||||
<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>
|
||||
</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>
|
||||
<router-view :flight="flight" @save="handleFlightEdit"/>
|
||||
</template>
|
||||
|
||||
@@ -101,7 +101,7 @@ const togglePublicVisibility = (variables) => toggleVisibility({
|
||||
<template #content>
|
||||
<q-card-section>
|
||||
<ContentCardEditButton
|
||||
:to="{name: 'flightEdit', params: {id: flightId, step: 'basic'}}"
|
||||
:to="{name: 'editFlightBasic', params: {id: flightId}}"
|
||||
label="Upravit informace"
|
||||
/>
|
||||
<FlightBasicInfo
|
||||
@@ -114,7 +114,7 @@ const togglePublicVisibility = (variables) => toggleVisibility({
|
||||
/>
|
||||
|
||||
<ContentCardEditButton
|
||||
:to="{name: 'flightEdit', params: {id: flightId, step: 'notes'}}"
|
||||
:to="{name: 'editFlightNotes', params: {id: flightId}}"
|
||||
label="Upravit text"
|
||||
/>
|
||||
<article class="q-my-lg" v-html="flight.description"/>
|
||||
@@ -129,12 +129,12 @@ const togglePublicVisibility = (variables) => toggleVisibility({
|
||||
<q-separator/>
|
||||
|
||||
<ContentCardEditButton
|
||||
:to="{name: 'flightEdit', params: {id: flightId, step: 'photos'}}"
|
||||
:to="{name: 'editFlightPhotos', params: {id: flightId}}"
|
||||
label="Upravit fotografie"
|
||||
/>
|
||||
<BasicInfoBanner
|
||||
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.
|
||||
</BasicInfoBanner>
|
||||
@@ -145,12 +145,12 @@ const togglePublicVisibility = (variables) => toggleVisibility({
|
||||
<q-separator/>
|
||||
|
||||
<ContentCardEditButton
|
||||
:to="{name: 'flightEdit', params: {id: flightId, step: 'track'}}"
|
||||
:to="{name: 'editFlightTrack', params: {id: flightId}}"
|
||||
label="Upravit trasu"
|
||||
/>
|
||||
<BasicInfoBanner
|
||||
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.
|
||||
</BasicInfoBanner>
|
||||
|
||||
@@ -32,10 +32,10 @@ const loading = computed(() => [
|
||||
useAircraftsStore().isQueryRunning, usePointsOfInterestStore().isQueryRunning, useAirportsStore().isQueryRunning
|
||||
].some(i => i.value))
|
||||
|
||||
const router = useRouter();
|
||||
const $router = useRouter();
|
||||
const handleSave = (formData) => {
|
||||
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>
|
||||
@@ -44,13 +44,12 @@ const handleSave = (formData) => {
|
||||
<FlightFormSteps :disabled="['copilot', 'track', 'photos', 'notes']"/>
|
||||
|
||||
<FlightEditSkeleton step="basic" v-if="loading"/>
|
||||
<template v-else>
|
||||
<FlightFormBasic
|
||||
@save="handleSave"
|
||||
v-else
|
||||
:is-request-pending="isMutationRunning"
|
||||
:aircrafts="aircrafts"
|
||||
:airports="airports"
|
||||
:locations="pointsOfInterest"
|
||||
@save="handleSave"
|
||||
/>
|
||||
</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
@@ -53,10 +53,17 @@ const routes = [
|
||||
{path: '', name: "myFlights", component: () => import('pages/profile/flight/FlightsPage.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/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: 'plany', name: "flightPlans", component: () => import('pages/profile/flightPlanner/FlightPlansPage.vue')},
|
||||
{path: 'plany/novy', name: "newFlightPlan", component: () => import('pages/profile/flightPlanner/NewFlightPlanPage.vue')},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user