Sprava eventu a organizaci
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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/>
|
||||
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -21,6 +21,10 @@ fragment flightListInfo on Flight {
|
||||
}
|
||||
}
|
||||
|
||||
event {
|
||||
name
|
||||
}
|
||||
|
||||
takeoffAirport {
|
||||
...airportInfo
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
// }
|
||||
// }
|
||||
// `;
|
||||
@@ -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}
|
||||
`;
|
||||
@@ -16,6 +16,11 @@ export const LOGGED_USER_DETAIL = gql`
|
||||
query loggedUserDetail {
|
||||
loggedUser {
|
||||
...userFullInfo
|
||||
|
||||
organizations {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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')},
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user