Sprava eventu a organizaci

This commit is contained in:
Michal Kváček
2023-09-20 13:26:13 +02:00
parent 6fbaef4a1e
commit 8c3c5ee48d
42 changed files with 624 additions and 56 deletions
@@ -2,6 +2,8 @@
import {aircraft} from "src/composables/objects/aircraft";
import {confirmObject} from "src/composables/dialog";
import {useQuasar} from "quasar";
import {storeToRefs} from "pinia";
import {useUserStore} from "stores/User";
const props = defineProps({
aircraft: {
@@ -11,9 +13,11 @@ const props = defineProps({
});
const emits = defineEmits(["delete"]);
const $q = useQuasar();
const loggedUser = storeToRefs(useUserStore()).user;
const confirmDelete = (aircraftId) => {
$q.dialog(confirmObject('Opravdu chceš smazat toto letadlo?'))
.onOk(() => {
@@ -26,7 +30,7 @@ const confirmDelete = (aircraftId) => {
<q-card class="q-mx-md q-my-md flight-detail-card">
<q-img :src="aircraft.photoUrl" v-if="aircraft.photoUrl"/>
<q-card-actions class="q-my-md">
<q-card-actions class="q-my-md" v-if="aircraft.createdById === loggedUser.id">
<div class="col-grow">
<q-btn
flat
@@ -41,6 +45,11 @@ const confirmDelete = (aircraftId) => {
<q-card-section>
<q-list>
<q-item v-if="aircraft.organization">
Provozovatel: {{ aircraft.organization.name }}
</q-item>
<q-item v-if="aircraft.manufacturer">
Výrobce: {{ aircraft.manufacturer }}
</q-item>
+30 -11
View File
@@ -5,6 +5,7 @@ import {setDefaultData} from "src/composables/form/formHandling";
import {defaultValues, isRequestPending} from "src/composables/form/props";
import {isNumber, isRequired, maxLength, minLength} from "src/composables/form/rules";
import {commonProps} from "src/composables/inputs";
import ComboboxInput from "components/inputs/ComboboxInput.vue";
const formData = reactive({
id: null,
@@ -14,12 +15,16 @@ const formData = reactive({
manufacturer: "",
description: "",
photo: null,
organizationId: null
organization: null
});
const props = defineProps({
defaultValues,
isRequestPending
isRequestPending,
organizations: {
type: Array,
required: true,
}
});
const rules = {
@@ -28,8 +33,12 @@ const rules = {
};
onMounted(() => {
if (Object.keys(props.defaultValues).length > 0)
if (Object.keys(props.defaultValues).length > 0) {
setDefaultData(formData, props.defaultValues);
if (props.defaultValues.organization) {
formData.organization = {id: props.defaultValues.organization.id, name: props.defaultValues.organization.name};
}
}
})
</script>
@@ -45,6 +54,23 @@ onMounted(() => {
v-bind="commonProps"
/>
<q-input
type="number"
v-model.number="formData.seats"
label="Počet míst (včetně pilota)"
min="1"
:rules="rules.seats"
v-bind="commonProps"
/>
<ComboboxInput
:options="organizations"
label="Organizace"
v-model="formData.organization"
v-bind="commonProps"
hint="Vyber provozovatele letadla. Tím se stane dostupné i pro ostatní členy vybrané organizace."
/>
<q-input
type="text"
v-model="formData.manufacturer"
@@ -58,14 +84,7 @@ onMounted(() => {
v-bind="commonProps"
/>
<q-input
type="number"
v-model.number="formData.seats"
label="Počet míst (včetně pilota)"
min="1"
:rules="rules.seats"
v-bind="commonProps"
/>
<q-input
type="textarea"
@@ -36,7 +36,11 @@ useAppStore().setPageTitle("Letadla");
({{ aircraft.manufacturer }})
</template>
</q-item-label>
<q-item-label caption v-if="aircraft.organization">
Provozovatel: {{ aircraft.organization.name }}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</template>
@@ -8,7 +8,7 @@ const props = defineProps({
</script>
<template>
<div style="height: 80px">
<div style="height: 60px">
<q-avatar
v-for="(avatar, idx) in avatars"
:key="idx"
@@ -16,7 +16,6 @@ const props = defineProps({
:style="`left: ${(idx+1) * 20}px`"
>
<img :src="avatar"/>
<!-- <q-icon name="flight" v-else />-->
</q-avatar>
</div>
</template>
+6 -4
View File
@@ -11,14 +11,16 @@ const props = defineProps({
<template>
<q-list bordered separator>
<q-item clickable v-ripple v-for="pilot of items" :key="pilot">
<q-item
clickable
:to="{name: 'copilotDetail', params: {id: pilot.id }}"
v-ripple
v-for="pilot of items" :key="pilot">
<q-item-section avatar>
<q-avatar icon="person" color="primary" text-color="white"/>
</q-item-section>
<q-item-section>
<router-link :to="{name: 'copilotDetail', params: {id: pilot.id }}">
{{ pilot.name }}
</router-link>
{{ pilot.name }}
</q-item-section>
</q-item>
</q-list>
+7 -4
View File
@@ -1,6 +1,5 @@
<script setup>
import AvatarsGroup from "components/AvatarsGroup.vue";
import AvatarsGroup from "components/avatars/AvatarsGroup.vue";
const props = defineProps({
items: {
@@ -14,10 +13,14 @@ const getCoverPhotos = (item) => item.flights.filter(f => !!f.coverPhoto).map(f
<template>
<q-list bordered separator>
<q-item clickable v-ripple v-for="event of items" :key="event" :to="{name: 'eventDetail', params: {id: event.id }}">
<q-item
clickable
:to="{name: 'eventDetail', params: {id: event.id }}"
v-ripple
v-for="event of items" :key="event"
>
<q-item-section>
<q-item-label>
<q-avatar icon="event" color="primary" text-color="white" v-if="event.flights.length === 0"/>
<AvatarsGroup :avatars="getCoverPhotos(event)" v-else/>
+7 -2
View File
@@ -1,7 +1,7 @@
<script setup>
import {minutesToHumanReadable} from "src/composables/datetimeUtils";
import {flightName} from "src/composables/objects/flight";
import AvatarFromPhoto from "components/AvatarFromPhoto.vue";
import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
const props = defineProps({
items: {
@@ -28,7 +28,7 @@ const props = defineProps({
v-for="flight of items" :key="flight"
ripple
clickable
:to="{name: routeDetail.name, params:{...routeDetail.params, id: flight.id}}"
:to="{name: routeDetail.name, params: {...routeDetail.params, id: flight.id}}"
>
<q-item-section avatar>
<AvatarFromPhoto :size="120" v-if="flight.coverPhoto" :src="flight.coverPhoto.thumbnailUrl"/>
@@ -49,6 +49,11 @@ const props = defineProps({
<q-icon name="flight"/>
{{ flight.aircraft.callSign }}
</div>
<div class="text-secondary" v-if="flight.event">
<q-icon name="event" />
{{ flight.event.name }}
</div>
</q-item-section>
<q-item-section side top v-if="flight.isPublic && displayVisibilityInfo">
@@ -0,0 +1,9 @@
<script setup>
import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
</script>
<template>
<q-card class="flight-detail-card">
<BasicListSkeleton :items-count-min="1" :items-count-max="5"/>
</q-card>
</template>
@@ -0,0 +1,5 @@
<script setup></script>
<template>
<em>Copilot skeleton</em>
</template>
@@ -0,0 +1,43 @@
<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";
import {isRequestPending} from "src/composables/form/props";
import {isRequired} from "src/composables/form/rules";
import RichTextEditor from "components/inputs/RichTextEditor.vue";
const props = defineProps({
event: {
type: Object
},
isRequestPending
})
const formData = reactive({
name: null,
});
const rules = {
name: [isRequired]
}
onMounted(() => {
if (props.event) {
setDefaultData(formData, props.event);
}
})
</script>
<template>
<FormWrapper v-model="formData" :is-request-pending="props.isRequestPending">
<q-input
v-bind="commonProps"
label="Jméno"
:rules="rules.name"
v-model="formData.name"
/>
</FormWrapper>
</template>
@@ -0,0 +1,36 @@
<script setup>
const props = defineProps({
items: {
type: Array,
required: true
},
myOrganizationsIds: {
type: Array,
required: false
}
})
</script>
<template>
<q-list bordered separator>
<q-item
clickable
:to="{name: 'organizationDetail', params: {id: organization.id }}"
v-ripple
v-for="organization of items"
:key="organization"
>
<q-item-section avatar>
<q-avatar icon="groups" color="primary" text-color="white"/>
</q-item-section>
<q-item-section>
<q-item-label>
{{ organization.name }}
<q-icon name="star" color="gold" v-if="myOrganizationsIds.includes(organization.id)"/>
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</template>
+1 -1
View File
@@ -1,7 +1,7 @@
<script setup>
import {user as pilot} from "src/composables/objects/user";
import {FORMAT_DATE, formatDatetime} from "src/composables/datetimeUtils";
import AvatarFromPhoto from "components/AvatarFromPhoto.vue";
import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
</script>
<template>
+1 -1
View File
@@ -3,7 +3,7 @@ import FormWrapper from "components/FormWrapper.vue";
import {commonProps} from "src/composables/inputs";
import {reactive, watch} from "vue";
import {setDefaultData} from "src/composables/form/formHandling";
import AvatarFromPhoto from "components/AvatarFromPhoto.vue";
import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
import {isRequestPending} from "src/composables/form/props";
const props = defineProps({
+2 -2
View File
@@ -18,9 +18,9 @@ export const fetchEvent = (eventId, username) => {
export const events = storeToRefs(useEventsStore()).items;
export const fetchEvents = (username = null) => {
export const fetchEvents = () => {
loading.value = true;
useEventsStore().fetch(username).finally(() => {
useEventsStore().fetch().finally(() => {
loading.value = false;
});
};
+43
View File
@@ -0,0 +1,43 @@
import {computed, ref} from "vue";
import {storeToRefs} from "pinia";
import {useOrganizationsStore} from "stores/Organizations";
import {useUserStore} from "stores/User";
import {successObject} from "src/composables/dialog";
export const loading = ref(true);
export const organization = ref(null);
export const fetchOrganization = (organizationId) => {
loading.value = true;
return useOrganizationsStore().fetchDetail(parseInt(organizationId))
.then(resp => {
console.log(resp);
organization.value = resp;
return resp;
})
.finally(() => loading.value = false);
}
export const organizations = storeToRefs(useOrganizationsStore()).items;
export const fetchOrganizations = () => {
loading.value = true;
return useOrganizationsStore().fetch().finally(() => loading.value = false);
}
export const isRequestPending = storeToRefs(useOrganizationsStore()).isMutationRunning;
const loggedUser = storeToRefs(useUserStore()).user;
export const myOrganizationIds = computed(() => loggedUser.value.organizations?.map(o => o.id) || []);
export const addToOrganization = async () => {
useOrganizationsStore().addUserToOrganization(organizationId).then((data) => {
organization.value.users = data.users;
useUserStore().fetchLoggedUserDetail();
});
};
export const removeFromOrganization = async () => {
useOrganizationsStore().removeUserFromOrganization(organizationId).then((data) => {
organization.value.users = data.users;
useUserStore().fetchLoggedUserDetail();
});
}
+11
View File
@@ -9,6 +9,12 @@ fragment aircraftFullInfo on Aircraft {
manufacturer
photoUrl
seats
createdById
organization {
id
name
}
}
`;
@@ -17,5 +23,10 @@ fragment aircraftBasicInfo on Aircraft {
id
callSign
manufacturer
organization {
id
name
}
}
`;
+4
View File
@@ -21,6 +21,10 @@ fragment flightListInfo on Flight {
}
}
event {
name
}
takeoffAirport {
...airportInfo
}
+31
View File
@@ -0,0 +1,31 @@
import gql from "graphql-tag";
import {FRAGMENT_AIRCRAFT_BASIC_INFO} from "src/graphql/fragments/aircraft";
export const FRAGMENT_ORGANIZATION_LIST_INFO = gql`
fragment organizationListInfo on Organization {
id
name
}
`;
export const FRAGMENT_ORGANIZATION_FULL_INFO = gql`
fragment organizationFullInfo on Organization {
id
name
createdById
users {
id
name
email
avatarImageUrl
}
aircrafts {
...aircraftBasicInfo
}
}
${FRAGMENT_AIRCRAFT_BASIC_INFO}
`;
// TODO: na info o userovi pouzit fragment - stejne jako v mutaci na pridani/odebrani usera z organizace
+54
View File
@@ -0,0 +1,54 @@
import gql from "graphql-tag";
import {FRAGMENT_ORGANIZATION_FULL_INFO} from "src/graphql/fragments/organization";
export const CREATE_ORGANIZATION = gql`
mutation createOrganization($input: CreateOrganizationInput!) {
createOrganization(input: $input) {
...organizationFullInfo
}
}
${FRAGMENT_ORGANIZATION_FULL_INFO}
`;
export const EDIT_ORGANIZATION = gql`
mutation editOrganization($id: Int!, $input: EditOrganizationInput!) {
editOrganization(id: $id, input: $input) {
...organizationFullInfo
}
}
${FRAGMENT_ORGANIZATION_FULL_INFO}
`;
export const ADD_USER_TO_ORGANIZATION = gql`
mutation addToOrganization($organizationId: Int!) {
addToOrganization(organizationId: $organizationId) {
users {
id
name
email
avatarImageUrl
}
}
}`;
export const REMOVE_USER_FROM_ORGANIZATION = gql`
mutation removeFromOrganization($organizationId: Int!) {
removeFromOrganization(organizationId: $organizationId) {
users {
id
name
email
avatarImageUrl
}
}
}`;
// export const DELETE_ORGANIZATION = gql`
// mutation deleteOrganization($id: Int!) {
// deleteOrganization(id: $id) {
// id
// }
// }
// `;
+22
View File
@@ -0,0 +1,22 @@
import gql from "graphql-tag";
import {FRAGMENT_ORGANIZATION_FULL_INFO, FRAGMENT_ORGANIZATION_LIST_INFO} from "src/graphql/fragments/organization";
export const ORGANIZATIONS_LIST = gql`
query organizationsList {
organizations {
...organizationListInfo
}
}
${FRAGMENT_ORGANIZATION_LIST_INFO}
`;
export const ORGANIZATION_DETAIL = gql`
query organization($id: Int!) {
organization(id: $id) {
...organizationFullInfo
}
}
${FRAGMENT_ORGANIZATION_FULL_INFO}
`;
+5
View File
@@ -16,6 +16,11 @@ export const LOGGED_USER_DETAIL = gql`
query loggedUserDetail {
loggedUser {
...userFullInfo
organizations {
id
name
}
}
}
+9 -7
View File
@@ -82,7 +82,7 @@ import {storeToRefs} from "pinia";
import BreadcrumbsNavigation from "components/navigation/BreadcrumbsNavigation.vue";
import {useAppStore} from "stores/App";
import {useMeta, useQuasar} from "quasar";
import AvatarFromPhoto from "components/AvatarFromPhoto.vue";
import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
import {useRouter} from "vue-router";
import {successObject} from "src/composables/dialog";
@@ -97,17 +97,12 @@ const linksList = [
icon: 'airplane_ticket',
to: {name: "addFlight"}
},
{separator: true},
{
title: 'Moje lety',
icon: "flight_takeoff",
to: {name: "myFlights"}
},
{separator: true},
{
title: 'Události',
icon: 'event',
to: {name: "events"}
},
{
title: 'Letadla',
icon: "connecting_airports",
@@ -124,11 +119,18 @@ const linksList = [
to: {name: "copilots"}
},
{separator: true},
{
title: 'Události',
icon: 'event',
to: {name: "events"}
},
{separator: true},
{
title: 'Organizace',
icon: "groups",
to: {name: "organizations"}
},
{separator: true},
]
const leftDrawerOpen = ref(false)
+1 -1
View File
@@ -37,7 +37,7 @@ import {onMounted} from "vue";
import {useAppStore} from "stores/App";
import {useMeta} from "quasar";
import BreadcrumbsNavigation from "components/navigation/BreadcrumbsNavigation.vue";
import AvatarFromPhoto from "components/AvatarFromPhoto.vue";
import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
const {pageTitle, breadcrumbs} = storeToRefs(useAppStore());
const loggedUser = storeToRefs(useUserStore()).user;
@@ -9,6 +9,10 @@ import {loading, aircraft, fetchAircraft} from "src/composables/objects/aircraft
import {useAppStore} from "stores/App";
import FlightsList from "components/flights/FlightsList.vue";
import AircraftDetailCard from "components/aircrafts/AircraftDetailCard.vue";
import {poi} from "src/composables/objects/poi";
import AccessDeniedBanner from "components/banners/AccessDeniedBanner.vue";
import {storeToRefs} from "pinia";
import {useUserStore} from "stores/User";
const $q = useQuasar();
const $router = useRouter();
+5 -1
View File
@@ -19,7 +19,11 @@ onMounted(() => {
:route-add="{name: 'addAircraft'}"
label-add="Vytvořit letadlo"
>
Aktuálně nemáš přidané žádné letadlo.
Aktuálně nemáš přidané žádné letadlo. Přidej se k existující organizaci nebo si sem ulož vlastní letadlo.
<br>
Letadlo můžeš založit i během ukládání informace k tvému prvnímu letu. A pak si ho tu jen doupravit.
</BasicInfoBanner>
<AircraftsList
@@ -8,11 +8,16 @@ import AircraftEditSkeleton from "components/aircrafts/AircraftEditSkeleton.vue"
import {successObject} from "src/composables/dialog";
import {aircraft, fetchAircraft, loading, isRequestPending} from "src/composables/objects/aircraft";
import {useAppStore} from "stores/App";
import {fetchOrganizations, organizations} from "src/composables/objects/organization";
import AccessDeniedBanner from "components/banners/AccessDeniedBanner.vue";
import {storeToRefs} from "pinia";
import {useUserStore} from "stores/User";
const $router = useRouter();
const $q = useQuasar();
const aircraftId = parseInt(useRoute().params.id);
const loggedUser = storeToRefs(useUserStore()).user;
const handleSave = (formData) => {
useAircraftsStore().update(aircraftId, formData).then(() => {
@@ -29,18 +34,24 @@ onMounted(() => {
{to: {name: "aircraftDetail", params: {id: aircraftId}}, label: aircraft.value.callSign},
{label: "Editace"}
])
})
});
});
</script>
<template>
<AircraftEditSkeleton v-if="loading"/>
<AccessDeniedBanner
v-else-if="aircraft.createdById !== loggedUser.id"
:route-back="{name: 'aircraftDetail', params: {id: aircraftId}}"
/>
<q-card v-else class="q-mx-md">
<q-card-section>
<AircraftForm
@save="handleSave"
:default-values="aircraft"
:organizations="loggedUser.organizations"
:is-request-pending="isRequestPending"
/>
</q-card-section>
@@ -3,13 +3,17 @@ import AircraftForm from "components/aircrafts/AircraftForm.vue";
import {useAircraftsStore} from "stores/Aircrafts";
import {useRouter} from "vue-router";
import {useQuasar} from "quasar";
import {ref} from "vue";
import {useAppStore} from "stores/App";
import {successObject} from "src/composables/dialog";
import {isRequestPending} from "src/composables/objects/aircraft";
import {fetchOrganizations, organizations} from "src/composables/objects/organization";
import {onMounted} from "vue";
import {storeToRefs} from "pinia";
import {useUserStore} from "stores/User";
const $q = useQuasar();
const router = useRouter();
const loggedUser = storeToRefs(useUserStore()).user;
const handleSave = (formData) => {
useAircraftsStore().create(formData).then(() => {
@@ -18,17 +22,24 @@ const handleSave = (formData) => {
});
};
useAppStore().setPageTitle("Nové letadlo");
useAppStore().setBreadcrumbs([
{to: {name: "aircrafts"}, label: "Letadla"},
{label: "Nové"}
]);
onMounted(() => {
fetchOrganizations();
useAppStore().setPageTitle("Nové letadlo");
useAppStore().setBreadcrumbs([
{to: {name: "aircrafts"}, label: "Letadla"},
{label: "Nové"}
]);
});
</script>
<template>
<q-card class="q-mx-md q-my-md">
<q-card-section>
<AircraftForm @save="handleSave" :is-request-pending="isRequestPending"/>
<AircraftForm
:is-request-pending="isRequestPending"
:organizations="loggedUser.organizations"
@save="handleSave"
/>
</q-card-section>
</q-card>
</template>
@@ -20,6 +20,8 @@ onMounted(() => {
v-else-if="!loading && copilots.length === 0"
:router-add="{name: 'addCopilot'}"
>
V tomto seznamu budou uvedeni všichni, kteří s tebou absolvovali nějaký tvůj let.
<br>
Zatím tu není nikdo. Přidej si let s někým nebo kopilota vytvoř ručně tu.
</BasicInfoBanner>
+6 -1
View File
@@ -21,7 +21,12 @@ useAppStore().setPageTitle("Letecké události")
:route-add="{name: 'addEvent'}"
label-add="Přidat"
>
Nemáš tu uloženou žádnou událost. Události slouží ke sloučení letů z jedné akce/výletu do přehledné skupiny.
Události slouží ke sloučení letů z jedné akce/výletu do přehledné skupiny. Aktuálně tu nemáš vytvořenou žádnou
událost, ke které bys mohl napárovat svoje lety.
<br>
Událost můžeš vytvořit i v rámci uložení letu.
</BasicInfoBanner>
<EventsList
+1 -3
View File
@@ -37,7 +37,7 @@ const airports = storeToRefs(useAirportsStore()).items;
const copilots = storeToRefs(useCopilotsStore()).items;
const formStep = ref("");
const formStep = ref("basic");
watch(() => $route.params.step, (val) => formStep.value = val);
onMounted(() => {
@@ -81,8 +81,6 @@ const handlePhotosUpload = (data) => {
}
Promise.allSettled(promises).then((values) => {
console.log(values.map(v => v.status));
const success = values.filter(v => v.status === 'fulfilled');
const failed = values.filter(v => v.status === 'rejected'); // TODO: nebo vse - success?
+1 -1
View File
@@ -46,7 +46,7 @@ const handleSave = (formData) => {
<template>
<FlightFormSteps :disabled="['copilot', 'track', 'photos', 'notes']"/>
<FlightEditSkeleton v-if="loading"/>
<FlightEditSkeleton step="basic" v-if="loading"/>
<template v-else>
<FlightFormBasic
@save="handleSave"
@@ -0,0 +1,45 @@
<script setup>
import {onMounted} from "vue";
import {loading, organization, fetchOrganization, isRequestPending} from "src/composables/objects/organization";
import {useAppStore} from "stores/App";
import {useRoute, useRouter} from "vue-router";
import {useOrganizationsStore} from "stores/Organizations";
import {successObject} from "src/composables/dialog";
import {useQuasar} from "quasar";
import OrganizationEditSkeleton from "components/organization/OrganizationEditSkeleton.vue";
import OrganizationForm from "components/organization/OrganizationForm.vue";
const organizationId = parseInt(useRoute().params.id);
onMounted(() => {
fetchOrganization(organizationId).then(() => {
useAppStore().setPageTitle(`${organization.value.name} - editace`);
useAppStore().setBreadcrumbs([
{to: {name: "organizations"}, label: "Organizace"},
{to: {name: "organizationDetail", params: {id: organizationId}}, label: organization.value.name},
{label: "Editace"}
])
})
});
const $q = useQuasar();
const $router = useRouter();
const handleSave = (formData) => {
useOrganizationsStore().update(organizationId, formData).then(() => {
$q.notify(successObject("Organizace úspěšně upravena"));
$router.push({name: "organizationDetail", params: {id: organizationId}});
});
}
</script>
<template>
<OrganizationEditSkeleton v-if="loading"/>
<OrganizationForm
v-else
:organization="organization"
:is-request-pending="isRequestPending"
@save="handleSave"
/>
</template>
@@ -0,0 +1,28 @@
<script setup>
import {useRouter} from "vue-router";
import {useQuasar} from "quasar";
import {successObject} from "src/composables/dialog";
import {useOrganizationsStore} from "stores/Organizations";
import {useAppStore} from "stores/App";
import {isRequestPending} from "src/composables/objects/organization";
import OrganizationForm from "components/organizations/OrganizationForm.vue";
const $q = useQuasar();
const router = useRouter();
const handleSave = (formData) => {
useOrganizationsStore().create(formData).then(() => {
$q.notify(successObject("Organizace úspěšně vytvořena"));
router.push({name: "organizations"});
});
};
useAppStore().setPageTitle("Nová organizace");
useAppStore().setBreadcrumbs([
{to: {name: "organizations"}, label: "Organizace"},
{label: "Nová"}
]);
</script>
<template>
<OrganizationForm @save="handleSave" :is-request-pending="isRequestPending"/>
</template>
@@ -0,0 +1,76 @@
<script setup>
import {useRoute} from "vue-router";
import {onMounted} from "vue";
import {organization, fetchOrganization, loading, myOrganizationIds, addToOrganization, removeFromOrganization} from "src/composables/objects/organization";
import {useAppStore} from "stores/App";
import FAB from "components/FAB.vue";
import {storeToRefs} from "pinia";
import {useUserStore} from "stores/User";
import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
import AircraftsList from "components/aircrafts/AircraftsList.vue";
import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
import {useQuasar} from "quasar";
import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
const organizationId = parseInt(useRoute().params.id);
const loggedUser = storeToRefs(useUserStore()).user;
const $q = useQuasar();
onMounted(() => {
fetchOrganization(organizationId).then(() => {
useAppStore().setPageTitle(organization.value.name);
useAppStore().setBreadcrumbs([
{to: {name: "organizations"}, label: "Organizace"},
{label: organization.value.name}
])
});
});
</script>
<template>
<h3>Členové</h3>
<BasicListSkeleton :items-count-min="1" :items-count-max="5" v-if="loading || !organization"/>
<template v-else>
<q-list bordered separator v-if="organization.users.length > 0">
<q-item v-for="user in organization.users" :key="user">
<q-item-section avatar>
<AvatarFromPhoto :src="user.avatarImageUrl" v-if="user.avatarImageUrl" size="50"/>
<q-avatar icon="person" v-else class="bg-primary text-white" />
</q-item-section>
<q-item-section>
{{ user.name || user.email }}
<template v-if="loggedUser.id === user.id">(já)</template>
</q-item-section>
</q-item>
</q-list>
<BasicInfoBanner v-else>
Zatím nikdo...
</BasicInfoBanner>
<q-btn color="positive" size="lg" @click="addToOrganization()" v-if="!myOrganizationIds.includes(organizationId)">
<q-icon name="plus"/>
Přidat se
</q-btn>
<q-btn color="warning" size="lg" @click="removeFromOrganization()" v-else>
<q-icon name="minus"/>
Opustit organizaci
</q-btn>
<h3>Letadla</h3>
<BasicListSkeleton :items-count-min="1" :items-count-max="5" v-if="loading"/>
<AircraftsList :items="organization.aircrafts" v-if="organization.aircrafts.length > 0"/>
<BasicInfoBanner v-else>
Žádné letadlo
</BasicInfoBanner>
<FAB
mode="edit"
v-if="organization.createdById === loggedUser.id"
:router-link="{name: 'organizationEdit', params: {id: organizationId}}"
/>
</template>
</template>
@@ -1,7 +1,25 @@
<script setup>
import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
import { onMounted} from "vue";
import {fetchOrganizations, loading, myOrganizationIds, organizations} from "src/composables/objects/organization";
import {useAppStore} from "stores/App";
import FAB from "components/FAB.vue";
import OrganizationsList from "components/organizations/OrganizationsList.vue";
import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
onMounted(() => {
fetchOrganizations();
useAppStore().setPageTitle("Organizace");
});
</script>
<template>
organizace
<BasicListSkeleton v-if="loading"/>
<BasicInfoBanner v-else-if="!loading && organizations.length === 0" :route-add='{name: "addOrganization"}'>
Aktuálně nejsou dostupné žádné organizace, ke kterým by ses mohl/a přidat.
Zaregistruj sem svůj aeroklub, abyste mohli sdílet letadla.
</BasicInfoBanner>
<OrganizationsList :items="organizations" :my-organizations-ids="myOrganizationIds" v-else/>
<FAB :router-link="{name: 'addOrganization'}" mode="add"/>
</template>
+1 -1
View File
@@ -4,7 +4,7 @@ import {onMounted} from "vue";
import {useRoute} from "vue-router";
import FlightsListSkeleton from "components/flights/FlightsListSkeleton.vue";
import FlightsList from "components/flights/FlightsList.vue";
import AvatarFromPhoto from "components/AvatarFromPhoto.vue";
import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
import {fetchFlights, flights, loading as flightsLoading} from "src/composables/objects/flight";
import {fetchUser, user as pilot, loading as pilotLoading} from "src/composables/objects/user";
import PublicProfileSkeleton from "components/profile/PublicProfileSkeleton.vue";
+4
View File
@@ -45,7 +45,11 @@ const routes = [
{path: 'events/add', name: "addEvent", component: () => import('pages/profile/event/NewEventPage.vue')},
{path: 'events/:id', name: "eventDetail", component: () => import('pages/profile/event/EventDetailPage.vue')},
{path: 'events/:id/edit', name: "eventEdit", component: () => import('pages/profile/event/EditEventPage.vue')},
{path: 'organizations', name: "organizations", component: () => import('pages/profile/organization/OrganizationsPage.vue')},
{path: 'organizations/add', name: "addOrganization", component: () => import('pages/profile/organization/NewOrganizationPage.vue')},
{path: 'organizations/:id', name: "organizationDetail", component: () => import('pages/profile/organization/OrganizationDetailPage.vue')},
{path: 'organizations/:id/edit', name: "editOrganization", component: () => import('pages/profile/organization/EditOrganizationPage.vue')},
]
},
+1 -1
View File
@@ -4,7 +4,7 @@ import {COPILOT_DETAIL, COPILOTS_LIST} from "src/graphql/queries/copilots";
import {CREATE_COPILOT, EDIT_COPILOT} from "src/graphql/mutations/copilot";
import {updateInItems} from "stores/helpers";
export const useCopilotsStore = defineStore('SecondPilots', {
export const useCopilotsStore = defineStore('Copilots', {
state: () => ({
items: [],
isMutationRunning: false,
+1 -1
View File
@@ -4,7 +4,7 @@ import {CREATE_EVENT, EDIT_EVENT} from "src/graphql/mutations/event";
import {updateInItems} from "stores/helpers";
import {EVENT_DETAIL, EVENTS_LIST} from "src/graphql/queries/event";
export const useEventsStore = defineStore('SecondPilots', {
export const useEventsStore = defineStore('Events', {
state: () => ({
items: [],
isMutationRunning: false,
+56
View File
@@ -0,0 +1,56 @@
import {defineStore} from 'pinia'
import {apolloWrapper} from "src/graphql/apollo";
import {ADD_USER_TO_ORGANIZATION, CREATE_ORGANIZATION, EDIT_ORGANIZATION, REMOVE_USER_FROM_ORGANIZATION} from "src/graphql/mutations/organization";
import {ORGANIZATION_DETAIL, ORGANIZATIONS_LIST} from "src/graphql/queries/organization";
import {updateInItems} from "stores/helpers";
export const useOrganizationsStore = defineStore('Organizations', {
state: () => ({
items: [],
isMutationRunning: false,
}),
actions: {
fetchDetail(organizationId) {
return apolloWrapper.query(ORGANIZATION_DETAIL, {id: organizationId}).then(({data}) => data.organization);
},
fetch() {
return apolloWrapper.query(ORGANIZATIONS_LIST).then(result => {
this.items = result.data.organizations;
return result.data.organizations;
});
},
create(formData) {
delete formData['id'];
this.isMutationRunning = true;
return apolloWrapper.mutate(CREATE_ORGANIZATION, {input: formData}).then(({data}) => {
const organization = data.createOrganization;
this.items.push(organization);
return organization;
}).finally(() => this.isMutationRunning = false);
},
addUserToOrganization(organizationId) {
this.isMutationRunning = true;
return apolloWrapper.mutate(ADD_USER_TO_ORGANIZATION, {organizationId}).then(({data}) => {
return data.addToOrganization;
}).finally(() => this.isMutationRunning = false);
},
removeUserFromOrganization(organizationId) {
this.isMutationRunning = true;
return apolloWrapper.mutate(REMOVE_USER_FROM_ORGANIZATION, {organizationId}).then(({data}) => {
return data.removeFromOrganization;
}).finally(() => this.isMutationRunning = false);
},
update(id, formData) {
return apolloWrapper.mutate(EDIT_ORGANIZATION, {id: id, input: formData}).then(({data}) => {
const organization = data.editOrganization;
updateInItems(this.items, "id", organization);
return organization;
}).finally(() => this.isMutationRunning = false);
},
}
})
+1 -1
View File
@@ -2,7 +2,7 @@ import {defineStore} from "pinia";
import {apolloWrapper} from "src/graphql/apollo";
import {DELETE_PHOTO, EDIT_PHOTO, UPLOAD_PHOTO} from "src/graphql/mutations/photo";
export const usePhotosStore = defineStore('photos', {
export const usePhotosStore = defineStore('Photos', {
state: () => ({
isMutationRunning: false,
}),