Prace s mapou, refresh tokenu (tak nejak), uprava vypisu POI, pridani breadcrumbs
This commit is contained in:
@@ -17,7 +17,7 @@ const props = defineProps({
|
||||
type: [Object, Boolean],
|
||||
required: false,
|
||||
default: () => ({
|
||||
label: "Save",
|
||||
label: "Uložit",
|
||||
icon: "save",
|
||||
align: "left"
|
||||
})
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<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>
|
||||
@@ -0,0 +1,12 @@
|
||||
<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>
|
||||
@@ -0,0 +1,5 @@
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
edit form
|
||||
</template>
|
||||
@@ -12,13 +12,13 @@ const props = defineProps({
|
||||
<template>
|
||||
<q-list bordered separator>
|
||||
<q-item clickable v-ripple v-for="pilot of items" :key="pilot">
|
||||
<q-item-label>
|
||||
<q-item-section avatar>
|
||||
<q-avatar icon="person" color="primary" text-color="white"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<router-link :to="{name: 'pilotDetail', params: {id: pilot.id }}">
|
||||
{{ pilot.name }}
|
||||
</router-link>
|
||||
</q-item-label>
|
||||
<q-item-section>
|
||||
section
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
@@ -13,21 +13,38 @@ const props = defineProps({
|
||||
<template>
|
||||
<q-list bordered separator>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="flight_takeoff"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ flight.takeoffAirport.name }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="flight_land"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ flight.landingAirport.name }}
|
||||
</q-item-section>
|
||||
|
||||
<q-list>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="flight_takeoff"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ flight.takeoffAirport.name }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item v-for="poi in flight.track" :key="poi.id">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="place"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ poi.pointOfInterest.name }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="flight_land"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ flight.landingAirport.name }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-item>
|
||||
|
||||
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="event"/>
|
||||
@@ -36,14 +53,6 @@ const props = defineProps({
|
||||
{{ formatDatetime(flight.takeoffDatetime) }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<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>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="schedule"/>
|
||||
@@ -52,12 +61,22 @@ const props = defineProps({
|
||||
{{ minutesToHumanReadable(flight.durationMinCalculated) }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<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-icon name="people"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ flight.copilot.name }}
|
||||
<router-link :to="{name: 'pilotDetail', params: {id: flight.copilot.id}}">
|
||||
{{ flight.copilot.name }}
|
||||
</router-link>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
@@ -12,21 +12,13 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const totalFlightTime = computed(() => {
|
||||
return props.items.reduce((acc, flight) => acc + flight.flightDurationMinutes, 0);
|
||||
})
|
||||
|
||||
const emit = defineEmits(["delete"]);
|
||||
const deleteFlight = (flightId) => {
|
||||
emit("delete", flightId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-4 col-lg-3" v-for="flight of items" :key="flight">
|
||||
<div class="col-12 col-md-6 col-lg-4" v-for="flight of items" :key="flight">
|
||||
|
||||
<q-card class="q-my-sm q-mx-sm">
|
||||
<q-card class="q-my-sm q-mx-sm" >
|
||||
<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">
|
||||
@@ -50,16 +42,7 @@ const deleteFlight = (flightId) => {
|
||||
{{ flight.aircraft.callSign }}
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-separator/>
|
||||
|
||||
<q-card-actions align="right">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
Total: {{ minutesToHumanReadable(totalFlightTime) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import FormWrapper from "components/FormWrapper.vue";
|
||||
import {reactive} from "vue";
|
||||
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
||||
|
||||
const props = defineProps({
|
||||
flight: {
|
||||
@@ -15,7 +16,18 @@ const formData = reactive({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormWrapper v-model="formData">
|
||||
<q-file multiple v-model="formData.photos" label="Fotky"/>
|
||||
<FormWrapper v-model="formData" :save-button='{
|
||||
label: "Nahrát",
|
||||
icon: "upload",
|
||||
align: "left"
|
||||
}'>
|
||||
<q-file
|
||||
class="q-my-lg"
|
||||
outlined
|
||||
standout
|
||||
multiple
|
||||
v-model="formData.photos"
|
||||
label="Vybert fotky k nahrání"
|
||||
/>
|
||||
</FormWrapper>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,67 @@
|
||||
<script setup></script>
|
||||
<script setup>
|
||||
import {Marker} from "vue3-google-map";
|
||||
import {onMounted, ref} from "vue";
|
||||
import axios from "axios";
|
||||
import MapWrapper from "components/MapWrapper.vue";
|
||||
|
||||
const center = ref({
|
||||
lat: 50.1314,
|
||||
lng: 14.5256
|
||||
});
|
||||
const zoom = ref(10);
|
||||
|
||||
const props = defineProps({
|
||||
gpsLatitude: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
gpsLongitude: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update:gpsLatitude", "update:gpsLongitude", "notFound"]);
|
||||
const emitModels = (lat, lng) => {
|
||||
emits("update:gpsLatitude", lat);
|
||||
emits("update:gpsLongitude", lng);
|
||||
}
|
||||
const markerDropped = (position) => {
|
||||
emitModels(position.latLng.lat(), position.latLng.lng());
|
||||
}
|
||||
|
||||
const setCoordinatesFromGeocode = (address) => {
|
||||
axios.get(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURI(address)}&key=${process.env.GOOGLE_MAPS_API_KEY}`).then(response => {
|
||||
if (response.data.status !== 'OK') {
|
||||
emits("notFound", props.label);
|
||||
return;
|
||||
}
|
||||
center.value = response.data.results[0].geometry.location;
|
||||
emitModels(center.value.lat, center.value.lng);
|
||||
zoom.value = 13;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
if (props.gpsLatitude && props.gpsLongitude) {
|
||||
center.value.lng = props.gpsLongitude;
|
||||
center.value.lat = props.gpsLatitude
|
||||
} else if (props.label) {
|
||||
setCoordinatesFromGeocode(props.label);
|
||||
} else {
|
||||
console.error("Set either label or gps coordinates.");
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>Tu bude mapa s GPS pointerem</div>
|
||||
<MapWrapper style="width: 100%; height: 500px" :center="center" :zoom="zoom">
|
||||
<Marker :options="{ position: center, title: label, draggable: true }" @dragend="markerDropped"/>
|
||||
</MapWrapper>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup>
|
||||
import FormWrapper from "components/FormWrapper.vue";
|
||||
import {onMounted, reactive} from "vue";
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {setDefaultData} from "src/composables/form/formHandling";
|
||||
import MapGPSInput from "components/inputs/MapGPSInput.vue";
|
||||
import {useQuasar} from "quasar";
|
||||
import {errorObject} from "src/composables/dialog";
|
||||
|
||||
const props = defineProps({
|
||||
poi: {
|
||||
@@ -14,22 +16,63 @@ const props = defineProps({
|
||||
|
||||
const formData = reactive({
|
||||
name: null,
|
||||
gpsLatitude: null,
|
||||
gpsLongitude: null,
|
||||
isPublic: false,
|
||||
});
|
||||
|
||||
const poiNameForMap = ref("");
|
||||
|
||||
onMounted(() => {
|
||||
setDefaultData(formData, props.poi)
|
||||
setDefaultData(formData, props.poi);
|
||||
poiNameForMap.value = props.poi.name;
|
||||
})
|
||||
|
||||
const showResetMapButton = ref(false);
|
||||
const resetMap = () => {
|
||||
formData.gpsLongitude = 0;
|
||||
formData.gpsLatitude = 0;
|
||||
poiNameForMap.value = formData.name;
|
||||
showResetMapButton.value = false;
|
||||
}
|
||||
|
||||
const zoom = ref(10);
|
||||
const $q = useQuasar();
|
||||
const handlePositionNotFound = () => {
|
||||
$q.notify(errorObject(`Lokalita ${formData.name} nenalezena. Nastav pozici ručně.`));
|
||||
zoom.value = 7;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormWrapper v-model="formData">
|
||||
|
||||
<q-input v-model="formData.name" label="Jméno" />
|
||||
<q-checkbox v-model="formData.isPublic" label="Veřejný?" />
|
||||
<q-input
|
||||
class="q-my-lg"
|
||||
outlined
|
||||
standout
|
||||
v-model="formData.name"
|
||||
@change="showResetMapButton=true"
|
||||
label="Jméno bodu"
|
||||
>
|
||||
<template #append v-if="showResetMapButton">
|
||||
<q-btn @click="resetMap" icon="gps_fixed" round flat text-color="green" title="Nastav pozici na mapě"/>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
|
||||
<MapGPSInput
|
||||
v-if="poiNameForMap"
|
||||
:key="poiNameForMap"
|
||||
:label="poiNameForMap"
|
||||
:zoom="zoom"
|
||||
v-model:gpsLatitude="formData.gpsLatitude"
|
||||
v-model:gpsLongitude="formData.gpsLongitude"
|
||||
@not-found="handlePositionNotFound"
|
||||
/>
|
||||
|
||||
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
|
||||
|
||||
<MapGPSInput />
|
||||
|
||||
</FormWrapper>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script setup>
|
||||
import {pointsOfInterest} from "src/composables/objects/poi";
|
||||
|
||||
const props = defineProps({
|
||||
items: {
|
||||
type: Array,
|
||||
@@ -11,6 +9,9 @@ const props = defineProps({
|
||||
<template>
|
||||
<q-list bordered separator>
|
||||
<q-item clickable v-ripple v-for="poi of items" :key="poi">
|
||||
<q-item-section avatar>
|
||||
<q-avatar icon="place" color="primary" text-color="white"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<router-link :to="{name: 'poiDetail', params: {id: poi.id}}">
|
||||
{{ poi.name }}
|
||||
|
||||
Reference in New Issue
Block a user