Drobne upravy ve vizualu, oprava comboboxu, funkcni prakticky cela editace letu

This commit is contained in:
Michal Kváček
2023-07-18 10:21:40 +02:00
parent 2d76d136cf
commit 4648c30f90
21 changed files with 233 additions and 132 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ module.exports = configure(function (/* ctx */) {
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
// 'mdi-v5',
'mdi-v5',
// 'fontawesome-v6',
// 'eva-icons',
// 'themify',
-19
View File
@@ -1,19 +0,0 @@
<script setup>
const props = defineProps({
label: {
type: String,
required: false,
default: "Přidat"
},
routerLink: {
type: Object,
required: true
}
})
</script>
<template>
<q-page-sticky location="bottom" :offset="[30, 30]">
<q-fab icon="add" color="primary" :label="props.label" @click="$router.push(props.routerLink)"/>
</q-page-sticky>
</template>
+32
View File
@@ -0,0 +1,32 @@
<script setup>
import {computed} from "vue";
const props = defineProps({
mode: {
type: String,
required: true
},
routerLink: {
type: Object,
required: true
}
})
const config = computed(() => {
if (props.mode === "add") {
return {icon: "add", label: "Přidat"};
} else if (props.mode === "edit") {
return {icon: "edit", label: "Upravit"};
}
return {icon: "unknown", label: `Neznámý typ ${props.mode}`}
})
</script>
<template>
<q-page-sticky location="bottom" :offset="[30, 30]">
<q-fab :icon="config.icon" color="primary" :label="config.label" @click="$router.push(props.routerLink)"/>
</q-page-sticky>
</template>
+21 -3
View File
@@ -1,7 +1,7 @@
<script setup>
import {isRequestPending} from "src/composables/form/props";
import {useSlots} from "vue";
import {computed, useSlots} from "vue";
import {useQuasar} from "quasar";
const props = defineProps({
modelValue: {
@@ -32,6 +32,10 @@ const saveForm = () => {
console.error("Not implemented")
}
}
const $q = useQuasar();
const fullwidthBtn = computed(() => $q.screen.width <= $q.screen.sizes.md);
</script>
<template>
@@ -40,11 +44,25 @@ const saveForm = () => {
<slot name="saveButton" />
<q-btn type="submit" color="primary" v-if="saveButton" :icon="props.saveButton.icon" :loading="props.isRequestPending">
<q-btn
color="primary"
type="submit"
:class="{'q-my-lg': true, 'btn--fullwidth': fullwidthBtn}"
v-if="saveButton"
:icon="props.saveButton.icon"
:loading="props.isRequestPending"
>
{{ props.saveButton.label }}
<template v-slot:loading>
Loading...
</template>
</q-btn>
</q-form>
</template>
<style>
.btn--fullwidth {
width: 100%;
}
</style>
+15 -12
View File
@@ -27,6 +27,15 @@ const deleteFlight = (flightId) => {
const totalFlightTime = computed(() => {
return props.items.reduce((acc, flight) => acc + flight.flightDurationMinutes, 0);
})
const flightName = (item) => {
if (item.name) {
return item.name;
} else {
return `${item.takeoffAirport.name} - ${item.landingAirport.name}`
}
}
</script>
<template>
@@ -34,23 +43,17 @@ const totalFlightTime = computed(() => {
<div class="col-12 col-md-4 col-lg-3" v-for="flight of items" :key="flight">
<q-card class="q-my-sm q-mx-sm">
<q-img :src="flight.titlePhotoUrl" v-if="flight.titlePhotoUrl">
<q-img :src="flight.coverPhoto.thumbnailUrl" v-if="flight.coverPhoto">
<div class="absolute-bottom text-center text-h6">
<router-link :to="{name: 'flightDetail', params:{id: flight.id}}" class="text-white">
{{ flight.name }}
<!-- {{ flight.takeoffAirport.name }} - {{ flight.landingAirport.name }}-->
{{ flightName(flight) }}
</router-link>
</div>
</q-img>
<q-card-section>
<div class="text-h6" v-if="!flight.titlePhotoUrl">
<div class="text-h6" v-if="!flight.coverPhoto">
<router-link :to="{name: 'flightDetail', params:{id: flight.id}}">
<template v-if="flight.name">
{{ flight.name }}
</template>
<template v-else>
{{ flight.takeoffAirport.icaoCode }} - {{ flight.landingAirport.icaoCode }}
</template>
{{ flightName(flight) }}
</router-link>
</div>
<div class="text-subtitle2">{{ minutesToHumanReadable(flight.durationMinCalculated) }}</div>
@@ -67,8 +70,8 @@ const totalFlightTime = computed(() => {
<q-separator/>
<q-card-actions align="right">
<!-- <q-btn flat icon="visibility"></q-btn>-->
<!-- <q-btn flat icon="edit" :to="{name: 'flightEdit', params:{id: flight.id}}"></q-btn>-->
<q-btn flat icon="visibility"></q-btn>
<q-btn flat icon="edit" :to="{name: 'flightEdit', params:{id: flight.id}}"></q-btn>
<q-btn flat icon="delete" color="red" @click="deleteFlight(flight.id)"></q-btn>
</q-card-actions>
</q-card>
@@ -30,13 +30,9 @@ const formData = reactive({
landingAirportId: null,
takeoffDatetime: null,
landingDatetime: null,
durationTotal: null,
landings: 1,
solo: true,
durationPic: 0,
withInstructor: false,
copilotId: null,
name: "",
description: ""
})
@@ -101,7 +97,7 @@ onMounted(() => {
<q-checkbox v-model="formData.solo" label="Solo?"/>
</div>
<div class="col">
<q-input v-model="formData.landings" label="Počet přistání" type="number" min="1"/>
<q-input v-model.number="formData.landings" label="Počet přistání" type="number" min="1"/>
</div>
</div>
</template>
@@ -34,12 +34,19 @@ onMounted(() => {
<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}]"/>
{{ formData.copilot }}
<div v-if="!formData.solo">
<ComboboxInput :options="copilots" v-model="formData.copilot" :key="formData.copilot" label="Druhy pilot" />
<ComboboxInput
class="q-my-lg"
outlined
standout
:options="copilots"
v-model="formData.copilot"
:key="formData.copilot"
:clearable="true"
label="Druhy pilot"
/>
<q-checkbox v-model="formData.withInstructor" v-if="formData.copilot" label="Instruktor"/>
<!-- <q-checkbox v-model="formData.withInstructor" v-if="formData.copilot" label="Instruktor"/>-->
<!-- <q-input v-model="formData.picTime" label="PIC time"/>-->
</div>
+52 -17
View File
@@ -1,6 +1,6 @@
<script setup>
import FormWrapper from "components/FormWrapper.vue";
import {onMounted, reactive, ref} from "vue";
import {onMounted, reactive, ref, watch} from "vue";
import ComboboxInput from "components/inputs/ComboboxInput.vue";
const props = defineProps({
@@ -29,6 +29,11 @@ const addTrackItem = (item) => {
formData.track.push(item);
poi.value = null;
}
watch(poi, (newVal) => {
if (newVal?.name) {
addTrackItem(newVal);
}
});
onMounted(() => {
for (const i in props.flight.track) {
@@ -43,26 +48,56 @@ onMounted(() => {
<template>
<FormWrapper v-model="formData">
<ComboboxInput
outlined
standout
class="q-my-lg"
v-model="poi"
:key="formData.track.length"
:options="locations"
@select="addTrackItem"
clearable
label="Otočný bod"
/>
{{ flight.track }}
<q-list>
<q-item>{{ flight.takeoffAirport.name }}</q-item>
<q-item v-for="(item, index) in formData.track" :key="item">
{{ index + 1 }}. {{ item.name }}
<q-btn @click="removeTrackItem(index)" icon="delete" text-color="red"/>
<q-list bordered>
<q-item-label header>Trasa</q-item-label>
<q-item>
<q-item-section avatar>
<q-avatar color="primary" text-color="white" icon="flight_takeoff"/>
</q-item-section>
<q-item-section>
{{ flight.takeoffAirport.name }}
</q-item-section>
</q-item>
<q-item>{{ flight.landingAirport.name }}</q-item>
<q-item v-for="(item, index) in formData.track" :key="item">
<q-item-section avatar>
<q-avatar size="32px" color="secondary" text-color="white">{{ index + 1 }}.</q-avatar>
</q-item-section>
<q-item-section>
<span>
{{ item.name }}
<q-badge color="green" v-if="!item.id">
<q-icon name="add"/>
</q-badge>
</span>
</q-item-section>
<q-item-section top side>
<q-btn @click="removeTrackItem(index)" flat dense round icon="delete" text-color="red"/>
</q-item-section>
</q-item>
<q-item>
<q-item-section avatar>
<q-avatar color="primary" text-color="white" icon="flight_land"/>
</q-item-section>
<q-item-section>
{{ flight.landingAirport.name }}
</q-item-section>
</q-item>
</q-list>
<ComboboxInput v-model="poi" :options="locations" clearable label="Location">
<template #after>
<q-btn @click="addTrackItem(poi)" v-if="poi" rounded>+</q-btn>
</template>
</ComboboxInput>
</FormWrapper>
</template>
+37 -24
View File
@@ -1,7 +1,6 @@
<script setup>
import {onMounted, ref, watch} from "vue";
const props = defineProps({
modelValue: {
type: [Object, Array]
@@ -17,23 +16,42 @@ const props = defineProps({
},
})
let model = ref({});
watch(() => props.modelValue, (newVal) => {
model.value = newVal;
console.log("zmena!", newVal);
});
const model = ref({});
onMounted(() => {
model.value = props.modelValue;
})
const emit = defineEmits(['update:modelValue']);
const emit = defineEmits(['update:modelValue', 'clear']);
watch(model, (val) => {
if (!val) return;
const value = val.name ? val : {name: val};
let value = val;
if (typeof val === 'string') value = {name: val};
emit("update:modelValue", value);
})
const clearValue = () => {
model.value = {};
emit("clear");
}
const inputValue = ref("");
const inputValueChanged = (value) => {
if (model.value?.name && value !== '') {
// reset model
delete model.value.id;
model.value.name = '';
}
inputValue.value = value;
}
const handleOnBlur = () => {
if (inputValue.value !== '') {
model.value = inputValue.value;
inputValue.value = "";
}
}
let availableOptions = ref([]);
const filterFn = (val, update, abort) => {
update(() => {
@@ -57,31 +75,26 @@ const filterFn = (val, update, abort) => {
v-model="model"
use-input
input-debounce="100"
:display-value="`${model.name}`"
:display-value="`${model?.name || ''}`"
:rules="rules"
@filter="filterFn"
@input-value="inputValueChanged"
@blur="handleOnBlur"
@clear="clearValue"
:options="availableOptions"
option-label="name"
option-value="id"
new-value-mode="add"
clear-icon="clear"
:v-bind="$attrs"
>
<template #before>
<slot name="before"/>
</template>
<template #after>
<slot name="after"/>
</template>
<template #append>
<slot name="append"/>
</template>
<!-- <template>-->
<!-- </template>-->
<template v-slot:no-option>
<q-item>
<q-item-section avatar>
<q-avatar text-color="green" size="32px" icon="add" />
</q-item-section>
<q-item-section class="text-grey">
Add new option
<span>Vytvořit novou hodnotu <q-chip>{{ inputValue }}</q-chip></span>
</q-item-section>
</q-item>
</template>
-4
View File
@@ -132,8 +132,4 @@ const myLocale = {
.proxy-wrapper {
background: white;
}
.q-date, .q-time {
width: 100%;
}
</style>
+8
View File
@@ -10,3 +10,11 @@ fragment aircraftFullInfo on Aircraft {
photoUrl
}
`;
export const FRAGMENT_AIRCRAFT_BASIC_INFO = gql`
fragment aircraftBasicInfo on Aircraft {
id
callSign
manufacturer
}
`;
+9 -3
View File
@@ -1,5 +1,5 @@
import gql from "graphql-tag";
import {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_PHOTO_FULL_INFO} from "src/graphql/fragments/photo";
@@ -10,10 +10,16 @@ fragment flightFullInfo on Flight {
description
takeoffDatetime
landingDatetime
landings
durationMinCalculated
solo
withInstructor
coverPhoto {
thumbnailUrl
url
}
takeoffAirport {
...airportInfo
}
@@ -64,12 +70,12 @@ fragment flightListInfo on Flight {
}
aircraft {
...aircraftFullInfo
...aircraftBasicInfo
}
}
${FRAGMENT_AIRCRAFT_FULL_INFO}
${FRAGMENT_AIRPORT_INFO}
${FRAGMENT_AIRCRAFT_BASIC_INFO}
`
export const FRAGMENT_FLIGHT_EDIT_IDS = gql`
+10
View File
@@ -0,0 +1,10 @@
import gql from "graphql-tag";
export const COPILOTS_LIST = gql`
query copilotsList {
copilots {
id
name
}
}
`;
@@ -4,7 +4,7 @@ import {useRoute} from "vue-router";
import {onMounted, reactive, ref} from "vue";
import {formatDatetime, minutesToHumanReadable} from "src/composables/datetimeUtils";
import FlightDetailSkeleton from "components/flights/FlightDetailSkeleton.vue";
import AddFAB from "components/AddFAB.vue";
import FAB from "components/FAB.vue";
const route = useRoute();
const loading = ref(false)
@@ -36,6 +36,6 @@ onMounted(() => {
<!-- Temp: 12°C-->
<AddFAB :router-link="{name: 'flightEdit', params:{id: flight.id}}" label="Upravit"/>
<FAB mode="edit" :router-link="{name: 'flightEdit', params:{id: flight.id}}"/>
</template>
</template>
@@ -1,12 +1,12 @@
<script setup>
import {useSecondPilotsStore} from "stores/SecondPilots";
import {useCopilotsStore} from "stores/Copilots";
import {useRoute} from "vue-router";
import {useFlightsStore} from "stores/Flights";
import {computed, onMounted} from "vue";
import {storeToRefs} from "pinia";
import {minutesToHumanReadable} from "src/composables/datetimeUtils";
const secondPilotStore = useSecondPilotsStore();
const secondPilotStore = useCopilotsStore();
const flightsStore = useFlightsStore();
const route = useRoute();
+3 -4
View File
@@ -11,7 +11,7 @@ import {useMeta, useQuasar} from "quasar";
import {storeToRefs} from "pinia";
import {flightName} from "src/composables/flight";
import FlightFormCopilot from "components/flights/form/FlightFormCopilot.vue";
import {useSecondPilotsStore} from "stores/SecondPilots";
import {useCopilotsStore} from "stores/Copilots";
import FlightFormNotes from "components/flights/form/FlightFormNotes.vue";
import FlightFormPhotos from "components/flights/form/FlightFormPhotos.vue";
import {usePhotosStore} from "stores/Photo";
@@ -29,7 +29,7 @@ const flightId = parseInt(route.params.id);
const aircrafts = storeToRefs(useAircraftsStore()).items;
const locations = storeToRefs(usePointsOfInterestStore()).items;
const airports = storeToRefs(useAirportsStore()).items;
const copilots = storeToRefs(useSecondPilotsStore()).items;
const copilots = storeToRefs(useCopilotsStore()).items;
let flight = ref({});
const disabledSteps = computed(() => flight.value.solo ? ["copilot"] : [flight.value.solo]);
@@ -51,13 +51,12 @@ onMounted(() => {
useAircraftsStore().fetch();
usePointsOfInterestStore().fetch();
useAirportsStore().fetch();
useSecondPilotsStore().fetch();
useCopilotsStore().fetch();
fetchFlight(flightId);
});
const handleFlightEdit = (data) => {
console.log(data);
useFlightsStore().update(flightId, data).then(() => {
$q.notify(successObject("Úspěšně upraveno"));
fetchFlight(flightId);
+2 -2
View File
@@ -3,7 +3,7 @@ import {useAircraftsStore} from "stores/Aircrafts";
import {onMounted, ref} from "vue";
import {storeToRefs} from "pinia";
import AircraftsList from "components/aircrafts/AircraftsList.vue";
import AddFAB from "components/AddFAB.vue";
import AddFAB from "components/FAB.vue";
import AircraftsListSkeleton from "components/aircrafts/AircraftsListSkeleton.vue";
const aircraftStore = useAircraftsStore();
@@ -24,5 +24,5 @@ onMounted(() => {
<AircraftsListSkeleton v-if="loading" />
<AircraftsList :items="aircrafts" v-if="!loading"/>
<AddFAB :router-link="{name: 'addAircraft'}"/>
<AddFAB mode="add" :router-link="{name: 'addAircraft'}"/>
</template>
+2 -2
View File
@@ -5,7 +5,7 @@ import {useMeta} from "quasar";
import FlightsList from "components/flights/FlightsList.vue";
import FlightsListSkeleton from "components/flights/FlightsListSkeleton.vue";
import {storeToRefs} from "pinia";
import AddFAB from "components/AddFAB.vue";
import AddFAB from "components/FAB.vue";
useMeta({title: "Moje lety"});
@@ -39,5 +39,5 @@ onMounted(() => {
<FlightsList :items="flights" v-else/>
<AddFAB :router-link="{name: 'addFlight'}"/>
<AddFAB mode="add" :router-link="{name: 'addFlight'}"/>
</template>
+2 -2
View File
@@ -1,9 +1,9 @@
<script setup>
import {storeToRefs} from "pinia";
import {useSecondPilotsStore} from "stores/SecondPilots";
import {useCopilotsStore} from "stores/Copilots";
import {onMounted} from "vue";
const pilotsStore = useSecondPilotsStore();
const pilotsStore = useCopilotsStore();
onMounted(() => {
pilotsStore.fetch();
+23
View File
@@ -0,0 +1,23 @@
import {defineStore} from 'pinia'
import {apolloWrapper} from "src/graphql/apollo";
import {COPILOTS_LIST} from "src/graphql/queries/copilots";
export const useCopilotsStore = defineStore('SecondPilots', {
state: () => ({
items: []
}),
getters: {},
actions: {
create(formData) {
console.log("not implemented");
},
fetch() {
return apolloWrapper.query(COPILOTS_LIST).then(result => {
this.items = result.data.copilots;
return result.data.copilots;
});
}
}
})
-26
View File
@@ -1,26 +0,0 @@
import {defineStore} from 'pinia'
export const useSecondPilotsStore = defineStore('SecondPilots', {
state: () => ({
items: []
}),
getters: {},
actions: {
create(formData) {
this.items.push(formData);
},
fetch() {
if (this.items.length > 0) return;
this.$patch({
items: [
{id: 1, name: "Karel Franta", isInstructor: false},
{id: 2, name: "Franta Pepa", isInstructor: false},
{id: 3, name: "Instruktor Letajici", isInstructor: true},
]
});
}
}
})