Homepage, lepsi POI

This commit is contained in:
Michal Kváček
2023-08-23 21:40:32 +02:00
parent 8c3b8d19dd
commit 19b078b43c
33 changed files with 370 additions and 141 deletions
@@ -0,0 +1,23 @@
<script setup>
import {randomInt} from "src/composables/random";
const props = defineProps({
lines: {
type: Number,
required: false,
default: 3
},
paragraphs: {
type: Number,
required: false,
default: 1
}
})
</script>
<template>
<div v-for="i in paragraphs" :key="i">
<q-skeleton type="text" :width="randomInt(80, 100)+'%'" v-for="j in lines - 1" :key="j"/>
<q-skeleton type="text" :width="randomInt(15, 100)+'%'"/>
</div>
</template>
@@ -1,6 +1,7 @@
<script setup> <script setup>
import {randomInt} from "src/composables/random"; import {randomInt} from "src/composables/random";
import ListItemSkeleton from "components/ListItemSkeleton.vue"; import ListItemSkeleton from "components/basicSkeletons/ListItemSkeleton.vue";
import BasicTextSkeleton from "components/basicSkeletons/BasicTextSkeleton.vue";
</script> </script>
<template> <template>
@@ -25,10 +26,7 @@ import ListItemSkeleton from "components/ListItemSkeleton.vue";
</q-list> </q-list>
<article class="q-my-lg"> <article class="q-my-lg">
<q-skeleton <BasicTextSkeleton :lines="randomInt(5, 10)" :paragraphs="randomInt(1, 3)" />
v-for="i in randomInt(5,8)" :key="i"
type="text" :width="randomInt(90, 100)+'%'"
/>
</article> </article>
<q-skeleton height="400px" square class="q-mt-lg"/> <q-skeleton height="400px" square class="q-mt-lg"/>
@@ -5,6 +5,7 @@ import {setDefaultData} from "src/composables/form/formHandling";
import DatetimePicker from "components/inputs/DatetimePicker.vue"; import DatetimePicker from "components/inputs/DatetimePicker.vue";
import ComboboxInput from "components/inputs/ComboboxInput.vue"; import ComboboxInput from "components/inputs/ComboboxInput.vue";
import {commonProps} from "src/composables/inputs"; import {commonProps} from "src/composables/inputs";
import {isRequired} from "src/composables/form/rules";
const props = defineProps({ const props = defineProps({
aircrafts: { aircrafts: {
@@ -34,7 +35,13 @@ const formData = reactive({
description: "", description: "",
}); });
const rules = {}; const rules = {
aircraft: [isRequired],
takeoffDatetime: [isRequired],
takeoffAirport: [isRequired],
landingDatetime: [isRequired],
landingAirport: [isRequired],
};
watch(() => props.flight, (newVal) => { watch(() => props.flight, (newVal) => {
if (newVal) { if (newVal) {
setDefaultData(formData, newVal); setDefaultData(formData, newVal);
@@ -50,7 +57,8 @@ const aircrafts = computed(() => {
watch(() => formData.takeoffDatetime, () => { watch(() => formData.takeoffDatetime, () => {
if (!formData.landingDatetime) { if (!formData.landingDatetime) {
formData.landingDatetime = formData.takeoffDatetime; formData.landingDatetime = structuredClone(formData.takeoffDatetime);
formData.landingDatetime.setTime(formData.landingDatetime.getTime() + 3600 * 1000);
} }
}); });
watch(() => formData.takeoffAirport, () => { watch(() => formData.takeoffAirport, () => {
@@ -71,10 +79,10 @@ watch(() => formData.takeoffAirport, () => {
<q-card-section> <q-card-section>
<ComboboxInput <ComboboxInput
:rules="rules.aircraft"
v-bind="commonProps" v-bind="commonProps"
:options="aircrafts" :options="aircrafts"
v-model="formData.aircraft" v-model="formData.aircraft"
:key="formData.aircraft"
label="Letadlo" label="Letadlo"
/> />
</q-card-section> </q-card-section>
@@ -90,12 +98,14 @@ watch(() => formData.takeoffAirport, () => {
<q-card-section> <q-card-section>
<DatetimePicker <DatetimePicker
v-model="formData.takeoffDatetime" v-model="formData.takeoffDatetime"
:rules="rules.takeoffDatetime"
label="Datum a čas vzletu: " label="Datum a čas vzletu: "
v-bind="commonProps" v-bind="commonProps"
/> />
<ComboboxInput <ComboboxInput
:options="airports" :options="airports"
:rules="rules.takeoffAirport"
v-model="formData.takeoffAirport" v-model="formData.takeoffAirport"
label="Letiště vzletu" label="Letiště vzletu"
v-bind="commonProps" v-bind="commonProps"
@@ -115,6 +125,7 @@ watch(() => formData.takeoffAirport, () => {
<DatetimePicker <DatetimePicker
v-model="formData.landingDatetime" v-model="formData.landingDatetime"
:min-date="formData.takeoffDatetime" :min-date="formData.takeoffDatetime"
:rules="rules.landingDatetime"
label="Datum a čas přistání" label="Datum a čas přistání"
v-bind="commonProps" v-bind="commonProps"
/> />
@@ -124,6 +135,7 @@ watch(() => formData.takeoffAirport, () => {
v-model="formData.landingAirport" v-model="formData.landingAirport"
label="Letiště přistání" label="Letiště přistání"
v-bind="commonProps" v-bind="commonProps"
:rules="rules.landingAirport"
:enable-new-values="false" :enable-new-values="false"
/> />
</q-card-section> </q-card-section>
+19 -7
View File
@@ -1,5 +1,5 @@
<script setup> <script setup>
import {onMounted, ref, watch} from "vue"; import {ref, watch} from "vue";
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@@ -19,6 +19,16 @@ const props = defineProps({
type: Array, type: Array,
required: true, required: true,
}, },
optionLabel: {
type: String,
required: false,
default: "name"
},
optionValue: {
type: String,
required: false,
default: "id"
}
}) })
const model = ref({}); const model = ref({});
@@ -32,6 +42,9 @@ watch(model, (val) => {
let value = val; let value = val;
if (typeof val === 'string') value = {name: val}; if (typeof val === 'string') value = {name: val};
if (value['__typename']) {
delete value['__typename'];
}
emit("update:modelValue", value); emit("update:modelValue", value);
}) })
@@ -72,7 +85,6 @@ const filterFn = (val, update, abort) => {
availableOptions.value = availableOptions.value.map(i => ({id: i.id, name: i.name})) availableOptions.value = availableOptions.value.map(i => ({id: i.id, name: i.name}))
}) })
} }
</script> </script>
<template> <template>
@@ -80,23 +92,23 @@ const filterFn = (val, update, abort) => {
v-model="model" v-model="model"
use-input use-input
input-debounce="100" input-debounce="100"
:display-value="`${model?.name || ''}`" :display-value="`${model ? model[optionLabel] : ''}`"
:rules="rules" :rules="rules"
@filter="filterFn" @filter="filterFn"
@input-value="inputValueChanged" @input-value="inputValueChanged"
@blur="handleOnBlur" @blur="handleOnBlur"
@clear="clearValue" @clear="clearValue"
:options="availableOptions" :options="availableOptions"
option-label="name" :option-label="optionLabel"
option-value="id" :option-value="optionValue"
:new-value-mode="enableNewValues ? 'add' : 'toggle'" :new-value-mode="enableNewValues ? 'add' : 'toggle'"
clear-icon="clear" clear-icon="clear"
:v-bind="$attrs" :v-bind="$attrs"
> >
<template v-slot:no-option> <template v-slot:no-option v-if="enableNewValues">
<q-item v-if="inputValue"> <q-item v-if="inputValue">
<q-item-section avatar> <q-item-section avatar>
<q-avatar text-color="green" size="32px" icon="add" /> <q-avatar text-color="green" size="32px" icon="add"/>
</q-item-section> </q-item-section>
<q-item-section class="text-grey"> <q-item-section class="text-grey">
<span>Vytvořit novou hodnotu <q-chip>{{ inputValue }}</q-chip></span> <span>Vytvořit novou hodnotu <q-chip>{{ inputValue }}</q-chip></span>
+8 -23
View File
@@ -15,35 +15,18 @@ const props = defineProps({
minDate: { minDate: {
type: [String, Date], type: [String, Date],
required: false required: false
},
rules: {
type: Array,
required: false,
default: () => ([]),
} }
}) })
let dateModel = ref(""); let dateModel = ref("");
let timeModel = ref(""); let timeModel = ref("");
let originalTime, originalDate; let originalTime, originalDate;
// onMounted(() => {
// let emitDefaultValue = false;
// if (props.modelValue) {
// let modelVal = props.modelValue;
// if (typeof modelVal === 'string') {
// modelVal = parseDate(props.modelValue);
// emitDefaultValue = true;
// }
//
// dateModel.value = formatDatetime(modelVal, "YYYY-MM-DD");
// timeModel.value = formatDatetime(modelVal, "HH:mm:ss");
// }
//
// setOriginalValues();
// if (emitDefaultValue) {
// emitModelUpdate();
// }
// });
// TODO: ^^ by asi slo prepsat do watchera, viz ComboboxInput
const publicModel = computed(() => { const publicModel = computed(() => {
if (!dateModel.value) { if (!dateModel.value) {
return ""; return "";
@@ -57,6 +40,8 @@ const popupProxy = ref(null);
const model = computed(() => dateModel.value + "T" + timeModel.value); const model = computed(() => dateModel.value + "T" + timeModel.value);
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const emitModelUpdate = () => { const emitModelUpdate = () => {
if (dateModel.value === '' || timeModel.value === '') return;
const val = parseDate(model.value); const val = parseDate(model.value);
emit("update:modelValue", val); emit("update:modelValue", val);
@@ -128,7 +113,7 @@ watch(() => props.modelValue, (newVal) => {
</script> </script>
<template> <template>
<q-input readonly v-bind="commonProps" v-model="publicModel"> <q-input readonly v-bind="commonProps" v-model="publicModel" :rules="rules">
<template #append> <template #append>
<q-icon name="event"/> <q-icon name="event"/>
</template> </template>
+18 -2
View File
@@ -1,5 +1,21 @@
<script setup></script> <script setup>
import {randomInt} from "src/composables/random";
import BasicTextSkeleton from "components/basicSkeletons/BasicTextSkeleton.vue";
</script>
<template> <template>
<em>POI detail skeleton</em> <div>
<BasicTextSkeleton :lines="randomInt(4, 8)" />
<q-skeleton type="rect" width="100%" class="q-my-lg" height="400px"/>
<div class="row">
<div class="col-12 col-sm-4 col-md-4 col-lg-3" v-for="i in randomInt(3, 10)" :key="i">
<q-card class="q-my-sm q-mx-sm">
<q-skeleton type="rect" width="100%" height="200px"/>
</q-card>
</div>
</div>
</div>
</template> </template>
@@ -5,25 +5,36 @@ import {setDefaultData} from "src/composables/form/formHandling";
import MapGPSInput from "components/inputs/MapGPSInput.vue"; import MapGPSInput from "components/inputs/MapGPSInput.vue";
import {useQuasar} from "quasar"; import {useQuasar} from "quasar";
import {errorObject} from "src/composables/dialog"; import {errorObject} from "src/composables/dialog";
import {commonProps} from "src/composables/inputs";
import {isRequired} from "src/composables/form/rules";
import ComboboxInput from "components/inputs/ComboboxInput.vue";
const props = defineProps({ const props = defineProps({
poi: { poi: {
type: Object, type: Object,
required: false, required: false,
default: () => ({}) default: () => ({})
},
types: {
type: Array,
required: false,
default: () => ([])
} }
}) })
const formData = reactive({ const formData = reactive({
name: null, name: null,
description: null,
gpsLatitude: null, gpsLatitude: null,
gpsLongitude: null, gpsLongitude: null,
type: null,
isPublic: false, isPublic: false,
}); });
const poiNameForMap = ref(""); const poiNameForMap = ref("");
onMounted(() => { onMounted(() => {
setDefaultData(formData, props.poi); setDefaultData(formData, props.poi);
// formData.type = {id: poi.type.id, name: poi.type.name};
poiNameForMap.value = props.poi.name; poiNameForMap.value = props.poi.name;
}) })
@@ -35,6 +46,10 @@ const resetMap = () => {
showResetMapButton.value = false; showResetMapButton.value = false;
} }
const rules = {
name: [isRequired]
}
const zoom = ref(10); const zoom = ref(10);
const $q = useQuasar(); const $q = useQuasar();
const handlePositionNotFound = () => { const handlePositionNotFound = () => {
@@ -46,19 +61,17 @@ const handlePositionNotFound = () => {
<template> <template>
<FormWrapper v-model="formData"> <FormWrapper v-model="formData">
<q-input <q-input
class="q-my-lg"
outlined
standout
v-model="formData.name" v-model="formData.name"
@change="showResetMapButton=true" @change="showResetMapButton=true"
label="Jméno bodu" label="Jméno bodu"
v-bind="commonProps"
:rules="rules.name"
> >
<template #append v-if="showResetMapButton"> <template #append v-if="showResetMapButton">
<q-btn @click="resetMap" icon="gps_fixed" round flat text-color="green" title="Nastav pozici na mapě"/> <q-btn @click="resetMap" icon="gps_fixed" round flat text-color="green" title="Nastav pozici na mapě"/>
</template> </template>
</q-input> </q-input>
<MapGPSInput <MapGPSInput
v-if="poiNameForMap" v-if="poiNameForMap"
:key="poiNameForMap" :key="poiNameForMap"
@@ -69,6 +82,10 @@ const handlePositionNotFound = () => {
@not-found="handlePositionNotFound" @not-found="handlePositionNotFound"
/> />
<q-input type="textarea" v-model="formData.description" v-bind="commonProps" label="Popis" />
<ComboboxInput v-model="formData.type" :options="types" label="Kategorie" v-bind="commonProps" />
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/> <q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
</FormWrapper> </FormWrapper>
</template> </template>
+9 -4
View File
@@ -8,14 +8,19 @@ const props = defineProps({
</script> </script>
<template> <template>
<q-list bordered separator> <q-list bordered separator>
<q-item clickable v-ripple v-for="poi of items" :key="poi"> <q-item clickable v-ripple v-for="poi of items" :key="poi" :to="{name: 'poiDetail', params: {id: poi.id}}">
<q-item-section avatar> <q-item-section avatar>
<q-avatar icon="place" color="primary" text-color="white"/> <q-avatar :icon="poi.type?.icon || 'place'" color="primary" text-color="white"/>
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<router-link :to="{name: 'poiDetail', params: {id: poi.id}}">
{{ poi.name }} {{ poi.name }}
</router-link> <div class="text-secondary" v-if="poi.type">
{{ poi.type.name }}
</div>
</q-item-section>
<q-item-section side top v-if="poi.isPublic">
<q-icon name="visibility"></q-icon>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
+1 -32
View File
@@ -1,5 +1,5 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import {FRAGMENT_AIRCRAFT_BASIC_INFO, FRAGMENT_AIRCRAFT_FULL_INFO} from "src/graphql/fragments/aircraft"; import {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"; import {FRAGMENT_POI_FULL_INFO} from "src/graphql/fragments/poi";
@@ -77,34 +77,3 @@ ${FRAGMENT_AIRPORT_INFO}
${FRAGMENT_POI_FULL_INFO} ${FRAGMENT_POI_FULL_INFO}
${FRAGMENT_WEATHER_FULL_INFO} ${FRAGMENT_WEATHER_FULL_INFO}
`; `;
export const FRAGMENT_FLIGHT_LIST_INFO = gql`
fragment flightListInfo on Flight {
id
name
takeoffDatetime
landingDatetime
durationMinCalculated
isPublic
coverPhoto {
thumbnailUrl
}
takeoffAirport {
...airportInfo
}
landingAirport {
...airportInfo
}
aircraft {
...aircraftBasicInfo
}
}
${FRAGMENT_AIRPORT_INFO}
${FRAGMENT_AIRCRAFT_BASIC_INFO}
`
+34
View File
@@ -0,0 +1,34 @@
import gql from "graphql-tag";
import {FRAGMENT_AIRPORT_INFO} from "src/graphql/fragments/airport";
import {FRAGMENT_AIRCRAFT_BASIC_INFO} from "src/graphql/fragments/aircraft";
export const FRAGMENT_FLIGHT_LIST_INFO = gql`
fragment flightListInfo on Flight {
id
name
takeoffDatetime
landingDatetime
durationMinCalculated
isPublic
coverPhoto {
thumbnailUrl
}
takeoffAirport {
...airportInfo
}
landingAirport {
...airportInfo
}
aircraft {
...aircraftBasicInfo
}
}
${FRAGMENT_AIRPORT_INFO}
${FRAGMENT_AIRCRAFT_BASIC_INFO}
`;
+17
View File
@@ -1,9 +1,15 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import {FRAGMENT_FLIGHT_LIST_INFO} from "src/graphql/fragments/flight_list";
export const FRAGMENT_POI_LIST_INFO = gql` export const FRAGMENT_POI_LIST_INFO = gql`
fragment pointOfInterestListInfo on PointOfInterest { fragment pointOfInterestListInfo on PointOfInterest {
id id
name name
isPublic
type {
id
name
}
} }
`; `;
@@ -11,9 +17,18 @@ export const FRAGMENT_POI_FULL_INFO = gql`
fragment pointOfInterestFullInfo on PointOfInterest { fragment pointOfInterestFullInfo on PointOfInterest {
id id
name name
description
isPublic isPublic
gpsLatitude gpsLatitude
gpsLongitude gpsLongitude
createdById
type {
id
name
}
flights {
...flightListInfo
}
photos { photos {
thumbnailUrl thumbnailUrl
url url
@@ -21,4 +36,6 @@ fragment pointOfInterestFullInfo on PointOfInterest {
description description
} }
} }
${FRAGMENT_FLIGHT_LIST_INFO}
`; `;
+20
View File
@@ -0,0 +1,20 @@
import gql from "graphql-tag";
export const FRAGMENT_POI_TYPE_LIST_INFO = gql`
fragment poiTypeListInfo on PointOfInterestType {
id
name
icon
isPublic
}
`;
export const FRAGMENT_POI_TYPE_FULL_INFO = gql`
fragment poiTypeFullInfo on PointOfInterestType {
id
name
icon
isPublic
createdById
}
`;
+1 -1
View File
@@ -1,6 +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"; import {FRAGMENT_FLIGHT_LIST_INFO} from "src/graphql/fragments/flight_list";
export const AIRCRAFTS_LIST = gql` export const AIRCRAFTS_LIST = gql`
query AircraftsList { query AircraftsList {
+4 -3
View File
@@ -1,5 +1,5 @@
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_list";
import {FRAGMENT_COPILOT_FULL_INFO, FRAGMENT_COPILOT_LIST_INFO} from "src/graphql/fragments/copilot"; import {FRAGMENT_COPILOT_FULL_INFO, FRAGMENT_COPILOT_LIST_INFO} from "src/graphql/fragments/copilot";
export const COPILOTS_LIST = gql` export const COPILOTS_LIST = gql`
@@ -18,11 +18,12 @@ query copilot($id: Int!) {
...copilotFullInfo ...copilotFullInfo
flights { flights {
...flightListInfo id
name
} }
} }
} }
${FRAGMENT_COPILOT_FULL_INFO} ${FRAGMENT_COPILOT_FULL_INFO}
${FRAGMENT_FLIGHT_LIST_INFO}
`; `;
+2 -1
View File
@@ -1,5 +1,6 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import {FRAGMENT_FLIGHT_FULL_INFO, FRAGMENT_FLIGHT_LIST_INFO} from "src/graphql/fragments/flight"; import {FRAGMENT_FLIGHT_FULL_INFO} from "src/graphql/fragments/flight";
import {FRAGMENT_FLIGHT_LIST_INFO} from "src/graphql/fragments/flight_list";
export const FLIGHTS_LIST = gql` export const FLIGHTS_LIST = gql`
query FlightsList($username: String) { query FlightsList($username: String) {
+22
View File
@@ -0,0 +1,22 @@
import gql from "graphql-tag";
import {FRAGMENT_POI_TYPE_FULL_INFO, FRAGMENT_POI_TYPE_LIST_INFO} from "src/graphql/fragments/poi_type";
export const POI_TYPE_LIST = gql`
query poiTypeList {
pointOfInterestTypes {
...poiTypeListInfo
}
}
${FRAGMENT_POI_TYPE_LIST_INFO}
`;
export const POI_TYPE_DETAIL = gql`
query poiTypeDetail($id: Int!) {
pointOfInterestType(id: $id) {
...poiTypeFullInfo
}
}
${FRAGMENT_POI_TYPE_FULL_INFO}
`;
@@ -53,12 +53,6 @@ watch(aircraft, (newVal) => {
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">
@@ -66,6 +60,14 @@ watch(aircraft, (newVal) => {
</q-card-actions> </q-card-actions>
</q-card> </q-card>
<q-card class="flight-detail-card" flat>
<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>
<FAB :router-link='{name: "aircraftEdit", params: {id: aircraft.id}}' mode="edit"/> <FAB :router-link='{name: "aircraftEdit", params: {id: aircraft.id}}' mode="edit"/>
</template> </template>
+1 -1
View File
@@ -3,7 +3,7 @@ import {onMounted} from "vue";
import AircraftsList from "components/aircrafts/AircraftsList.vue"; import AircraftsList from "components/aircrafts/AircraftsList.vue";
import {aircrafts, fetchAircrafts, loading} from "src/composables/objects/aircraft"; import {aircrafts, fetchAircrafts, loading} from "src/composables/objects/aircraft";
import FAB from "components/FAB.vue"; import FAB from "components/FAB.vue";
import BasicListSkeleton from "components/BasicListSkeleton.vue"; import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
onMounted(() => { onMounted(() => {
fetchAircrafts(); fetchAircrafts();
+1 -1
View File
@@ -4,7 +4,7 @@ import {fetchCopilots, loading, copilots} from "src/composables/objects/copilot"
import CopilotsList from "components/copilots/CopilotsList.vue"; import CopilotsList from "components/copilots/CopilotsList.vue";
import FAB from "components/FAB.vue"; import FAB from "components/FAB.vue";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import BasicListSkeleton from "components/BasicListSkeleton.vue"; import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
import BasicInfoBanner from "components/BasicInfoBanner.vue"; import BasicInfoBanner from "components/BasicInfoBanner.vue";
onMounted(() => { onMounted(() => {
+1 -1
View File
@@ -30,7 +30,7 @@ onMounted(() => {
</q-banner> </q-banner>
</div> </div>
<FlightsList class="q-mx-md q-my-md" v-else :items="flights" @delete="handleDeleteFlight($q, $event)" /> <FlightsList v-else :items="flights" @delete="handleDeleteFlight($q, $event)" />
<FAB mode="add" :router-link="{name: 'addFlight'}"/> <FAB mode="add" :router-link="{name: 'addFlight'}"/>
</template> </template>
+6 -2
View File
@@ -24,7 +24,6 @@ onMounted(() => {
]); ]);
}); });
const aircrafts = storeToRefs(useAircraftsStore()).items; const aircrafts = storeToRefs(useAircraftsStore()).items;
const locations = storeToRefs(usePointsOfInterestStore()).items; const locations = storeToRefs(usePointsOfInterestStore()).items;
const airports = storeToRefs(useAirportsStore()).items; const airports = storeToRefs(useAirportsStore()).items;
@@ -39,5 +38,10 @@ const handleSave = (formData) => {
<template> <template>
<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"
/>
</template> </template>
@@ -2,12 +2,14 @@
import POIEditSkeleton from "components/poi/POIEditSkeleton.vue"; import POIEditSkeleton from "components/poi/POIEditSkeleton.vue";
import {fetchPoi, loading, poi} from "src/composables/objects/poi"; import {fetchPoi, loading, poi} from "src/composables/objects/poi";
import {onMounted} from "vue"; import {onMounted} from "vue";
import {useRoute} from "vue-router"; import {useRoute, useRouter} from "vue-router";
import POIEditForm from "components/poi/POIEditForm.vue";
import {usePointsOfInterestStore} from "stores/PointsOfInterest"; import {usePointsOfInterestStore} from "stores/PointsOfInterest";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import {useQuasar} from "quasar"; import {useQuasar} from "quasar";
import {successObject} from "src/composables/dialog"; import {successObject} from "src/composables/dialog";
import {usePointsOfInterestTypeStore} from "stores/PointOfInterestType";
import {storeToRefs} from "pinia";
import POIForm from "components/poi/POIForm.vue";
const poiId = parseInt(useRoute().params.id); const poiId = parseInt(useRoute().params.id);
@@ -20,12 +22,18 @@ onMounted(() => {
{label: "Editace"}, {label: "Editace"},
]); ]);
}); });
usePointsOfInterestTypeStore().fetch();
}) })
const poiTypes = storeToRefs(usePointsOfInterestTypeStore()).items;
const $q = useQuasar(); const $q = useQuasar();
const router = useRouter();
const handleSave = (data) => { const handleSave = (data) => {
usePointsOfInterestStore().update(poiId, data).then(respData => { usePointsOfInterestStore().update(poiId, data).then(respData => {
$q.notify(successObject("Úspěšně uloženo")); $q.notify(successObject("Úspěšně uloženo"));
router.push({name: "poiDetail", params: {id: poiId}});
}); });
} }
</script> </script>
@@ -33,5 +41,5 @@ const handleSave = (data) => {
<template> <template>
<POIEditSkeleton v-if="loading"/> <POIEditSkeleton v-if="loading"/>
<POIEditForm :poi="poi" v-else @save="handleSave"/> <POIForm :poi="poi" :types="poiTypes" v-else @save="handleSave"/>
</template> </template>
@@ -1,25 +1,32 @@
<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 {ref} from "vue";
import POIForm from "components/poi/POIForm.vue";
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
import {successObject} from "src/composables/dialog";
import {useAppStore} from "stores/App";
import {storeToRefs} from "pinia";
let isRequestPending = ref(false);
const $q = useQuasar(); const $q = useQuasar();
const router = useRouter(); const router = useRouter();
const handleSave = (formData) => {
isRequestPending.value = true;
useAircraftsStore().create(formData).then(() => {
$q.notify({message: "Letadlo úspěšně vytvořeno", position: "top", color: "positive"});
router.push({name: "aircrafts"}); const handleSave = (formData) => {
}).finally(() => { usePointsOfInterestStore().create(formData).then(() => {
isRequestPending.value = true; $q.notify(successObject("Místo úspěšně uloženo"));
router.push({name: "poi"});
}) })
} }
const isRequestPending = storeToRefs(usePointsOfInterestStore()).isMutationRunning;
useAppStore().setPageTitle("Nové zajímavé místo");
useAppStore().setBreadcrumbs([
{to: "poi", label: "Zajímavá místa"},
{label: "Nové"},
])
</script> </script>
<template> <template>
<AircraftForm @save="handleSave" :is-request-pending="isRequestPending"/> <POIForm @save="handleSave" :is-request-pending="isRequestPending"/>
</template> </template>
@@ -7,6 +7,12 @@ 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 EmbeddedMap from "components/map/EmbeddedMap.vue"; import EmbeddedMap from "components/map/EmbeddedMap.vue";
import {useUserStore} from "stores/User";
import {storeToRefs} from "pinia";
import BasicInfoBanner from "components/BasicInfoBanner.vue";
import FlightsList from "components/flights/FlightsList.vue";
const loggedUser = storeToRefs(useUserStore()).user;
onMounted(() => { onMounted(() => {
fetchPoi(useRoute().params.id).then(() => { fetchPoi(useRoute().params.id).then(() => {
@@ -16,7 +22,6 @@ onMounted(() => {
{label: poi.value.name}, {label: poi.value.name},
]) ])
}); });
}) })
</script> </script>
@@ -24,6 +29,7 @@ onMounted(() => {
<POIDetailSkeleton v-if="loading || !poi"/> <POIDetailSkeleton v-if="loading || !poi"/>
<template v-else> <template v-else>
<article v-html="poi.description" />
<EmbeddedMap <EmbeddedMap
v-if="poi.gpsLatitude && poi.gpsLongitude" v-if="poi.gpsLatitude && poi.gpsLongitude"
@@ -31,12 +37,23 @@ onMounted(() => {
:gps-longitude="poi.gpsLongitude" :gps-longitude="poi.gpsLongitude"
:zoom="13" :zoom="13"
/> />
<BasicInfoBanner v-else>
Tento bod nemá nastavenou polohu. Po nastavení polohy tu bude zobrazena mapa.
</BasicInfoBanner>
<PhotosList <PhotosList
:photos="poi.photos" :photos="poi.photos"
:show-actions="false" :show-actions="false"
v-if="poi.photos.length > 0"
/> />
<FAB :router-link="{name: 'poiEdit', params:{id: poi.id}}" mode="edit"/>
<BasicInfoBanner v-else>
Pokud přidáš toto místo k nějaké fotce, zobrazí se tu.
</BasicInfoBanner>
<FlightsList :items="poi.flights" />
<FAB v-if="poi.createdById === loggedUser.id" :router-link="{name: 'poiEdit', params:{id: poi.id}}" mode="edit"/>
</template> </template>
</template> </template>
@@ -4,7 +4,7 @@ import {fetchPOIList, loading, pointsOfInterest} from "src/composables/objects/p
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import POIList from "components/poi/POIList.vue"; import POIList from "components/poi/POIList.vue";
import FAB from "components/FAB.vue"; import FAB from "components/FAB.vue";
import BasicListSkeleton from "components/BasicListSkeleton.vue"; import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
onMounted(() => { onMounted(() => {
fetchPOIList(); fetchPOIList();
+6 -6
View File
@@ -2,6 +2,7 @@ import {defineStore} from 'pinia'
import {apolloWrapper} from "src/graphql/apollo"; import {apolloWrapper} from "src/graphql/apollo";
import {AIRCRAFT_DETAIL, AIRCRAFTS_LIST} from "src/graphql/queries/aircrafts"; import {AIRCRAFT_DETAIL, AIRCRAFTS_LIST} from "src/graphql/queries/aircrafts";
import {CREATE_AIRCRAFT, DELETE_AIRCRAFT, EDIT_AIRCRAFT} from "src/graphql/mutations/aircrafts"; import {CREATE_AIRCRAFT, DELETE_AIRCRAFT, EDIT_AIRCRAFT} from "src/graphql/mutations/aircrafts";
import {deleteFromItems, updateInItems} from "stores/helpers";
export const useAircraftsStore = defineStore('Aircrafts', { export const useAircraftsStore = defineStore('Aircrafts', {
state: () => ({ state: () => ({
@@ -29,17 +30,16 @@ export const useAircraftsStore = defineStore('Aircrafts', {
}, },
delete(id) { delete(id) {
return apolloWrapper.mutate(DELETE_AIRCRAFT, {id}).then(({data}) => { return apolloWrapper.mutate(DELETE_AIRCRAFT, {id}).then(({data}) => {
const idx = this.items.findIndex(a => a.id === id); deleteFromItems(this.items, 'id', id);
console.log(idx);
this.items.splice(idx, 1);
return data; return data;
}); });
}, },
update(id, formData) { update(id, formData) {
return apolloWrapper.mutate(EDIT_AIRCRAFT, {id: id, input: formData}).then(({data}) => { return apolloWrapper.mutate(EDIT_AIRCRAFT, {id: id, input: formData}).then(({data}) => {
console.log("Pridat update ve storu") const aircraft = data.editAircraft;
return data.editAircraft; updateInItems(this.items, 'id', aircraft);
return aircraft;
}); });
}, },
fetch() { fetch() {
@@ -47,7 +47,7 @@ export const useAircraftsStore = defineStore('Aircrafts', {
.query(AIRCRAFTS_LIST) .query(AIRCRAFTS_LIST)
.then((result) => { .then((result) => {
this.items = result.data.aircrafts; this.items = result.data.aircrafts;
return result.data.aircrafts; return this.items;
}); });
} }
} }
+5 -1
View File
@@ -2,6 +2,7 @@ import {defineStore} from 'pinia'
import {apolloWrapper} from "src/graphql/apollo"; import {apolloWrapper} from "src/graphql/apollo";
import {COPILOT_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"; import {CREATE_COPILOT, EDIT_COPILOT} from "src/graphql/mutations/copilot";
import {updateInItems} from "stores/helpers";
export const useCopilotsStore = defineStore('SecondPilots', { export const useCopilotsStore = defineStore('SecondPilots', {
state: () => ({ state: () => ({
@@ -28,7 +29,10 @@ export const useCopilotsStore = defineStore('SecondPilots', {
}, },
update(id, formData) { update(id, formData) {
return apolloWrapper.mutate(EDIT_COPILOT, {id: id, input: formData}).then(({data}) => { return apolloWrapper.mutate(EDIT_COPILOT, {id: id, input: formData}).then(({data}) => {
return data.editCopilot; const copilot = data.editCopilot;
updateInItems(this.items, "id", copilot);
return copilot;
}); });
}, },
} }
+2 -6
View File
@@ -2,6 +2,7 @@ import {defineStore} from 'pinia'
import {apolloWrapper} from "src/graphql/apollo" import {apolloWrapper} from "src/graphql/apollo"
import {FLIGHT_DETAIL, FLIGHTS_LIST} from "src/graphql/queries/flights"; import {FLIGHT_DETAIL, FLIGHTS_LIST} from "src/graphql/queries/flights";
import {EDIT_FLIGHT, CREATE_FLIGHT, DELETE_FLIGHT} from "src/graphql/mutations/flight"; import {EDIT_FLIGHT, CREATE_FLIGHT, DELETE_FLIGHT} from "src/graphql/mutations/flight";
import {updateInItems} from "stores/helpers";
export const useFlightsStore = defineStore('Flights', { export const useFlightsStore = defineStore('Flights', {
state: () => ({ state: () => ({
@@ -26,12 +27,7 @@ export const useFlightsStore = defineStore('Flights', {
update(id, formData) { update(id, formData) {
return apolloWrapper.mutate(EDIT_FLIGHT, {id: id, input: formData}).then(({data}) => { return apolloWrapper.mutate(EDIT_FLIGHT, {id: id, input: formData}).then(({data}) => {
const flight = data.editFlight; const flight = data.editFlight;
const idx = this.items.findIndex(item => item.id === id); updateInItems(this.items, "id", flight);
if (idx < 0) {
this.items.push(flight)
} else {
this.items[idx] = flight;
}
return flight; return flight;
}); });
+39
View File
@@ -0,0 +1,39 @@
import {defineStore} from 'pinia'
import {apolloWrapper} from "src/graphql/apollo";
import {POI_TYPE_DETAIL, POI_TYPE_LIST} from "src/graphql/queries/poi_type";
export const usePointsOfInterestTypeStore = defineStore('PointsOfInterestType', {
state: () => ({
items: []
}),
actions: {
// create(input) {
// return apolloWrapper.mutate(CREATE_POI, {input}).then((data) => {
// const poi = data.data.createPointOfInterest;
// this.items.push(poi);
// return poi;
// })
// },
// update(id, input) {
// return apolloWrapper.mutate(EDIT_POI, {id, input}).then((data) => {
// console.log("Pridat update POI ve store");
// return data.data.editPointOfInterest;
// })
// },
fetchDetail(id) {
if (typeof id !== "number") {
id = parseInt(id);
}
return apolloWrapper.query(POI_TYPE_DETAIL, {id}).then(result => result.data.pointOfInterest);
},
fetch() {
return apolloWrapper.query(POI_TYPE_LIST).then(result => {
this.items = result.data.pointOfInterestTypes;
return result.data.pointOfInterestTypes;
});
}
}
})
+20 -13
View File
@@ -1,32 +1,39 @@
import {defineStore} from 'pinia' import {defineStore} from 'pinia'
import {apolloWrapper} from "src/graphql/apollo"; import {apolloWrapper} from "src/graphql/apollo";
import {POI_DETAIL, POI_LIST} from "src/graphql/queries/poi"; import {POI_DETAIL, POI_LIST} from "src/graphql/queries/poi";
import {EDIT_POI} from "src/graphql/mutations/poi"; import {CREATE_POI, EDIT_POI} from "src/graphql/mutations/poi";
import {updateInItems} from "stores/helpers";
export const usePointsOfInterestStore = defineStore('PointsOfInterest', { export const usePointsOfInterestStore = defineStore('PointsOfInterest', {
state: () => ({ state: () => ({
items: [] items: [],
isMutationRunning: false,
}), }),
getters: { getters: {
getDetail: (state) => (poiId) => { getDetail: (state) => (poiId) => {
console.log(poiId, state.items, state.items.length); return state.items.find(p => p.id === poiId);
const item = state.items.find(p => {
console.log(p, poiId);
return p.id === poiId
}
);
console.log('item', item);
return item;
}, },
}, },
actions: { actions: {
create(input) {
this.isMutationRunning = true;
return apolloWrapper.mutate(CREATE_POI, {input}).then((data) => {
const poi = data.data.createPointOfInterest;
this.items.push(poi);
return poi;
}).finally(() => this.isMutationRunning = false);
},
update(id, input) { update(id, input) {
this.isMutationRunning = true;
return apolloWrapper.mutate(EDIT_POI, {id, input}).then((data) => { return apolloWrapper.mutate(EDIT_POI, {id, input}).then((data) => {
console.log("Pridat update POI ve store"); const poi = data.data.editPointOfInterest;
return data.data.editPointOfInterest; updateInItems(this.items, "id", poi);
}) return poi;
}).finally(() => this.isMutationRunning = false);
}, },
fetchDetail(id) { fetchDetail(id) {
+13
View File
@@ -0,0 +1,13 @@
export const updateInItems = (items, key, data) => {
const idx = items.findIndex(i => i[key] === data[key]);
if (idx >= 0)
items[idx] = data;
else items.push(data);
}
export const deleteFromItems = (items, key, value) => {
const idx = items.findIndex(a => a[key] === value);
if (idx < 0) return;
items.splice(idx, 1);
}