Prace s mapu, zobrazeni GPX, editace kopilota, skeletony
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 610 B |
Binary file not shown.
|
After Width: | Height: | Size: 803 B |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
+3
-4
@@ -41,15 +41,14 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||||
extras: [
|
extras: [
|
||||||
// 'ionicons-v4',
|
// 'ionicons-v4',
|
||||||
'mdi-v5',
|
// 'line-awesome',
|
||||||
// 'fontawesome-v6',
|
// 'fontawesome-v6',
|
||||||
// 'eva-icons',
|
// 'eva-icons',
|
||||||
// 'themify',
|
// 'themify',
|
||||||
// 'line-awesome',
|
|
||||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||||
|
'mdi-v5',
|
||||||
'roboto-font', // optional, you are not bound to it
|
|
||||||
'material-icons', // optional, you are not bound to it
|
'material-icons', // optional, you are not bound to it
|
||||||
|
'roboto-font', // optional, you are not bound to it
|
||||||
],
|
],
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import MapWrapper from "components/MapWrapper.vue";
|
|
||||||
import {Marker, Polyline} from "vue3-google-map";
|
|
||||||
import {computed} from "vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
gpsLatitude: {
|
|
||||||
type: Number,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
gpsLongitude: {
|
|
||||||
type: Number,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
zoom: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
default: 10
|
|
||||||
},
|
|
||||||
markers: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
showAsTrack: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const mapCenter = computed(() => {
|
|
||||||
const north = Math.max(...props.markers.map(m => m.lng));
|
|
||||||
const south = Math.min(...props.markers.map(m => m.lng));
|
|
||||||
const east = Math.max(...props.markers.map(m => m.lat));
|
|
||||||
const west = Math.min(...props.markers.map(m => m.lat));
|
|
||||||
|
|
||||||
const longitudeDiff = north - south;
|
|
||||||
const latitudeDiff = east - west;
|
|
||||||
|
|
||||||
return {
|
|
||||||
lat: west + latitudeDiff / 2,
|
|
||||||
lng: south + longitudeDiff / 2
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<MapWrapper v-bind="$attrs" :center="mapCenter" :zoom="zoom">
|
|
||||||
<template v-if="!showAsTrack">
|
|
||||||
<Marker v-for="(marker, idx) in markers" :key="idx" :options="{position: marker}"/>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<Polyline :options="{path: markers, geodesic: true, strokeColor: '#ff0000', strokeOpacity: 0.3, strokeWeight: 5}"/>
|
|
||||||
</template>
|
|
||||||
</MapWrapper>
|
|
||||||
</template>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import {GoogleMap} from "vue3-google-map";
|
|
||||||
|
|
||||||
const key = process.env.GOOGLE_MAPS_API_KEY;
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<GoogleMap :api-key="key" v-bind="$attrs">
|
|
||||||
<slot />
|
|
||||||
</GoogleMap>
|
|
||||||
</template>
|
|
||||||
@@ -6,10 +6,10 @@ import {randomInt} from "src/composables/random";
|
|||||||
<q-list bordered separator>
|
<q-list bordered separator>
|
||||||
<q-item v-ripple v-for="i in randomInt(2, 5)" :key="i">
|
<q-item v-ripple v-for="i in randomInt(2, 5)" :key="i">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-skeleton type="QAvatar"></q-skeleton>
|
<q-skeleton type="QAvatar" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-skeleton :width="randomInt(10,25)+'%'"></q-skeleton>
|
<q-skeleton :width="randomInt(10,25)+'%'" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<script setup></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
edit form
|
|
||||||
</template>
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {onMounted, reactive} from "vue";
|
||||||
|
import {setDefaultData} from "src/composables/form/formHandling";
|
||||||
|
import FormWrapper from "components/FormWrapper.vue";
|
||||||
|
import {commonProps} from "src/composables/inputs";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
copilot: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
name: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.copilot) {
|
||||||
|
setDefaultData(formData, props.copilot);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FormWrapper v-model="formData">
|
||||||
|
<q-input v-bind="commonProps" label="Jméno" v-model="formData.name" />
|
||||||
|
</FormWrapper>
|
||||||
|
</template>
|
||||||
@@ -1,5 +1,16 @@
|
|||||||
<script setup></script>
|
<script setup>
|
||||||
|
import {randomInt} from "src/composables/random";
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<em>Copilot skeleton</em>
|
<q-list bordered separator>
|
||||||
|
<q-item v-ripple v-for="i in randomInt(2, 5)" :key="i">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-skeleton type="QAvatar"/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-skeleton :width="randomInt(10,25)+'%'"/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const props = defineProps({
|
|||||||
<q-avatar icon="person" color="primary" text-color="white"/>
|
<q-avatar icon="person" color="primary" text-color="white"/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<router-link :to="{name: 'pilotDetail', params: {id: pilot.id }}">
|
<router-link :to="{name: 'copilotDetail', params: {id: pilot.id }}">
|
||||||
{{ pilot.name }}
|
{{ pilot.name }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {flight} from "src/composables/objects/flight";
|
import {flight} from "src/composables/objects/flight";
|
||||||
import {formatDatetime, minutesToHumanReadable} from "src/composables/datetimeUtils";
|
import {formatDatetime, minutesToHumanReadable} from "src/composables/datetimeUtils";
|
||||||
|
import WeatherInfo from "components/weather/WeatherInfo.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
flight: {
|
flight: {
|
||||||
@@ -20,7 +21,11 @@ const props = defineProps({
|
|||||||
<q-icon name="flight_takeoff"/>
|
<q-icon name="flight_takeoff"/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
|
<div>
|
||||||
{{ flight.takeoffAirport.name }}
|
{{ flight.takeoffAirport.name }}
|
||||||
|
|
||||||
|
<WeatherInfo class="q-mx-sm" v-if="flight.takeoffWeatherInfo" :weather="flight.takeoffWeatherInfo"/>
|
||||||
|
</div>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
||||||
@@ -38,7 +43,10 @@ const props = defineProps({
|
|||||||
<q-icon name="flight_land"/>
|
<q-icon name="flight_land"/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
|
<div>
|
||||||
{{ flight.landingAirport.name }}
|
{{ flight.landingAirport.name }}
|
||||||
|
<WeatherInfo class="q-mx-sm" v-if="flight.landingWeatherInfo" :weather="flight.landingWeatherInfo"/>
|
||||||
|
</div>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@@ -61,20 +69,12 @@ const props = defineProps({
|
|||||||
{{ minutesToHumanReadable(flight.durationMinCalculated) }}
|
{{ minutesToHumanReadable(flight.durationMinCalculated) }}
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item>
|
<q-item v-if="flight.copilot">
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="thermostat"/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
Clouds: few, Wind: 220/12kt, Temp: 12°C
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item v-if="!flight.solo">
|
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-icon name="people"/>
|
<q-icon name="people"/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<router-link :to="{name: 'pilotDetail', params: {id: flight.copilot.id}}">
|
<router-link :to="{name: 'copilotDetail', params: {id: flight.copilot.id}}">
|
||||||
{{ flight.copilot.name }}
|
{{ flight.copilot.name }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|||||||
@@ -15,34 +15,32 @@ const props = defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<q-list bordered separator class="q-mx-md q-my-md">
|
||||||
<div class="col-12 col-md-6 col-lg-4" v-for="flight of items" :key="flight">
|
<q-item v-for="flight of items" :key="flight" ripple clickable :to="{name: 'flightDetail', params:{id: flight.id}}">
|
||||||
|
<q-item-section avatar>
|
||||||
|
|
||||||
<q-card class="q-my-sm q-mx-sm" >
|
<q-avatar size="100px">
|
||||||
<q-img :src="flight.coverPhoto.thumbnailUrl" v-if="flight.coverPhoto">
|
<div v-if="flight.coverPhoto" :style="`width: 100px; height: 100px; background-image: url('${flight.coverPhoto.thumbnailUrl}'); background-size: cover`"></div>
|
||||||
<div class="absolute-bottom text-center text-h6">
|
</q-avatar>
|
||||||
<router-link :to="{name: 'flightDetail', params:{id: flight.id}}" class="text-white">
|
|
||||||
|
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
{{ flightName(flight) }}
|
{{ flightName(flight) }}
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</q-img>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="text-h6" v-if="!flight.coverPhoto">
|
|
||||||
<router-link :to="{name: 'flightDetail', params:{id: flight.id}}">
|
|
||||||
{{ flightName(flight) }}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
<div class="text-subtitle2">{{ minutesToHumanReadable(flight.durationMinCalculated) }}</div>
|
|
||||||
<div class="text-secondary">
|
<div class="text-secondary">
|
||||||
<q-icon name="today"/>
|
<q-icon name="flight_takeoff"/>
|
||||||
{{ formatDatetime(flight.takeoffDatetime) }}
|
{{ formatDatetime(flight.takeoffDatetime) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-secondary">
|
<div class="text-secondary">
|
||||||
<q-icon name="plane"/>
|
<q-icon name="schedule" />
|
||||||
|
{{ minutesToHumanReadable(flight.durationMinCalculated) }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="text-secondary">
|
||||||
|
<q-icon name="flight"/>
|
||||||
{{ flight.aircraft.callSign }}
|
{{ flight.aircraft.callSign }}
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-item-section>
|
||||||
</q-card>
|
</q-item>
|
||||||
</div>
|
</q-list>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
<script setup>
|
||||||
|
import {randomInt} from "src/composables/random";
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
Skeleton
|
<q-list bordered separator class="q-mx-md q-my-md">
|
||||||
|
<q-item v-for="i in randomInt(5, 10)" :key="i" ripple>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar size="100px">
|
||||||
|
<q-skeleton type="QAvatar" size="100px"></q-skeleton>
|
||||||
|
</q-avatar>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-skeleton :width="randomInt(30,50)+'%'"/>
|
||||||
|
|
||||||
|
<q-skeleton type="text" :width="randomInt(10,15)+'%'"/>
|
||||||
|
<q-skeleton type="text" :width="randomInt(5,10)+'%'"/>
|
||||||
|
<q-skeleton type="text" width="8%"/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, reactive, ref, watch} from "vue";
|
import {computed, onMounted, reactive, ref, watch} from "vue";
|
||||||
import FormWrapper from "components/FormWrapper.vue";
|
import FormWrapper from "components/FormWrapper.vue";
|
||||||
import {setDefaultData} from "src/composables/form/formHandling";
|
import {setDefaultData} from "src/composables/form/formHandling";
|
||||||
import DatetimePicker from "components/inputs/DatetimePicker.vue";
|
import DatetimePicker from "components/inputs/DatetimePicker.vue";
|
||||||
import AirportLocationInput from "components/inputs/AirportLocationInput.vue";
|
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
||||||
|
import {commonProps} from "src/composables/inputs";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
aircrafts: {
|
aircrafts: {
|
||||||
@@ -21,16 +22,14 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
aircraftId: 43,
|
aircraft: null,
|
||||||
takeoffAirportId: null,
|
takeoffAirport: null,
|
||||||
landingAirportId: null,
|
landingAirport: null,
|
||||||
takeoffDatetime: null,
|
takeoffDatetime: null,
|
||||||
landingDatetime: null,
|
landingDatetime: null,
|
||||||
landings: 1,
|
landings: 1,
|
||||||
solo: true,
|
|
||||||
|
|
||||||
name: "",
|
name: "",
|
||||||
description: ""
|
description: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
const recalculateDuration = (data) => {
|
const recalculateDuration = (data) => {
|
||||||
@@ -47,55 +46,72 @@ const isMounted = ref(false);
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.flight) {
|
if (props.flight) {
|
||||||
setDefaultData(formData, props.flight);
|
setDefaultData(formData, props.flight);
|
||||||
|
formData.aircraft = {id: formData.aircraft.id, name: formData.aircraft.callSign};
|
||||||
|
formData.takeoffAirport = {id: formData.takeoffAirport.id, name: formData.takeoffAirport.name}
|
||||||
|
formData.landingAirport = {id: formData.landingAirport.id, name: formData.landingAirport.name}
|
||||||
}
|
}
|
||||||
isMounted.value = true;
|
isMounted.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const aircrafts = computed(() => {
|
||||||
|
return props.aircrafts.map(a => ({id: a.id, name: a.callSign}));
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => formData.takeoffAirport, () => {
|
||||||
|
if (!formData.landingAirport) {
|
||||||
|
formData.landingAirport = {...formData.takeoffAirport};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormWrapper v-model="formData" v-if="isMounted">
|
<FormWrapper v-model="formData" v-if="isMounted">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<q-select
|
<ComboboxInput
|
||||||
v-model="formData.aircraftId"
|
v-bind="commonProps"
|
||||||
option-label="callSign"
|
|
||||||
option-value="id"
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
:rules="rules.aircraft"
|
|
||||||
:options="aircrafts"
|
:options="aircrafts"
|
||||||
|
v-model="formData.aircraft"
|
||||||
|
:key="formData.aircraft"
|
||||||
label="Letadlo"
|
label="Letadlo"
|
||||||
>
|
/>
|
||||||
<template #prepend>
|
|
||||||
<q-icon name="flight" />
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="formData.aircraftId">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-12 col-md-6">
|
<div class="col col-12 col-md-6">
|
||||||
<h3>Vzlet</h3>
|
<h3>Vzlet</h3>
|
||||||
<DatetimePicker v-model="formData.takeoffDatetime" label="Datum a čas vzletu: "/>
|
<DatetimePicker
|
||||||
<AirportLocationInput v-model="formData.takeoffAirportId" :airports="airports" label="Letiště vzletu" takeoff/>
|
v-model="formData.takeoffDatetime"
|
||||||
|
label="Datum a čas vzletu: "
|
||||||
|
v-bind="commonProps"
|
||||||
|
/>
|
||||||
|
<ComboboxInput
|
||||||
|
:options="airports"
|
||||||
|
v-model="formData.takeoffAirport"
|
||||||
|
label="Letiště vzletu"
|
||||||
|
v-bind="commonProps"
|
||||||
|
:enable-new-values="false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-12 col-md-6" v-if="!!formData.takeoffDatetime && !!formData.takeoffAirportId">
|
<div class="col col-12 col-md-6">
|
||||||
|
|
||||||
<h3>Přistání</h3>
|
<h3>Přistání</h3>
|
||||||
<DatetimePicker v-model="formData.landingDatetime" label="Datum a čas přistání"/>
|
<DatetimePicker
|
||||||
<AirportLocationInput v-model="formData.landingAirportId" :airports="airports" label="Letiště přistání" landing/>
|
v-model="formData.landingDatetime"
|
||||||
</div>
|
:min-date="formData.takeoffDatetime"
|
||||||
</div>
|
label="Datum a čas přistání"
|
||||||
|
v-bind="commonProps"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="row" v-if="formData.landingAirportId">
|
<ComboboxInput
|
||||||
<div class="col">
|
:options="airports"
|
||||||
<q-checkbox v-model="formData.solo" label="Solo?"/>
|
v-model="formData.landingAirport"
|
||||||
</div>
|
label="Letiště přistání"
|
||||||
<div class="col">
|
v-bind="commonProps"
|
||||||
<q-input v-model.number="formData.landings" label="Počet přistání" type="number" min="1"/>
|
:enable-new-values="false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, reactive, ref} from "vue";
|
import {onMounted, reactive, ref, watch} from "vue";
|
||||||
import FormWrapper from "components/FormWrapper.vue";
|
import FormWrapper from "components/FormWrapper.vue";
|
||||||
import {setDefaultData} from "src/composables/form/formHandling";
|
import {setDefaultData} from "src/composables/form/formHandling";
|
||||||
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
||||||
@@ -16,14 +16,20 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
solo: true,
|
|
||||||
withInstructor: false,
|
|
||||||
copilot: null,
|
copilot: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const solo = ref(true);
|
||||||
|
watch(() => solo.value, (newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
formData.copilot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.flight) {
|
if (props.flight) {
|
||||||
setDefaultData(formData, props.flight);
|
setDefaultData(formData, props.flight);
|
||||||
|
solo.value = !formData.copilot;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -32,9 +38,15 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<FormWrapper v-model="formData">
|
<FormWrapper v-model="formData">
|
||||||
|
|
||||||
<q-btn-toggle spread no-caps v-model="formData.solo" toggle-color="primary" :options="[{label: 'Solo', value: true}, {label: 'S někým', value: false}]"/>
|
<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="!formData.solo">
|
<div v-if="!solo">
|
||||||
<ComboboxInput
|
<ComboboxInput
|
||||||
class="q-my-lg"
|
class="q-my-lg"
|
||||||
outlined
|
outlined
|
||||||
@@ -45,9 +57,6 @@ onMounted(() => {
|
|||||||
:clearable="true"
|
:clearable="true"
|
||||||
label="Druhy pilot"
|
label="Druhy pilot"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- <q-checkbox v-model="formData.withInstructor" v-if="formData.copilot" label="Instruktor"/>-->
|
|
||||||
<!-- <q-input v-model="formData.picTime" label="PIC time"/>-->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {onMounted, ref} from "vue";
|
|||||||
import FormWrapper from "components/FormWrapper.vue";
|
import FormWrapper from "components/FormWrapper.vue";
|
||||||
import {setDefaultData} from "src/composables/form/formHandling";
|
import {setDefaultData} from "src/composables/form/formHandling";
|
||||||
import RichTextEditor from "components/inputs/RichTextEditor.vue";
|
import RichTextEditor from "components/inputs/RichTextEditor.vue";
|
||||||
|
import {commonProps} from "src/composables/inputs";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
flight: {
|
flight: {
|
||||||
@@ -28,7 +29,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormWrapper v-model="formData" v-if="isMounted">
|
<FormWrapper v-model="formData" v-if="isMounted">
|
||||||
<q-input type="text" v-model="formData.name" label="Název"/>
|
<q-input type="text" v-model="formData.name" label="Jméno letu" v-bind="commonProps" />
|
||||||
<RichTextEditor v-model="formData.description" label="Popis letu, poznámky, ..." />
|
<RichTextEditor v-model="formData.description" v-bind="commonProps" label="Popis letu, poznámky, ..." />
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import FormWrapper from "components/FormWrapper.vue";
|
import FormWrapper from "components/FormWrapper.vue";
|
||||||
import {onMounted, reactive, ref, watch} from "vue";
|
import {onMounted, reactive, ref, watch} from "vue";
|
||||||
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
||||||
|
import {commonProps} from "src/composables/inputs";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@@ -18,7 +19,8 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
track: []
|
track: [],
|
||||||
|
gpxTrack: null
|
||||||
});
|
});
|
||||||
const removeTrackItem = (index) => {
|
const removeTrackItem = (index) => {
|
||||||
formData.track.splice(index, 1);
|
formData.track.splice(index, 1);
|
||||||
@@ -48,16 +50,22 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormWrapper v-model="formData">
|
<FormWrapper v-model="formData">
|
||||||
|
|
||||||
|
<q-file v-model="formData.gpxTrack" v-bind="commonProps" label="Trasa v GPX" hint="Slouží pro vykreslení kompletní a detailní trasy"/>
|
||||||
|
|
||||||
|
<template v-if="flight.gpxTrackUrl">
|
||||||
|
<q-icon name="check" color="green" /> Trasa nahrána
|
||||||
|
</template>
|
||||||
|
|
||||||
<ComboboxInput
|
<ComboboxInput
|
||||||
outlined
|
v-bind="commonProps"
|
||||||
standout
|
|
||||||
class="q-my-lg"
|
|
||||||
v-model="poi"
|
v-model="poi"
|
||||||
:key="formData.track.length"
|
:key="formData.track.length"
|
||||||
:options="locations"
|
:options="locations"
|
||||||
@select="addTrackItem"
|
@select="addTrackItem"
|
||||||
clearable
|
clearable
|
||||||
label="Otočný bod"
|
label="Otočný bod"
|
||||||
|
hint="Nastav významné otočné body"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-list bordered>
|
<q-list bordered>
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import {onMounted, ref} from "vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: [Number]
|
|
||||||
},
|
|
||||||
airports: {
|
|
||||||
type: Array,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
takeoff: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
landing: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
|
||||||
const model = ref(null);
|
|
||||||
const emitModelValue = (val) => {
|
|
||||||
emit("update:modelValue", val);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
model.value = props.modelValue;
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-select
|
|
||||||
v-model="model"
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
option-label="name"
|
|
||||||
option-value="id"
|
|
||||||
clearable
|
|
||||||
:options="airports"
|
|
||||||
@input="emitModelValue"
|
|
||||||
>
|
|
||||||
<template #prepend v-if="takeoff">
|
|
||||||
<q-icon name="flight_takeoff"/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #append v-if="landing">
|
|
||||||
<q-icon name="flight_land"/>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</template>
|
|
||||||
@@ -10,6 +10,11 @@ const props = defineProps({
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
enableNewValues: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -17,9 +22,9 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const model = ref({});
|
const model = ref({});
|
||||||
onMounted(() => {
|
watch(() => props.modelValue, (newVal) => {
|
||||||
model.value = props.modelValue;
|
model.value = newVal;
|
||||||
})
|
}, {immediate: true});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'clear']);
|
const emit = defineEmits(['update:modelValue', 'clear']);
|
||||||
watch(model, (val) => {
|
watch(model, (val) => {
|
||||||
@@ -84,7 +89,7 @@ const filterFn = (val, update, abort) => {
|
|||||||
:options="availableOptions"
|
:options="availableOptions"
|
||||||
option-label="name"
|
option-label="name"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
new-value-mode="add"
|
:new-value-mode="enableNewValues ? 'add' : 'toggle'"
|
||||||
clear-icon="clear"
|
clear-icon="clear"
|
||||||
:v-bind="$attrs"
|
:v-bind="$attrs"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ const props = defineProps({
|
|||||||
type: [String, Boolean],
|
type: [String, Boolean],
|
||||||
required: false,
|
required: false,
|
||||||
default: FORMAT_DATETIME_LONG
|
default: FORMAT_DATETIME_LONG
|
||||||
|
},
|
||||||
|
minDate: {
|
||||||
|
type: [String, Date],
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let dateModel = ref("");
|
let dateModel = ref("");
|
||||||
@@ -36,6 +39,7 @@ onMounted(() => {
|
|||||||
emitModelUpdate();
|
emitModelUpdate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// TODO: ^^ by asi slo prepsat do watchera, viz ComboboxInput
|
||||||
|
|
||||||
|
|
||||||
const publicModel = computed(() => {
|
const publicModel = computed(() => {
|
||||||
@@ -92,7 +96,12 @@ const myLocale = {
|
|||||||
pluralDay: 'dny'
|
pluralDay: 'dny'
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://quasar.dev/vue-composables/use-form-child - mozna resi ohack s onMounted()?
|
const isDateValid = (date) => {
|
||||||
|
if (!props.minDate) return true;
|
||||||
|
|
||||||
|
const minDate = formatDatetime(props.minDate, "YYYY/MM/DD");
|
||||||
|
return date >= minDate;
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -107,10 +116,10 @@ const myLocale = {
|
|||||||
<div class="proxy-wrapper">
|
<div class="proxy-wrapper">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-sm-6" v-if="showDatePicker">
|
<div class="col-12 col-sm-6" v-if="showDatePicker">
|
||||||
<q-date square v-model="dateModel" :today-btn="true" flat mask="YYYY-MM-DD" :locale="myLocale"/>
|
<q-date square v-model="dateModel" :options="isDateValid" :today-btn="true" flat mask="YYYY-MM-DD" :locale="myLocale"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-sm-6" v-if="showTimePicker">
|
<div class="col-12 col-sm-6" v-if="showTimePicker">
|
||||||
<q-time square v-model="timeModel" format24h flat mask="HH:mm"/>
|
<q-time square v-model="timeModel" format24h flat mask="HH:mm:ss"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mb-sm">
|
<div class="row q-mb-sm">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import {Marker} from "vue3-google-map";
|
import {Marker} from "vue3-google-map";
|
||||||
import {onMounted, ref} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import MapWrapper from "components/MapWrapper.vue";
|
import MapWrapper from "components/map/MapWrapper.vue";
|
||||||
|
|
||||||
const center = ref({
|
const center = ref({
|
||||||
lat: 50.1314,
|
lat: 50.1314,
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script setup>
|
||||||
|
import {computed} from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
address: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
gpsLatitude: {
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
gpsLongitude: {
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 12
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: "500px"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const addressEncoded = computed(() => {
|
||||||
|
if (!props.gpsLatitude || !props.gpsLongitude) {
|
||||||
|
return encodeURI(props.address);
|
||||||
|
} else {
|
||||||
|
return `${props.gpsLatitude},${props.gpsLongitude}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<iframe frameborder="0" scrolling="no" marginheight="0" marginwidth="0"
|
||||||
|
:src="`https://maps.google.com/maps?q=${addressEncoded}&t=m&z=${zoom}&output=embed&iwloc=near`"
|
||||||
|
:style="`width: 100%; height: ${height}`"
|
||||||
|
:aria-label="address"/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<script setup>
|
||||||
|
import MapWrapper from "components/map/MapWrapper.vue";
|
||||||
|
import {Marker, Polyline, InfoWindow} from "vue3-google-map";
|
||||||
|
import {computed, onMounted, ref} from "vue";
|
||||||
|
import {axios} from "boot/axios";
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
gpxTrackUrl: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
photos: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const track = ref([]);
|
||||||
|
const gpxLoading = ref(!!props.gpxTrackUrl);
|
||||||
|
onMounted(() => {
|
||||||
|
if (!props.gpxTrackUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gpxLoading.value = true;
|
||||||
|
axios.get(props.gpxTrackUrl)
|
||||||
|
.then(({data}) => (new window.DOMParser()).parseFromString(data, "text/xml"))
|
||||||
|
.then(doc => {
|
||||||
|
const points = [];
|
||||||
|
const nodes = [...doc.getElementsByTagName('trkpt')];
|
||||||
|
nodes.forEach(node => {
|
||||||
|
const lat = parseFloat(node.getAttribute("lat"));
|
||||||
|
const lng = parseFloat(node.getAttribute("lon"));
|
||||||
|
points.push({lat, lng});
|
||||||
|
})
|
||||||
|
|
||||||
|
track.value = points; // puvodne se bounds bralo z tohodle
|
||||||
|
})
|
||||||
|
.finally(() => gpxLoading.value = false);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const bounds = computed(() => {
|
||||||
|
const photosTransformed = props.photos.map(p => ({position: {lat: p.gpsLatitude, lng: p.gpsLongitude}}));
|
||||||
|
const allData = [...props.markers, ...track.value, ...photosTransformed];
|
||||||
|
return allData.filter(i => !!i.position).map(i => i.position);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="gpxLoading" v-bind="$attrs">Loading</div>
|
||||||
|
<MapWrapper v-bind="$attrs" :bounds="bounds" v-if="!gpxLoading">
|
||||||
|
<Marker v-for="(marker, idx) in markers" :key="idx" :options="marker"/>
|
||||||
|
|
||||||
|
<Marker v-for="(photo, idx) in photos" :key="idx" :options="{icon: '/mapIcons/photo-16x16.png', position: {lat: photo.gpsLatitude, lng: photo.gpsLongitude}}">
|
||||||
|
<InfoWindow :options="{position: {lat: photo.gpsLatitude, lng: photo.gpsLongitude}}">
|
||||||
|
<img :src="photo.thumbnailUrl" />
|
||||||
|
</InfoWindow>
|
||||||
|
</Marker>
|
||||||
|
|
||||||
|
<template v-if="track.length > 0">
|
||||||
|
<Polyline :options="{path: track, geodesic: true, strokeColor: '#ff0000', strokeOpacity: 0.3, strokeWeight: 5}"/>
|
||||||
|
</template>
|
||||||
|
</MapWrapper>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup>
|
||||||
|
import {GoogleMap} from "vue3-google-map";
|
||||||
|
import {ref, watch} from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
bounds: {
|
||||||
|
type: Array,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = process.env.GOOGLE_MAPS_API_KEY;
|
||||||
|
|
||||||
|
|
||||||
|
const mapRef = ref(null);
|
||||||
|
watch(
|
||||||
|
() => mapRef.value?.ready,
|
||||||
|
(ready) => {
|
||||||
|
if (!props.bounds || !ready) return;
|
||||||
|
const map = mapRef.value.map;
|
||||||
|
const api = mapRef.value.api;
|
||||||
|
|
||||||
|
const bounds = new api.LatLngBounds();
|
||||||
|
for (let i = 0; i < props.bounds.length; i++) {
|
||||||
|
bounds.extend(props.bounds[i]);
|
||||||
|
}
|
||||||
|
map.fitBounds(bounds);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<GoogleMap :api-key="key" v-bind="$attrs" :max-zoom="13" ref="mapRef">
|
||||||
|
<slot/>
|
||||||
|
</GoogleMap>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<script setup>
|
||||||
|
import EmbeddedMap from "components/map/EmbeddedMap.vue";
|
||||||
|
import {computed, onMounted, ref} from "vue";
|
||||||
|
import {formatDatetime} from "src/composables/datetimeUtils";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
photos: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
openedIndex: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue"])
|
||||||
|
|
||||||
|
|
||||||
|
const open = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue;
|
||||||
|
},
|
||||||
|
set(newVal) {
|
||||||
|
emit("update:modelValue", newVal);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const photoIndex = ref(null);
|
||||||
|
onMounted(() => {
|
||||||
|
photoIndex.value = props.openedIndex
|
||||||
|
});
|
||||||
|
|
||||||
|
const photo = computed(() => props.photos[photoIndex.value]);
|
||||||
|
|
||||||
|
const nextPhoto = () => {
|
||||||
|
if (photoIndex.value < props.photos.length - 1)
|
||||||
|
photoIndex.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevPhoto = () => {
|
||||||
|
if (photoIndex.value > 0) photoIndex.value--;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-dialog v-model="open" full-width @keyup.left="prevPhoto" @keyup.right="nextPhoto">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-12 col-md-9">
|
||||||
|
<q-img :src="photo.url"/>
|
||||||
|
<strong v-if="photo.name !== ''">{{ photo.name }}</strong>
|
||||||
|
|
||||||
|
<p v-if="photo.description !== ''">{{ photo.description }}</p>
|
||||||
|
|
||||||
|
<div class="text-center q-my-sm">
|
||||||
|
<q-btn @click="prevPhoto" v-if="photoIndex > 0" icon="chevron_left" flat rounded>Předchozí</q-btn>
|
||||||
|
<q-btn @click="nextPhoto" v-if="photoIndex < photos.length - 1" icon-right="chevron_right" flat rounded>Další</q-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-12 col-md-3">
|
||||||
|
<EmbeddedMap class="q-mx-sm" v-if="photo.gpsLatitude && photo.gpsLongitude" :gps-latitude="photo.gpsLatitude" :gps-longitude="photo.gpsLongitude"/>
|
||||||
|
|
||||||
|
<q-list>
|
||||||
|
<q-item v-if="photo.pointOfInterest">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar icon="marker"></q-avatar>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
{{ formatDatetime(photo.exposedAt) }}
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item v-if="photo.exposedAt">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar icon="photo_camera"></q-avatar>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
{{ formatDatetime(photo.exposedAt) }}
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item v-if="photo.gpsAltitude">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar icon="height"/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
{{ photo.gpsAltitude }} m
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {computed, onMounted, ref} from "vue";
|
import {computed, ref} from "vue";
|
||||||
import {setDefaultData} from "src/composables/form/formHandling";
|
import {setDefaultData} from "src/composables/form/formHandling";
|
||||||
import DialogWrapper from "components/DialogWrapper.vue";
|
import DialogWrapper from "components/DialogWrapper.vue";
|
||||||
import FormWrapper from "components/FormWrapper.vue";
|
import FormWrapper from "components/FormWrapper.vue";
|
||||||
@@ -10,7 +10,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const emits = defineEmits(["edit", "delete", "detail"])
|
const emits = defineEmits(["edit", "delete", "openDetail"])
|
||||||
|
|
||||||
const openEdit = (photo) => {
|
const openEdit = (photo) => {
|
||||||
emits("edit", photo);
|
emits("edit", photo);
|
||||||
@@ -20,16 +20,16 @@ const deletePhoto = (id) => {
|
|||||||
emits("delete", id);
|
emits("delete", id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDetail = (photo) => {
|
const openDetail = (photo, idx) => {
|
||||||
emits("detail", photo);
|
emits("openDetail", {photo, idx, allPhotos: props.photos});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-4 col-lg-3" v-for="photo in photos" :key="photo.id">
|
<div class="col-12 col-sm-4 col-md-4 col-lg-3" v-for="(photo, idx) in photos" :key="photo.id">
|
||||||
<q-card class="q-my-sm q-mx-sm">
|
<q-card class="q-my-sm q-mx-sm">
|
||||||
<q-img :src="photo.thumbnailUrl" @click="openDetail">
|
<q-img :src="photo.thumbnailUrl" @click="openDetail(photo, idx)">
|
||||||
<div class="absolute-bottom">
|
<div class="absolute-bottom">
|
||||||
<div class="text-h6" v-if="photo.name !== ''">
|
<div class="text-h6" v-if="photo.name !== ''">
|
||||||
{{ photo.name }}
|
{{ photo.name }}
|
||||||
@@ -40,8 +40,14 @@ const openDetail = (photo) => {
|
|||||||
</div>
|
</div>
|
||||||
</q-img>
|
</q-img>
|
||||||
|
|
||||||
<q-card-actions v-if="showActions">
|
<q-card-actions v-if="showActions" align="right">
|
||||||
{{ photo.isFlightCover ? 'ano' : 'ne' }}
|
<div class="col-grow">
|
||||||
|
<q-icon name="star" v-if="photo.isFlightCover" color="red"/>
|
||||||
|
<q-icon name="place" v-if="photo.pointOfInterestId" />
|
||||||
|
<q-icon name="gps_fixed" v-if="photo.gpsLatitude && photo.gpsLongitude" />
|
||||||
|
<q-icon name="gps_off" v-else />
|
||||||
|
</div>
|
||||||
|
|
||||||
<q-btn flat icon="edit" color="blue" @click="openEdit(photo)"></q-btn>
|
<q-btn flat icon="edit" color="blue" @click="openEdit(photo)"></q-btn>
|
||||||
<q-btn flat icon="delete" color="red" @click="deletePhoto(photo.id)"></q-btn>
|
<q-btn flat icon="delete" color="red" @click="deletePhoto(photo.id)"></q-btn>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const formData = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const poiNameForMap = ref("");
|
const poiNameForMap = ref("");
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setDefaultData(formData, props.poi);
|
setDefaultData(formData, props.poi);
|
||||||
poiNameForMap.value = props.poi.name;
|
poiNameForMap.value = props.poi.name;
|
||||||
@@ -46,7 +45,6 @@ const handlePositionNotFound = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormWrapper v-model="formData">
|
<FormWrapper v-model="formData">
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
class="q-my-lg"
|
class="q-my-lg"
|
||||||
outlined
|
outlined
|
||||||
@@ -72,7 +70,5 @@ const handlePositionNotFound = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
|
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
|
||||||
|
|
||||||
|
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
cloudcover: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-icon name="mdi-weather-sunny" v-if="cloudcover < 20"/>
|
||||||
|
<q-icon name="mdi-weather-partly-cloudy" v-else-if="cloudcover < 75"/>
|
||||||
|
<q-icon name="mdi-weather-cloudy" v-else/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<script setup>
|
||||||
|
import {ref} from "vue";
|
||||||
|
import CloudCoverIcon from "components/weather/CloudCoverIcon.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
weather: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const kmhToKnots = (speedKmh) => {
|
||||||
|
return Math.round(speedKmh / 0.53996);
|
||||||
|
}
|
||||||
|
|
||||||
|
const openDetail = ref(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<q-btn rounded @click="openDetail = true" v-bind="$attrs">
|
||||||
|
<CloudCoverIcon :cloudcover="weather.cloudcoverTotal"/>
|
||||||
|
|
||||||
|
<q-icon name="navigation" :style="`transform: rotate(${weather.windDirectionSurface}deg)`"/>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-dialog v-model="openDetail">
|
||||||
|
<q-card>
|
||||||
|
<q-list>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<CloudCoverIcon :cloudcover="weather.cloudcoverTotal"/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
Pokrytí oblačností (celkové): {{ weather.cloudcoverTotal }} %
|
||||||
|
<br>
|
||||||
|
Nízké mraky: {{ weather.cloudcoverLow }} %
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
<q-item-section avatar>
|
||||||
|
QNH
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
{{ weather.qnh }} hPa
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="air"/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<div :title="`${weather.windSpeedSurface} km/h`">
|
||||||
|
{{ kmhToKnots(weather.windSpeedSurface) }} kt, {{ weather.windDirectionSurface }} °
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-thermometer"/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
Teplota: {{ weather.temperatureSurface }} ° C
|
||||||
|
<br>
|
||||||
|
Rosný bod: {{ weather.dewpointSurface }} ° C
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const commonProps = {
|
||||||
|
class: "q-my-lg",
|
||||||
|
outlined: true,
|
||||||
|
standout: true
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ export const aircraft = ref(null);
|
|||||||
|
|
||||||
export const fetchAircraft = (aircraftId) => {
|
export const fetchAircraft = (aircraftId) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
useAircraftsStore().fetchDetail(aircraftId).then((data) => {
|
return useAircraftsStore().fetchDetail(aircraftId).then((data) => {
|
||||||
aircraft.value = data;
|
aircraft.value = data;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
@@ -17,8 +17,11 @@ export const loading = ref(true);
|
|||||||
export const flight = ref(null);
|
export const flight = ref(null);
|
||||||
export const fetchFlight = (flightId) => {
|
export const fetchFlight = (flightId) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
useFlightsStore().fetchDetail(parseInt(flightId))
|
return useFlightsStore().fetchDetail(parseInt(flightId))
|
||||||
.then(respFlight => flight.value = respFlight)
|
.then(respFlight => {
|
||||||
|
flight.value = respFlight;
|
||||||
|
return respFlight;
|
||||||
|
})
|
||||||
.finally(() => loading.value = false);
|
.finally(() => loading.value = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,7 +35,8 @@ export const fetchFlights = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const handleDeleteFlight = ($q, flightId, onOkCallback = () => {}) => {
|
export const handleDeleteFlight = ($q, flightId, onOkCallback = () => {
|
||||||
|
}) => {
|
||||||
$q.dialog(confirmObject(`Opravdu chceš smazat tento let?`))
|
$q.dialog(confirmObject(`Opravdu chceš smazat tento let?`))
|
||||||
.onOk(() => {
|
.onOk(() => {
|
||||||
useFlightsStore().delete(flightId).then(onOkCallback);
|
useFlightsStore().delete(flightId).then(onOkCallback);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
export const FRAGMENT_COPILOT_LIST_INFO = gql`
|
||||||
|
fragment copilotListInfo on Copilot {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FRAGMENT_COPILOT_FULL_INFO = gql`
|
||||||
|
fragment copilotFullInfo on Copilot {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -2,6 +2,20 @@ import gql from "graphql-tag";
|
|||||||
import {FRAGMENT_AIRCRAFT_BASIC_INFO, FRAGMENT_AIRCRAFT_FULL_INFO} from "src/graphql/fragments/aircraft";
|
import {FRAGMENT_AIRCRAFT_BASIC_INFO, FRAGMENT_AIRCRAFT_FULL_INFO} from "src/graphql/fragments/aircraft";
|
||||||
import {FRAGMENT_AIRPORT_INFO} from "src/graphql/fragments/airport";
|
import {FRAGMENT_AIRPORT_INFO} from "src/graphql/fragments/airport";
|
||||||
import {FRAGMENT_PHOTO_FULL_INFO} from "src/graphql/fragments/photo";
|
import {FRAGMENT_PHOTO_FULL_INFO} from "src/graphql/fragments/photo";
|
||||||
|
import {FRAGMENT_POI_FULL_INFO} from "src/graphql/fragments/poi";
|
||||||
|
|
||||||
|
export const FRAGMENT_WEATHER_FULL_INFO = gql`
|
||||||
|
fragment weatherFullInfo on WeatherInfo {
|
||||||
|
datetime
|
||||||
|
qnh
|
||||||
|
temperatureSurface
|
||||||
|
dewpointSurface
|
||||||
|
windSpeedSurface
|
||||||
|
windDirectionSurface
|
||||||
|
cloudcoverTotal
|
||||||
|
cloudcoverLow
|
||||||
|
rain
|
||||||
|
}`;
|
||||||
|
|
||||||
export const FRAGMENT_FLIGHT_FULL_INFO = gql`
|
export const FRAGMENT_FLIGHT_FULL_INFO = gql`
|
||||||
fragment flightFullInfo on Flight {
|
fragment flightFullInfo on Flight {
|
||||||
@@ -12,8 +26,6 @@ fragment flightFullInfo on Flight {
|
|||||||
landingDatetime
|
landingDatetime
|
||||||
landings
|
landings
|
||||||
durationMinCalculated
|
durationMinCalculated
|
||||||
solo
|
|
||||||
withInstructor
|
|
||||||
|
|
||||||
coverPhoto {
|
coverPhoto {
|
||||||
thumbnailUrl
|
thumbnailUrl
|
||||||
@@ -24,12 +36,10 @@ fragment flightFullInfo on Flight {
|
|||||||
...airportInfo
|
...airportInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gpxTrackUrl
|
||||||
track {
|
track {
|
||||||
pointOfInterest {
|
pointOfInterest {
|
||||||
id
|
...pointOfInterestFullInfo
|
||||||
name
|
|
||||||
gpsLatitude
|
|
||||||
gpsLongitude
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +47,15 @@ fragment flightFullInfo on Flight {
|
|||||||
...airportInfo
|
...airportInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
landingWeatherInfo {
|
||||||
|
...weatherFullInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
takeoffWeatherInfo {
|
||||||
|
...weatherFullInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
aircraft {
|
aircraft {
|
||||||
...aircraftFullInfo
|
...aircraftFullInfo
|
||||||
}
|
}
|
||||||
@@ -49,6 +68,8 @@ fragment flightFullInfo on Flight {
|
|||||||
${FRAGMENT_PHOTO_FULL_INFO}
|
${FRAGMENT_PHOTO_FULL_INFO}
|
||||||
${FRAGMENT_AIRCRAFT_FULL_INFO}
|
${FRAGMENT_AIRCRAFT_FULL_INFO}
|
||||||
${FRAGMENT_AIRPORT_INFO}
|
${FRAGMENT_AIRPORT_INFO}
|
||||||
|
${FRAGMENT_POI_FULL_INFO}
|
||||||
|
${FRAGMENT_WEATHER_FULL_INFO}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const FRAGMENT_FLIGHT_LIST_INFO = gql`
|
export const FRAGMENT_FLIGHT_LIST_INFO = gql`
|
||||||
@@ -92,10 +113,3 @@ fragment flightEditIds on Flight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// export const FRAGMENT_LOCATION = gql`
|
|
||||||
// fragment airportInfo on Airport {
|
|
||||||
// id
|
|
||||||
// name
|
|
||||||
// }
|
|
||||||
// `;
|
|
||||||
|
|||||||
@@ -11,4 +11,8 @@ export const FRAGMENT_PHOTO_FULL_INFO = gql`
|
|||||||
flightId
|
flightId
|
||||||
pointOfInterestId
|
pointOfInterestId
|
||||||
isFlightCover
|
isFlightCover
|
||||||
|
gpsLatitude
|
||||||
|
gpsLongitude
|
||||||
|
gpsAltitude
|
||||||
|
exposedAt
|
||||||
}`;
|
}`;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import gql from "graphql-tag";
|
||||||
|
import {FRAGMENT_COPILOT_FULL_INFO} from "src/graphql/fragments/copilot";
|
||||||
|
|
||||||
|
export const CREATE_COPILOT = gql`
|
||||||
|
mutation createCopilot($input: CreateCopilotInput!) {
|
||||||
|
createCopilot(input: $input) {
|
||||||
|
...copilotFullInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${FRAGMENT_COPILOT_FULL_INFO}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EDIT_COPILOT = gql`
|
||||||
|
mutation editCopilot($id: Int!, $input: EditCopilotInput!) {
|
||||||
|
editCopilot(id: $id, input: $input) {
|
||||||
|
...copilotFullInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${FRAGMENT_COPILOT_FULL_INFO}
|
||||||
|
`;
|
||||||
|
export const DELETE_COPILOT = gql`
|
||||||
|
mutation deleteCopilot($id: Int!) {
|
||||||
|
deleteCopilot(id: $id) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
import {FRAGMENT_AIRCRAFT_FULL_INFO} from "src/graphql/fragments/aircraft";
|
import {FRAGMENT_AIRCRAFT_FULL_INFO} from "src/graphql/fragments/aircraft";
|
||||||
|
import {FRAGMENT_FLIGHT_LIST_INFO} from "src/graphql/fragments/flight";
|
||||||
|
|
||||||
export const AIRCRAFTS_LIST = gql`
|
export const AIRCRAFTS_LIST = gql`
|
||||||
query AircraftsList {
|
query AircraftsList {
|
||||||
@@ -16,8 +17,13 @@ export const AIRCRAFT_DETAIL = gql`
|
|||||||
query AircraftDetail($id: Int!) {
|
query AircraftDetail($id: Int!) {
|
||||||
aircraft(id: $id) {
|
aircraft(id: $id) {
|
||||||
...aircraftFullInfo
|
...aircraftFullInfo
|
||||||
|
|
||||||
|
flights {
|
||||||
|
...flightListInfo
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${FRAGMENT_AIRCRAFT_FULL_INFO}
|
${FRAGMENT_AIRCRAFT_FULL_INFO}
|
||||||
|
${FRAGMENT_FLIGHT_LIST_INFO}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
import {FRAGMENT_FLIGHT_LIST_INFO} from "src/graphql/fragments/flight";
|
import {FRAGMENT_FLIGHT_LIST_INFO} from "src/graphql/fragments/flight";
|
||||||
|
import {FRAGMENT_COPILOT_FULL_INFO, FRAGMENT_COPILOT_LIST_INFO} from "src/graphql/fragments/copilot";
|
||||||
|
|
||||||
export const COPILOTS_LIST = gql`
|
export const COPILOTS_LIST = gql`
|
||||||
query copilotsList {
|
query copilotsList {
|
||||||
copilots {
|
copilots {
|
||||||
id
|
...copilotListInfo
|
||||||
name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${FRAGMENT_COPILOT_LIST_INFO}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const COPILOTS_DETAIL = gql`
|
export const COPILOT_DETAIL = gql`
|
||||||
query copilot($id: Int!) {
|
query copilot($id: Int!) {
|
||||||
copilot(id: $id) {
|
copilot(id: $id) {
|
||||||
id
|
...copilotFullInfo
|
||||||
name
|
|
||||||
|
|
||||||
flights {
|
flights {
|
||||||
...flightListInfo
|
...flightListInfo
|
||||||
@@ -22,5 +23,6 @@ query copilot($id: Int!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${FRAGMENT_COPILOT_FULL_INFO}
|
||||||
${FRAGMENT_FLIGHT_LIST_INFO}
|
${FRAGMENT_FLIGHT_LIST_INFO}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -26,11 +26,19 @@
|
|||||||
|
|
||||||
<q-img class="absolute-top" src="https://galerie.kvacek.cz/_data/i/upload/2022/06/22/20220622102027-b104fc0d-me.jpg" style="height: 150px">
|
<q-img class="absolute-top" src="https://galerie.kvacek.cz/_data/i/upload/2022/06/22/20220622102027-b104fc0d-me.jpg" style="height: 150px">
|
||||||
<div class="absolute-bottom bg-transparent">
|
<div class="absolute-bottom bg-transparent">
|
||||||
<q-avatar size="56px" class="q-mb-sm">
|
<router-link :to="{name: 'profile'}">
|
||||||
|
<q-avatar size="56px" class="q-mb-sm" v-if="loggedUser.avatarImageUrl">
|
||||||
<q-img :src="loggedUser.avatarImageUrl"/>
|
<q-img :src="loggedUser.avatarImageUrl"/>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
|
<q-avatar icon="person" color="primary" text-color="white" size="56px" v-else/>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
<div class="text-weight-bold">{{ loggedUser.name }}</div>
|
<router-link
|
||||||
|
v-if="loggedUser.name !== ''"
|
||||||
|
:to="{name: 'profile'}"
|
||||||
|
class="text-weight-bold">
|
||||||
|
{{ loggedUser.name }}
|
||||||
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</q-img>
|
</q-img>
|
||||||
<q-scroll-area style="height: calc(100% - 150px); margin-top: 150px; border-right: 1px solid #ddd">
|
<q-scroll-area style="height: calc(100% - 150px); margin-top: 150px; border-right: 1px solid #ddd">
|
||||||
@@ -47,7 +55,7 @@
|
|||||||
</q-drawer>
|
</q-drawer>
|
||||||
|
|
||||||
<q-page-container class="q-px-md">
|
<q-page-container class="q-px-md">
|
||||||
<BreadcrumbsNavigation class="q-my-md" :items="breadcrumbs"/>
|
<BreadcrumbsNavigation class="q-mx-md q-my-md" :items="breadcrumbs"/>
|
||||||
<router-view/>
|
<router-view/>
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
</q-layout>
|
</q-layout>
|
||||||
@@ -94,7 +102,7 @@ const linksList = [
|
|||||||
{
|
{
|
||||||
title: 'Spolucestující',
|
title: 'Spolucestující',
|
||||||
icon: "people",
|
icon: "people",
|
||||||
to: {name: "pilots"}
|
to: {name: "copilots"}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -103,9 +111,8 @@ const toggleLeftDrawer = () => {
|
|||||||
leftDrawerOpen.value = !leftDrawerOpen.value
|
leftDrawerOpen.value = !leftDrawerOpen.value
|
||||||
};
|
};
|
||||||
|
|
||||||
const loggedUserStore = useUserStore();
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loggedUserStore.setupCredentials();
|
useUserStore().setupCredentials();
|
||||||
})
|
})
|
||||||
const loggedUser = storeToRefs(loggedUserStore).user;
|
const loggedUser = storeToRefs(useUserStore()).user;
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,14 +4,6 @@ import {onMounted} from "vue";
|
|||||||
import {useFlightsStore} from "stores/Flights";
|
import {useFlightsStore} from "stores/Flights";
|
||||||
|
|
||||||
const loggedUser = useUserStore().user
|
const loggedUser = useUserStore().user
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
useUserStore().setupCredentials();
|
|
||||||
setInterval(() => {
|
|
||||||
useFlightsStore().fetch();
|
|
||||||
}, 1000);
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {confirmObject, successObject} from "src/composables/dialog";
|
|||||||
import {loading, aircraft, fetchAircraft} from "src/composables/objects/aircraft";
|
import {loading, aircraft, fetchAircraft} from "src/composables/objects/aircraft";
|
||||||
import FAB from "components/FAB.vue";
|
import FAB from "components/FAB.vue";
|
||||||
import {useAppStore} from "stores/App";
|
import {useAppStore} from "stores/App";
|
||||||
|
import FlightsList from "components/flights/FlightsList.vue";
|
||||||
|
import {copilot} from "src/composables/objects/copilot";
|
||||||
|
|
||||||
const aircraftId = parseInt(useRoute().params.id);
|
const aircraftId = parseInt(useRoute().params.id);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -47,23 +49,24 @@ watch(aircraft, (newVal) => {
|
|||||||
<q-item>
|
<q-item>
|
||||||
Výrobce: {{ aircraft.manufacturer }}
|
Výrobce: {{ aircraft.manufacturer }}
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
||||||
<q-item>
|
<q-item>
|
||||||
Model: {{ aircraft.model }}
|
Model: {{ aircraft.model }}
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
||||||
</q-list>
|
</q-list>
|
||||||
|
|
||||||
|
<h3>Lety</h3>
|
||||||
|
<q-banner v-if="aircraft.flights.length === 0" class="bg-light-blue text-white">
|
||||||
|
Zatím žádné lety.
|
||||||
|
</q-banner>
|
||||||
|
<FlightsList :items="aircraft.flights" v-else/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn @click="confirmDelete(aircraft.id)" icon="delete" text-color="red" rounded flat/>
|
<q-btn @click="confirmDelete(aircraft.id)" icon="delete" text-color="red" rounded flat/>
|
||||||
|
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <h2>Limits</h2>-->
|
<!-- <h2>Limits</h2>-->
|
||||||
<!-- Wind: {{ aircraft.limits.wind.frontKt }}/{{ aircraft.limits.wind.sideKt }} kt-->
|
<!-- Wind: {{ aircraft.limits.wind.frontKt }}/{{ aircraft.limits.wind.sideKt }} kt-->
|
||||||
<!-- Neprekrocitelna rychlost: {{ aircraft.limits.speed.vmax }}-->
|
<!-- Neprekrocitelna rychlost: {{ aircraft.limits.speed.vmax }}-->
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ onMounted(() => {
|
|||||||
fetchCopilot(copilotId).then(() => {
|
fetchCopilot(copilotId).then(() => {
|
||||||
useAppStore().setPageTitle(copilot.value.name);
|
useAppStore().setPageTitle(copilot.value.name);
|
||||||
useAppStore().setBreadcrumbs([
|
useAppStore().setBreadcrumbs([
|
||||||
{to: {name: "pilots"}, label: "Kopiloti"},
|
{to: {name: "copilots"}, label: "Kopiloti"},
|
||||||
{label: copilot.value.name}
|
{label: copilot.value.name}
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
@@ -29,6 +29,6 @@ onMounted(() => {
|
|||||||
</q-banner>
|
</q-banner>
|
||||||
<FlightsList :items="copilot.flights" v-else/>
|
<FlightsList :items="copilot.flights" v-else/>
|
||||||
|
|
||||||
<FAB mode="edit" :router-link="{name: 'pilotEdit', params: {id: copilotId}}"/>
|
<FAB mode="edit" :router-link="{name: 'copilotEdit', params: {id: copilotId}}"/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {useRoute, useRouter} from "vue-router";
|
import {useRoute, useRouter} from "vue-router";
|
||||||
import {computed, onMounted, watch} from "vue";
|
import {computed, onMounted, ref, watch} from "vue";
|
||||||
import FlightDetailSkeleton from "components/flights/FlightDetailSkeleton.vue";
|
import FlightDetailSkeleton from "components/flights/FlightDetailSkeleton.vue";
|
||||||
import {fetchFlight, flight, flightName, handleDeleteFlight, loading} from "src/composables/objects/flight";
|
import {fetchFlight, flight, flightName, handleDeleteFlight, loading} from "src/composables/objects/flight";
|
||||||
import {useAppStore} from "stores/App";
|
import {useAppStore} from "stores/App";
|
||||||
import PhotosList from "components/photos/PhotosList.vue";
|
|
||||||
import FlightBasicInfo from "components/flights/FlightBasicInfo.vue";
|
import FlightBasicInfo from "components/flights/FlightBasicInfo.vue";
|
||||||
import {useQuasar} from "quasar";
|
import {useQuasar} from "quasar";
|
||||||
import {successObject} from "src/composables/dialog";
|
import {successObject} from "src/composables/dialog";
|
||||||
import MapWithMarkers from "components/MapWithMarkers.vue";
|
import MapWithMarkers from "components/map/MapWithMarkers.vue";
|
||||||
import FAB from "components/FAB.vue";
|
import FAB from "components/FAB.vue";
|
||||||
|
import PhotoDetail from "components/photos/PhotoDetail.vue";
|
||||||
|
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const $router = useRouter();
|
const $router = useRouter();
|
||||||
|
const displayedPhoto = ref(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchFlight(useRoute().params.id)
|
fetchFlight(useRoute().params.id).then(() => {
|
||||||
|
if (flight.value.photos.length === 0) return;
|
||||||
|
displayedPhoto.value = flight.value.coverPhoto.url || flight.value.photos[0].url;
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(flight, (newVal) => {
|
watch(flight, (newVal) => {
|
||||||
@@ -36,35 +41,77 @@ const handleDeleteAndRedirect = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const flightPath = computed(() => {
|
const trackWithGPS = computed(() => {
|
||||||
|
return flight.value.track.filter(poi => poi.pointOfInterest.gpsLatitude && poi.pointOfInterest.gpsLongitude)
|
||||||
|
});
|
||||||
|
|
||||||
|
const pointsOfInterests = computed(() => {
|
||||||
const path = [{
|
const path = [{
|
||||||
|
label: flight.value.takeoffAirport.name,
|
||||||
|
position:
|
||||||
|
{
|
||||||
lat: flight.value.takeoffAirport.gpsLatitude,
|
lat: flight.value.takeoffAirport.gpsLatitude,
|
||||||
lng: flight.value.takeoffAirport.gpsLongitude
|
lng: flight.value.takeoffAirport.gpsLongitude
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
for (const poi of flight.value.track) {
|
for (const poi of trackWithGPS.value) {
|
||||||
path.push({lat: poi.pointOfInterest.gpsLatitude, lng: poi.pointOfInterest.gpsLongitude});
|
path.push({
|
||||||
|
label: poi.pointOfInterest.name,
|
||||||
|
position: {lat: poi.pointOfInterest.gpsLatitude, lng: poi.pointOfInterest.gpsLongitude}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
path.push({
|
path.push({
|
||||||
|
label: flight.value.takeoffAirport.name,
|
||||||
|
position: {
|
||||||
lat: flight.value.landingAirport.gpsLatitude,
|
lat: flight.value.landingAirport.gpsLatitude,
|
||||||
lng: flight.value.landingAirport.gpsLongitude
|
lng: flight.value.landingAirport.gpsLongitude
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const openPhotoDetail = ref(false);
|
||||||
|
const photoData = ref(null);
|
||||||
|
const openDetail = ({photo, idx, allPhotos}) => {
|
||||||
|
openPhotoDetail.value = true;
|
||||||
|
photoData.value = {photo, idx, allPhotos}
|
||||||
|
}
|
||||||
|
const mapPhotos = computed(() => {
|
||||||
|
return flight.value.photos.filter(photo => photo.gpsLatitude && photo.gpsLongitude)
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayMap = computed(() => {
|
||||||
|
return (
|
||||||
|
!!flight.value.gpsTrackUrl ||
|
||||||
|
mapPhotos.value.length > 0 ||
|
||||||
|
trackWithGPS.value.length > 0 ||
|
||||||
|
flight.value.landingAirport.id !== flight.value.takeoffAirport.id
|
||||||
|
)
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FlightDetailSkeleton v-if="loading || !flight"/>
|
<FlightDetailSkeleton v-if="loading || !flight"/>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<q-card class="q-mx-md-lg q-my-md">
|
<q-card class="q-mx-md q-my-md">
|
||||||
<q-parallax
|
<q-carousel
|
||||||
:src="flight.coverPhoto.url"
|
v-if="flight.photos.length > 0"
|
||||||
v-if="flight.coverPhoto"
|
animated
|
||||||
:height="450"
|
v-model="displayedPhoto"
|
||||||
/>
|
arrows
|
||||||
|
swipeable
|
||||||
|
height="auto"
|
||||||
|
infinite
|
||||||
|
>
|
||||||
|
<q-carousel-slide style="padding: 0" v-for="(photo, idx) in flight.photos" :key="photo.url" :name="photo.url">
|
||||||
|
<q-img :src="photo.url" @click="openDetail({photo, idx, allPhotos: flight.photos})"/>
|
||||||
|
</q-carousel-slide>
|
||||||
|
</q-carousel>
|
||||||
|
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<FlightBasicInfo :flight="flight"/>
|
<FlightBasicInfo :flight="flight"/>
|
||||||
@@ -72,12 +119,12 @@ const flightPath = computed(() => {
|
|||||||
<article class="q-my-lg" v-html="flight.description"/>
|
<article class="q-my-lg" v-html="flight.description"/>
|
||||||
|
|
||||||
<MapWithMarkers
|
<MapWithMarkers
|
||||||
|
v-if="displayMap"
|
||||||
style="width: 100%; height: 500px;"
|
style="width: 100%; height: 500px;"
|
||||||
:show-as-track="true"
|
:gpx-track-url="flight.gpxTrackUrl"
|
||||||
:markers="flightPath"
|
:markers="pointsOfInterests"
|
||||||
|
:photos="mapPhotos"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PhotosList :photos="flight.photos" :show-actions="false"/>
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
@@ -85,6 +132,8 @@ const flightPath = computed(() => {
|
|||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
|
<PhotoDetail v-if="photoData" v-model="openPhotoDetail" :photo="photoData.photo" :photos="photoData.allPhotos" :opened-index="photoData.idx"/>
|
||||||
|
|
||||||
<FAB :router-link="{name: 'flightEdit', params:{id: flight.id}}" mode="edit"/>
|
<FAB :router-link="{name: 'flightEdit', params:{id: flight.id}}" mode="edit"/>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import FAB from "components/FAB.vue";
|
|||||||
import {fetchPoi, loading, poi} from "src/composables/objects/poi";
|
import {fetchPoi, loading, poi} from "src/composables/objects/poi";
|
||||||
import PhotosList from "components/photos/PhotosList.vue";
|
import PhotosList from "components/photos/PhotosList.vue";
|
||||||
import {useAppStore} from "stores/App";
|
import {useAppStore} from "stores/App";
|
||||||
import MapWithMarker from "components/MapWithMarkers.vue";
|
import EmbeddedMap from "components/map/EmbeddedMap.vue";
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchPoi(useRoute().params.id).then(() => {
|
fetchPoi(useRoute().params.id).then(() => {
|
||||||
@@ -25,11 +25,11 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|
||||||
<MapWithMarker
|
<EmbeddedMap
|
||||||
v-if="poi.gpsLongitude && poi.gpsLatitude"
|
v-if="poi.gpsLatitude && poi.gpsLongitude"
|
||||||
style="width: 100%; height: 500px;"
|
:gps-latitude="poi.gpsLatitude"
|
||||||
|
:gps-longitude="poi.gpsLongitude"
|
||||||
:zoom="13"
|
:zoom="13"
|
||||||
:markers="[{lat: poi.gpsLatitude, lng: poi.gpsLongitude}]"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PhotosList
|
<PhotosList
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import {onMounted, ref} from "vue";
|
|||||||
import AircraftEditSkeleton from "components/aircrafts/AircraftEditSkeleton.vue";
|
import AircraftEditSkeleton from "components/aircrafts/AircraftEditSkeleton.vue";
|
||||||
import {successObject} from "src/composables/dialog";
|
import {successObject} from "src/composables/dialog";
|
||||||
import {aircraft, fetchAircraft, loading} from "src/composables/objects/aircraft";
|
import {aircraft, fetchAircraft, loading} from "src/composables/objects/aircraft";
|
||||||
|
import {useAppStore} from "stores/App";
|
||||||
|
import {copilot} from "src/composables/objects/copilot";
|
||||||
|
|
||||||
|
|
||||||
let isRequestPending = ref(false);
|
let isRequestPending = ref(false);
|
||||||
@@ -16,29 +18,32 @@ const aircraftId = parseInt(useRoute().params.id);
|
|||||||
const handleSave = (formData) => {
|
const handleSave = (formData) => {
|
||||||
isRequestPending.value = true;
|
isRequestPending.value = true;
|
||||||
useAircraftsStore().update(aircraftId, formData).then(() => {
|
useAircraftsStore().update(aircraftId, formData).then(() => {
|
||||||
$q.notify(successObject("Letadlo uspesne upraveno"));
|
$q.notify(successObject("Letadlo úspěšně upraveno"));
|
||||||
|
|
||||||
useRouter().push({name: "aircrafts"});
|
useRouter().push({name: "aircrafts"});
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
isRequestPending.value = true;
|
isRequestPending.value = false;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchAircraft(aircraftId)
|
fetchAircraft(aircraftId).then(() => {
|
||||||
|
useAppStore().setPageTitle(`${aircraft.value.callSign} - editace`);
|
||||||
|
useAppStore().setBreadcrumbs([
|
||||||
|
{to: {name: "aircraft"}, label: "Letadla"},
|
||||||
|
{to: {name: "aircraftDetail", params: {id: aircraftId}}, label: aircraft.value.callSign},
|
||||||
|
{label: "Editace"}
|
||||||
|
])
|
||||||
|
})
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AircraftEditSkeleton v-if="loading"/>
|
<AircraftEditSkeleton v-if="loading"/>
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<h1>Editace letadla {{ aircraft.callSign }}</h1>
|
|
||||||
|
|
||||||
<AircraftForm
|
<AircraftForm
|
||||||
|
v-else
|
||||||
@save="handleSave"
|
@save="handleSave"
|
||||||
:default-values="aircraft"
|
:default-values="aircraft"
|
||||||
:is-request-pending="isRequestPending"
|
:is-request-pending="isRequestPending"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ import {onMounted} from "vue";
|
|||||||
import {loading, copilot, fetchCopilot} from "src/composables/objects/copilot";
|
import {loading, copilot, fetchCopilot} from "src/composables/objects/copilot";
|
||||||
import {useAppStore} from "stores/App";
|
import {useAppStore} from "stores/App";
|
||||||
import CopilotEditSkeleton from "components/copilots/CopilotEditSkeleton.vue";
|
import CopilotEditSkeleton from "components/copilots/CopilotEditSkeleton.vue";
|
||||||
import CopilotEditForm from "components/copilots/CopilotEditForm.vue";
|
import CopilotEditForm from "components/copilots/CopilotForm.vue";
|
||||||
import {useRoute} from "vue-router";
|
import {useRoute} from "vue-router";
|
||||||
|
import {useCopilotsStore} from "stores/Copilots";
|
||||||
|
import {successObject} from "src/composables/dialog";
|
||||||
|
import {useQuasar} from "quasar";
|
||||||
|
|
||||||
const copilotId = parseInt(useRoute().params.id);
|
const copilotId = parseInt(useRoute().params.id);
|
||||||
|
|
||||||
@@ -12,19 +15,27 @@ onMounted(() => {
|
|||||||
fetchCopilot(copilotId).then(() => {
|
fetchCopilot(copilotId).then(() => {
|
||||||
useAppStore().setPageTitle(`${copilot.value.name} - editace`);
|
useAppStore().setPageTitle(`${copilot.value.name} - editace`);
|
||||||
useAppStore().setBreadcrumbs([
|
useAppStore().setBreadcrumbs([
|
||||||
{to: {name: "pilots"}, label: "Kopiloti"},
|
{to: {name: "copilots"}, label: "Kopiloti"},
|
||||||
{to: {name: "pilotDetail", params: {id: copilotId}}, label: copilot.value.name},
|
{to: {name: "copilotDetail", params: {id: copilotId}}, label: copilot.value.name},
|
||||||
{label: "Editace"}
|
{label: "Editace"}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const $q = useQuasar();
|
||||||
|
|
||||||
|
const handleSave = (formData) => {
|
||||||
|
useCopilotsStore().update(copilotId, formData).then(() => {
|
||||||
|
$q.notify(successObject("Kopilot úspěšně upraven"));
|
||||||
|
|
||||||
|
// useRouter().push({name: "copilots"});
|
||||||
|
// TODO: ^^ hazi error
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CopilotEditSkeleton v-if="loading"/>
|
<CopilotEditSkeleton v-if="loading"/>
|
||||||
|
|
||||||
<CopilotEditForm v-else />
|
<CopilotEditForm v-else :copilot="copilot" @save="handleSave"/>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import FlightFormPhotos from "components/flights/form/FlightFormPhotos.vue";
|
|||||||
import {usePhotosStore} from "stores/Photo";
|
import {usePhotosStore} from "stores/Photo";
|
||||||
import PhotosList from "components/photos/PhotosList.vue";
|
import PhotosList from "components/photos/PhotosList.vue";
|
||||||
import {confirmObject, successObject} from "src/composables/dialog";
|
import {confirmObject, successObject} from "src/composables/dialog";
|
||||||
import PhotoForm from "components/flights/PhotoForm.vue";
|
import PhotoForm from "components/photos/PhotoForm.vue";
|
||||||
import FlightFormTrack from "components/flights/form/FlightFormTrack.vue";
|
import FlightFormTrack from "components/flights/form/FlightFormTrack.vue";
|
||||||
import {fetchFlight, flight, loading} from "src/composables/objects/flight";
|
import {fetchFlight, flight, loading} from "src/composables/objects/flight";
|
||||||
import FlightEditSkeleton from "components/flights/FlightEditSkeleton.vue";
|
import FlightEditSkeleton from "components/flights/FlightEditSkeleton.vue";
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import AircraftForm from "components/aircrafts/AircraftForm.vue";
|
|||||||
import {useAircraftsStore} from "stores/Aircrafts";
|
import {useAircraftsStore} from "stores/Aircrafts";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {useQuasar} from "quasar";
|
import {useQuasar} from "quasar";
|
||||||
import {ref} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
|
import {useAppStore} from "stores/App";
|
||||||
|
|
||||||
let isRequestPending = ref(false);
|
let isRequestPending = ref(false);
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
@@ -18,6 +19,15 @@ const handleSave = (formData) => {
|
|||||||
isRequestPending.value = true;
|
isRequestPending.value = true;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
useAppStore().setPageTitle("Nové letadlo");
|
||||||
|
useAppStore().setBreadcrumbs([
|
||||||
|
{to: {name: "aircrafts"}, label: "Letadla"},
|
||||||
|
{label: "Nové"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,25 +1,34 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import AircraftForm from "components/aircrafts/AircraftForm.vue";
|
|
||||||
import {useAircraftsStore} from "stores/Aircrafts";
|
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {useQuasar} from "quasar";
|
import {useQuasar} from "quasar";
|
||||||
import {ref} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
|
import {successObject} from "src/composables/dialog";
|
||||||
|
import {useCopilotsStore} from "stores/Copilots";
|
||||||
|
import CopilotForm from "components/copilots/CopilotForm.vue";
|
||||||
|
import {useAppStore} from "stores/App";
|
||||||
|
|
||||||
let isRequestPending = ref(false);
|
let isRequestPending = ref(false);
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const handleSave = (formData) => {
|
const handleSave = (formData) => {
|
||||||
isRequestPending.value = true;
|
isRequestPending.value = true;
|
||||||
useAircraftsStore().create(formData).then(() => {
|
useCopilotsStore().create(formData).then(() => {
|
||||||
$q.notify({message: "Letadlo úspěšně vytvořeno", position: "top", color: "positive"});
|
$q.notify(successObject("Kopilot úspěšně vytvořen"));
|
||||||
|
router.push({name: "copilots"});
|
||||||
router.push({name: "aircrafts"});
|
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
isRequestPending.value = true;
|
isRequestPending.value = false;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
useAppStore().setPageTitle("Nový spolucestující");
|
||||||
|
useAppStore().setBreadcrumbs([
|
||||||
|
{to: {name: "copilots"}, label: "Spolucestující"},
|
||||||
|
{label: "Nový"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AircraftForm @save="handleSave" :is-request-pending="isRequestPending"/>
|
<CopilotForm @save="handleSave" :is-request-pending="isRequestPending"/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,49 +5,39 @@ import {useFlightsStore} from "stores/Flights";
|
|||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
||||||
import {storeToRefs} from "pinia";
|
import {storeToRefs} from "pinia";
|
||||||
import {useMeta} from "quasar";
|
|
||||||
import {useAirportsStore} from "stores/Airports";
|
import {useAirportsStore} from "stores/Airports";
|
||||||
import FlightFormSteps from "components/flights/form/FlightFormSteps.vue";
|
import FlightFormSteps from "components/flights/form/FlightFormSteps.vue";
|
||||||
import FlightFormBasic from "components/flights/form/FlightFormBasic.vue";
|
import FlightFormBasic from "components/flights/form/FlightFormBasic.vue";
|
||||||
|
import {useAppStore} from "stores/App";
|
||||||
|
|
||||||
const aircraftsStore = useAircraftsStore();
|
|
||||||
const poiStore = usePointsOfInterestStore();
|
|
||||||
const flightsStore = useFlightsStore();
|
|
||||||
const airportStore = useAirportsStore();
|
|
||||||
|
|
||||||
useMeta({title: "New flight"});
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
aircraftsStore.fetch();
|
useAircraftsStore().fetch();
|
||||||
poiStore.fetch();
|
usePointsOfInterestStore().fetch();
|
||||||
flightsStore.fetch();
|
useFlightsStore().fetch();
|
||||||
airportStore.fetch();
|
useAirportsStore().fetch();
|
||||||
|
|
||||||
|
useAppStore().setPageTitle("Nový let");
|
||||||
|
useAppStore().setBreadcrumbs([
|
||||||
|
{to: {name: "myFlights"}, label: "Moje lety"},
|
||||||
|
{label: "Nový"}
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const aircrafts = storeToRefs(aircraftsStore).items;
|
const aircrafts = storeToRefs(useAircraftsStore()).items;
|
||||||
const locations = storeToRefs(poiStore).items;
|
const locations = storeToRefs(usePointsOfInterestStore()).items;
|
||||||
const airports = storeToRefs(airportStore).items;
|
const airports = storeToRefs(useAirportsStore()).items;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const handleSave = (formData) => {
|
const handleSave = (formData) => {
|
||||||
flightsStore.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"}});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const formStep = ref("basic")
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<q-breadcrumbs>
|
|
||||||
<q-breadcrumbs-el :to="{name: 'myFlights'}">Moje lety</q-breadcrumbs-el>
|
|
||||||
<q-breadcrumbs-el>Nový</q-breadcrumbs-el>
|
|
||||||
</q-breadcrumbs>
|
|
||||||
|
|
||||||
<!-- {{ formData }}-->
|
|
||||||
|
|
||||||
<FlightFormSteps :disabled="['copilot', 'track', 'photos', 'notes']"/>
|
<FlightFormSteps :disabled="['copilot', 'track', 'photos', 'notes']"/>
|
||||||
|
|
||||||
<FlightFormBasic @save="handleSave" :aircrafts="aircrafts" :airports="airports" :locations="locations" />
|
<FlightFormBasic @save="handleSave" :aircrafts="aircrafts" :airports="airports" :locations="locations" />
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ const routes = [
|
|||||||
{path: 'poi/add', name: "addPOI", component: () => import('pages/profile/forms/NewPointOfInterestPage.vue')},
|
{path: 'poi/add', name: "addPOI", component: () => import('pages/profile/forms/NewPointOfInterestPage.vue')},
|
||||||
{path: 'poi/:id', name: "poiDetail", component: () => import('pages/profile/details/PointOfInterestDetailPage.vue')},
|
{path: 'poi/:id', name: "poiDetail", component: () => import('pages/profile/details/PointOfInterestDetailPage.vue')},
|
||||||
{path: 'poi/:id/edit', name: "poiEdit", component: () => import('pages/profile/forms/EditPointOfInterestPage.vue')},
|
{path: 'poi/:id/edit', name: "poiEdit", component: () => import('pages/profile/forms/EditPointOfInterestPage.vue')},
|
||||||
{path: 'pilots', name: "pilots", component: () => import('pages/profile/lists/CopilotsPage.vue')},
|
{path: 'copilots', name: "copilots", component: () => import('pages/profile/lists/CopilotsPage.vue')},
|
||||||
{path: 'pilots/add', name: "addPilots", component: () => import('pages/profile/forms/NewCopilotPage.vue')},
|
{path: 'copilots/add', name: "addCopilot", component: () => import('pages/profile/forms/NewCopilotPage.vue')},
|
||||||
{path: 'pilots/:id', name: "pilotDetail", component: () => import('pages/profile/details/CopilotDetailPage.vue')},
|
{path: 'copilots/:id', name: "copilotDetail", component: () => import('pages/profile/details/CopilotDetailPage.vue')},
|
||||||
{path: 'pilots/:id/edit', name: "pilotEdit", component: () => import('pages/profile/forms/EditCopilotPage.vue')},
|
{path: 'copilots/:id/edit', name: "copilotEdit", component: () => import('pages/profile/forms/EditCopilotPage.vue')},
|
||||||
{path: 'aircrafts', name: "aircrafts", component: () => import('pages/profile/lists/AircraftsPage.vue')},
|
{path: 'aircrafts', name: "aircrafts", component: () => import('pages/profile/lists/AircraftsPage.vue')},
|
||||||
{path: 'aircrafts/new', name: "addAircraft", component: () => import('pages/profile/forms/NewAircraftsPage.vue')},
|
{path: 'aircrafts/new', name: "addAircraft", component: () => import('pages/profile/forms/NewAircraftsPage.vue')},
|
||||||
{path: 'aircrafts/:id', name: "aircraftDetail", component: () => import('pages/profile/details/AircraftDetailPage.vue')},
|
{path: 'aircrafts/:id', name: "aircraftDetail", component: () => import('pages/profile/details/AircraftDetailPage.vue')},
|
||||||
|
|||||||
@@ -42,9 +42,6 @@ export const useAircraftsStore = defineStore('Aircrafts', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetch() {
|
fetch() {
|
||||||
/***
|
|
||||||
* {id: id, name: "OK-ABC" + id, private: true, limits: {wind: {frontKt: 22, sideKt: 16}, speed: {vmax: 220}}};
|
|
||||||
*/
|
|
||||||
return apolloWrapper
|
return apolloWrapper
|
||||||
.query(AIRCRAFTS_LIST)
|
.query(AIRCRAFTS_LIST)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
|||||||
+17
-8
@@ -1,26 +1,35 @@
|
|||||||
import {defineStore} from 'pinia'
|
import {defineStore} from 'pinia'
|
||||||
import {apolloWrapper} from "src/graphql/apollo";
|
import {apolloWrapper} from "src/graphql/apollo";
|
||||||
import {COPILOTS_DETAIL, COPILOTS_LIST} from "src/graphql/queries/copilots";
|
import {COPILOT_DETAIL, COPILOTS_LIST} from "src/graphql/queries/copilots";
|
||||||
|
import {CREATE_COPILOT, EDIT_COPILOT} from "src/graphql/mutations/copilot";
|
||||||
|
|
||||||
export const useCopilotsStore = defineStore('SecondPilots', {
|
export const useCopilotsStore = defineStore('SecondPilots', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
items: []
|
items: []
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {},
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
create(formData) {
|
|
||||||
console.log("not implemented");
|
|
||||||
},
|
|
||||||
fetchDetail(copilotId) {
|
fetchDetail(copilotId) {
|
||||||
return apolloWrapper.query(COPILOTS_DETAIL, {id: copilotId}).then(({data}) => data.copilot);
|
return apolloWrapper.query(COPILOT_DETAIL, {id: copilotId}).then(({data}) => data.copilot);
|
||||||
},
|
},
|
||||||
fetch() {
|
fetch() {
|
||||||
return apolloWrapper.query(COPILOTS_LIST).then(result => {
|
return apolloWrapper.query(COPILOTS_LIST).then(result => {
|
||||||
this.items = result.data.copilots;
|
this.items = result.data.copilots;
|
||||||
return result.data.copilots;
|
return result.data.copilots;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
create(formData) {
|
||||||
|
delete formData['id'];
|
||||||
|
return apolloWrapper.mutate(CREATE_COPILOT, {input: formData}).then(({data}) => {
|
||||||
|
this.items.push(data.createCopilot);
|
||||||
|
|
||||||
|
return data.createCopilot;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
update(id, formData) {
|
||||||
|
return apolloWrapper.mutate(EDIT_COPILOT, {id: id, input: formData}).then(({data}) => {
|
||||||
|
return data.editCopilot;
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user