This commit is contained in:
Michal Kváček
2023-08-26 11:45:50 +02:00
parent ccc8ff22e2
commit dcc3230cbe
18 changed files with 132 additions and 42 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ services:
- ./node_modules:/app/node_modules:delegated - ./node_modules:/app/node_modules:delegated
command: quasar dev command: quasar dev
environment: environment:
VUE_API_URL: "http://localhost:8000" API_URL: "http://localhost:8000"
# nginx: # nginx:
# image: # image:
+29
View File
@@ -0,0 +1,29 @@
<script setup>
import {poi} from "src/composables/objects/poi";
const props = defineProps({
routeBack: {
type: Object,
required: false
},
labelBack: {
type: String,
required: false,
default: "Přejít zpět na detail"
}
});
</script>
<template>
<q-banner class="bg-red text-white">
<template v-slot:avatar>
<q-icon name="warning"/>
</template>
Tato data ti nepatří a proto je nemůžeš měnit.
<template v-slot:action v-if="routeBack">
<q-btn flat color="white" :to="routeBack" :label="labelBack"/>
</template>
</q-banner>
</template>
+1 -1
View File
@@ -55,7 +55,7 @@ const fullwidthBtn = computed(() => $q.screen.width <= $q.screen.sizes.md);
{{ props.saveButton.label }} {{ props.saveButton.label }}
<template v-slot:loading> <template v-slot:loading>
Loading... <q-spinner />
</template> </template>
</q-btn> </q-btn>
</div> </div>
@@ -18,6 +18,15 @@ const props = defineProps({
<template> <template>
<q-list bordered separator> <q-list bordered separator>
<q-item>
<q-item-section avatar>
<q-icon name="flight"/>
</q-item-section>
<q-item-section>
{{ flight.aircraft.callSign }}
</q-item-section>
</q-item>
<q-item> <q-item>
<q-item-section avatar> <q-item-section avatar>
<q-icon name="event"/> <q-icon name="event"/>
+1 -1
View File
@@ -50,7 +50,7 @@ const mapPhotos = computed(() => {
const displayMap = computed(() => { const displayMap = computed(() => {
return ( return (
!!flight.value.gpsTrackUrl || !!flight.value.gpxTrackUrl ||
mapPhotos.value.length > 0 || mapPhotos.value.length > 0 ||
trackWithGPS.value.length > 0 || trackWithGPS.value.length > 0 ||
flight.value.landingAirport.id !== flight.value.takeoffAirport.id flight.value.landingAirport.id !== flight.value.takeoffAirport.id
@@ -7,6 +7,10 @@ const props = defineProps({
flight: { flight: {
type: Object, type: Object,
required: false required: false
},
isRequestPending: {
type: Boolean,
required: true
} }
} }
) )
@@ -16,7 +20,7 @@ const formData = reactive({
</script> </script>
<template> <template>
<FormWrapper v-model="formData" :save-button='{ <FormWrapper v-model="formData" :is-request-pending="isRequestPending" :save-button='{
label: "Nahrát", label: "Nahrát",
icon: "upload", icon: "upload",
align: "left" align: "left"
@@ -31,7 +35,7 @@ const formData = reactive({
label="Vybert fotky k nahrání" label="Vybert fotky k nahrání"
> >
<template #prepend> <template #prepend>
<q-icon name="upload" /> <q-icon name="upload"/>
</template> </template>
</q-file> </q-file>
</FormWrapper> </FormWrapper>
+4 -3
View File
@@ -4,14 +4,15 @@ import {commonProps} from "src/composables/inputs";
import {reactive, watch} from "vue"; import {reactive, watch} from "vue";
import {setDefaultData} from "src/composables/form/formHandling"; import {setDefaultData} from "src/composables/form/formHandling";
import AvatarFromPhoto from "components/AvatarFromPhoto.vue"; import AvatarFromPhoto from "components/AvatarFromPhoto.vue";
import {user as pilot} from "src/composables/objects/user"; import {isRequestPending} from "src/composables/form/props";
const props = defineProps({ const props = defineProps({
user: { user: {
type: Object, type: Object,
required: false, required: false,
default: null default: null
} },
isRequestPending
}); });
const formData = reactive({ const formData = reactive({
avatarImage: null, avatarImage: null,
@@ -25,7 +26,7 @@ watch(() => props.user, (newVal) => {
<template> <template>
<FormWrapper v-model="formData"> <FormWrapper v-model="formData" :is-request-pending="props.isRequestPending">
<div class="text-center"> <div class="text-center">
<AvatarFromPhoto :size="200" :src="user.avatarImageUrl" v-if="user.avatarImageUrl" /> <AvatarFromPhoto :size="200" :src="user.avatarImageUrl" v-if="user.avatarImageUrl" />
+5 -2
View File
@@ -6,13 +6,16 @@ import {commonProps} from "src/composables/inputs";
import RichTextEditor from "components/inputs/RichTextEditor.vue"; import RichTextEditor from "components/inputs/RichTextEditor.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 {isRequestPending} from "src/composables/form/props";
const props = defineProps({ const props = defineProps({
user: { user: {
type: Object, type: Object,
required: false, required: false,
default: null default: null
} },
isRequestPending,
}) })
const formData = reactive({ const formData = reactive({
@@ -26,7 +29,7 @@ watch(() => props.user, (newVal) => {
</script> </script>
<template> <template>
<FormWrapper v-model="formData"> <FormWrapper v-model="formData" :is-request-pending="props.isRequestPending">
<q-list> <q-list>
<q-item v-if="user.publicUsername"> <q-item v-if="user.publicUsername">
+2 -4
View File
@@ -4,13 +4,11 @@ import {FRAGMENT_FLIGHT_FULL_INFO} from "src/graphql/fragments/flight";
export const CREATE_FLIGHT = gql` export const CREATE_FLIGHT = gql`
mutation createFlight($input: CreateFlightInput!) { mutation createFlight($input: CreateFlightInput!) {
createFlight(input: $input) { createFlight(input: $input) {
...flightFullInfo id
} }
} }
${FRAGMENT_FLIGHT_FULL_INFO}
` `
// ^^ nesmim se tu ptat na aircraft, jinak mi to upadne
export const EDIT_FLIGHT = gql` export const EDIT_FLIGHT = gql`
mutation editFlight($id: Int!, $input: EditFlightInput!) { mutation editFlight($id: Int!, $input: EditFlightInput!) {
editFlight(id: $id, input: $input) { editFlight(id: $id, input: $input) {
+11 -3
View File
@@ -20,13 +20,21 @@
<q-icon name="person" v-else/> <q-icon name="person" v-else/>
<q-menu auto-close> <q-menu auto-close>
<q-list> <q-list style="min-width: 200px">
<q-item clickable :to="{name: 'profile'}"> <q-item clickable :to="{name: 'profile'}">
<q-item-section avatar>
<q-icon name="person"/>
</q-item-section>
<q-item-section>Osobní nastavení</q-item-section> <q-item-section>Osobní nastavení</q-item-section>
</q-item> </q-item>
<q-item clickable @click="logout"> <q-item clickable @click="logout" class="q-mt-lg">
<q-item-section>Odhlásit</q-item-section> <q-item-section avatar>
<q-icon name="logout"/>
</q-item-section>
<q-item-section>
Odhlásit
</q-item-section>
</q-item> </q-item>
</q-list> </q-list>
+7 -7
View File
@@ -13,11 +13,11 @@ useAppStore().setPageTitle("Profil");
const loggedUser = storeToRefs(useUserStore()).user; const loggedUser = storeToRefs(useUserStore()).user;
const $q = useQuasar(); const $q = useQuasar();
const handleSave = (formData) => { const handleSave = async (formData) => {
useUserStore().update(loggedUser.id, formData).then((user) => { await useUserStore().update(formData);
$q.notify(successObject("Uspesne upraveno")); $q.notify(successObject("Úspěšně uloženo"));
});
}; };
const isRequestPending = storeToRefs(useUserStore()).isMutationRunning;
const openedTab = ref("profile"); const openedTab = ref("profile");
</script> </script>
@@ -30,15 +30,15 @@ const openedTab = ref("profile");
<q-tab-panels v-model="openedTab"> <q-tab-panels v-model="openedTab">
<q-tab-panel name="profile"> <q-tab-panel name="profile">
<ProfileImagesForm :user="loggedUser" @save="handleSave"/> <ProfileImagesForm :user="loggedUser" :isRequestPending="isRequestPending" @save="handleSave"/>
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="changePassword"> <q-tab-panel name="changePassword">
<ChangePasswordForm @save="handleSave" /> <ChangePasswordForm @save="handleSave" :isRequestPending="isRequestPending" />
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="publicProfile"> <q-tab-panel name="publicProfile">
<PublicProfileInfo @save="handleSave" :user="loggedUser" /> <PublicProfileInfo @save="handleSave" :isRequestPending="isRequestPending" :user="loggedUser" />
</q-tab-panel> </q-tab-panel>
</q-tab-panels> </q-tab-panels>
@@ -10,6 +10,7 @@ import FAB from "components/FAB.vue";
import {useAppStore} from "stores/App"; import {useAppStore} from "stores/App";
import FlightsList from "components/flights/FlightsList.vue"; import FlightsList from "components/flights/FlightsList.vue";
import {copilot} from "src/composables/objects/copilot"; import {copilot} from "src/composables/objects/copilot";
import {flight} from "src/composables/objects/flight";
const aircraftId = parseInt(useRoute().params.id); const aircraftId = parseInt(useRoute().params.id);
onMounted(() => { onMounted(() => {
@@ -44,6 +45,21 @@ watch(aircraft, (newVal) => {
<q-card class="q-mx-md q-my-md flight-detail-card"> <q-card class="q-mx-md q-my-md flight-detail-card">
<q-img :src="aircraft.photoUrl" v-if="aircraft.photoUrl" /> <q-img :src="aircraft.photoUrl" v-if="aircraft.photoUrl" />
<q-card-actions class="q-my-md">
<div class="col-grow">
<q-btn
flat
:to='{name: "aircraftEdit", params: {id: aircraft.id}}'
icon="edit"
text-color="primary"
label="Upravit"
/>
</div>
<q-btn @click="confirmDelete(aircraft.id)" icon="delete" flat text-color="red" label="Smazat"/>
</q-card-actions>
<q-card-section> <q-card-section>
<q-list> <q-list>
<q-item v-if="aircraft.manufacturer"> <q-item v-if="aircraft.manufacturer">
@@ -55,9 +71,10 @@ watch(aircraft, (newVal) => {
</q-list> </q-list>
</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 :to='{name: "aircraftEdit", params: {id: aircraft.id}}' icon="edit" text-color="primary" rounded flat/>-->
</q-card-actions> <!-- <q-btn @click="confirmDelete(aircraft.id)" icon="delete" text-color="red" rounded flat/>-->
<!-- </q-card-actions>-->
</q-card> </q-card>
<q-card class="flight-detail-card" flat> <q-card class="flight-detail-card" flat>
@@ -68,7 +85,7 @@ watch(aircraft, (newVal) => {
<FlightsList :items="aircraft.flights" v-else/> <FlightsList :items="aircraft.flights" v-else/>
</q-card> </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>
@@ -55,6 +55,8 @@ const handleFlightEdit = (data) => {
}) })
} }
const isRequestPending = storeToRefs(useFlightsStore()).isMutationRunning;
const handlePhotosUpload = (data) => { const handlePhotosUpload = (data) => {
const promises = []; const promises = [];
for (const photo of data.photos) { for (const photo of data.photos) {
@@ -134,6 +136,7 @@ const openPhotoEdit = (photo) => {
<q-tab-panel name="photos"> <q-tab-panel name="photos">
<FlightFormPhotos <FlightFormPhotos
:flight="flight" :flight="flight"
:is-request-pending="isRequestPending"
@save="handlePhotosUpload" @save="handlePhotosUpload"
/> />
@@ -144,6 +147,8 @@ const openPhotoEdit = (photo) => {
@save="handlePhotoUpdate" @save="handlePhotoUpdate"
/> />
Tu nejsou videt fotky po nahrani
<PhotosList <PhotosList
:photos="flight.photos" :photos="flight.photos"
@edit="openPhotoEdit" @edit="openPhotoEdit"
@@ -10,6 +10,8 @@ import {successObject} from "src/composables/dialog";
import {usePointsOfInterestTypeStore} from "stores/PointOfInterestType"; import {usePointsOfInterestTypeStore} from "stores/PointOfInterestType";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
import POIForm from "components/poi/POIForm.vue"; import POIForm from "components/poi/POIForm.vue";
import {useUserStore} from "stores/User";
import AccessDeniedBanner from "components/AccessDeniedBanner.vue";
const poiId = parseInt(useRoute().params.id); const poiId = parseInt(useRoute().params.id);
@@ -27,6 +29,7 @@ onMounted(() => {
}) })
const poiTypes = storeToRefs(usePointsOfInterestTypeStore()).items; const poiTypes = storeToRefs(usePointsOfInterestTypeStore()).items;
const loggedUser = storeToRefs(useUserStore()).user;
const $q = useQuasar(); const $q = useQuasar();
const router = useRouter(); const router = useRouter();
@@ -40,6 +43,6 @@ const handleSave = (data) => {
<template> <template>
<POIEditSkeleton v-if="loading"/> <POIEditSkeleton v-if="loading"/>
<AccessDeniedBanner v-else-if="poi.createdbyId !== loggedUser.id" :route-back="{name: 'poiDetail', params: {id: poiId}}"/>
<POIForm :poi="poi" :types="poiTypes" v-else @save="handleSave"/> <POIForm :poi="poi" :types="poiTypes" v-else @save="handleSave"/>
</template> </template>
@@ -29,7 +29,7 @@ onMounted(() => {
<POIDetailSkeleton v-if="loading || !poi"/> <POIDetailSkeleton v-if="loading || !poi"/>
<template v-else> <template v-else>
<article v-html="poi.description" /> <article v-html="poi.description" class="q-my-lg" />
<EmbeddedMap <EmbeddedMap
v-if="poi.gpsLatitude && poi.gpsLongitude" v-if="poi.gpsLatitude && poi.gpsLongitude"
@@ -51,7 +51,7 @@ onMounted(() => {
Pokud přidáš toto místo k nějaké fotce, zobrazí se tu. Pokud přidáš toto místo k nějaké fotce, zobrazí se tu.
</BasicInfoBanner> </BasicInfoBanner>
<FlightsList :items="poi.flights" /> <FlightsList :items="poi.flights" v-if="poi.flights.length > 0 && loggedUser.id === poi.createdById"/>
<FAB v-if="poi.createdById === loggedUser.id" :router-link="{name: 'poiEdit', params:{id: poi.id}}" mode="edit"/> <FAB v-if="poi.createdById === loggedUser.id" :router-link="{name: 'poiEdit', params:{id: poi.id}}" mode="edit"/>
</template> </template>
+8 -5
View File
@@ -6,7 +6,8 @@ import {deleteFromItems, updateInItems} from "stores/helpers";
export const useAircraftsStore = defineStore('Aircrafts', { export const useAircraftsStore = defineStore('Aircrafts', {
state: () => ({ state: () => ({
items: [] items: [],
isMutationRunning: false
}), }),
getters: { getters: {
@@ -15,18 +16,19 @@ export const useAircraftsStore = defineStore('Aircrafts', {
actions: { actions: {
fetchDetail(id) { fetchDetail(id) {
console.log(id);
return apolloWrapper return apolloWrapper
.query(AIRCRAFT_DETAIL, {id}) .query(AIRCRAFT_DETAIL, {id})
.then((result) => result.data.aircraft); .then((result) => result.data.aircraft);
}, },
create(formData) { create(formData) {
delete formData['id']; delete formData['id'];
this.isMutationRunning = true;
return apolloWrapper.mutate(CREATE_AIRCRAFT, {input: formData}).then(({data}) => { return apolloWrapper.mutate(CREATE_AIRCRAFT, {input: formData}).then(({data}) => {
this.items.push(data.createAircraft); // this.items.push(data.createAircraft);
return data.createAircraft; return data.createAircraft;
}) }).finally(() => this.isMutationRunning = false);
}, },
delete(id) { delete(id) {
return apolloWrapper.mutate(DELETE_AIRCRAFT, {id}).then(({data}) => { return apolloWrapper.mutate(DELETE_AIRCRAFT, {id}).then(({data}) => {
@@ -36,11 +38,12 @@ export const useAircraftsStore = defineStore('Aircrafts', {
}); });
}, },
update(id, formData) { update(id, formData) {
this.isMutationRunning = true;
return apolloWrapper.mutate(EDIT_AIRCRAFT, {id: id, input: formData}).then(({data}) => { return apolloWrapper.mutate(EDIT_AIRCRAFT, {id: id, input: formData}).then(({data}) => {
const aircraft = data.editAircraft; const aircraft = data.editAircraft;
updateInItems(this.items, 'id', aircraft); updateInItems(this.items, 'id', aircraft);
return aircraft; return aircraft;
}); }).finally(() => this.isMutationRunning = false);
}, },
fetch() { fetch() {
return apolloWrapper return apolloWrapper
+10 -3
View File
@@ -6,7 +6,8 @@ import {updateInItems} from "stores/helpers";
export const useFlightsStore = defineStore('Flights', { export const useFlightsStore = defineStore('Flights', {
state: () => ({ state: () => ({
items: [] items: [],
isMutationRunning: false
}), }),
getters: { getters: {
@@ -19,18 +20,24 @@ export const useFlightsStore = defineStore('Flights', {
create(formData) { create(formData) {
delete formData['id']; delete formData['id'];
return apolloWrapper.mutate(CREATE_FLIGHT, {input: formData}).then(({data}) => { return apolloWrapper.mutate(CREATE_FLIGHT, {input: formData}).then(({data}) => {
this.items.push(data.createFlight); const flightId = data.createFlight.id;
// pitomy ohack kvuli tomu, ze se mi nedari dostat z API rovnou informace o nove vytvorenem letadle
this.fetchDetail(flightId).then(flight => {
this.items.push(flight);
})
return data.createFlight; return data.createFlight;
}) })
}, },
update(id, formData) { update(id, formData) {
this.isMutationRunning = true;
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;
updateInItems(this.items, "id", flight); updateInItems(this.items, "id", flight);
return flight; return flight;
}); }).finally(() => this.isMutationRunning = false);
}, },
fetchDetail(id, username = null) { fetchDetail(id, username = null) {
if (typeof id !== "number") { if (typeof id !== "number") {
+6 -3
View File
@@ -8,15 +8,18 @@ export const useUserStore = defineStore('LoggedUser', {
state: () => ({ state: () => ({
user: {}, user: {},
isRefreshing: false, isRefreshing: false,
isMutationRunning: false,
}), }),
actions: { actions: {
update(id, formData) { update(formData) {
apolloWrapper.mutate(EDIT_USER, {id, input: formData}).then((response) => { this.isMutationRunning = true;
apolloWrapper.mutate(EDIT_USER, {input: formData}).then((response) => {
const user = response.data?.editLoggedUser; const user = response.data?.editLoggedUser;
this.user = user; this.user = user;
return user; return user;
}); }).finally(() => this.isMutationRunning = false);
}, },
fetchLoggedUserDetail() { fetchLoggedUserDetail() {