2021-05-25 15:11:44 +00:00
|
|
|
<template>
|
|
|
|
<q-card bordered>
|
|
|
|
<div class="text-weight-medium q-px-xs">
|
|
|
|
{{ asHour(modelValue.start) }}
|
|
|
|
<template v-if="modelValue.end">- {{ asHour(modelValue.end) }}</template>
|
|
|
|
</div>
|
|
|
|
<div class="q-px-xs">
|
2021-11-24 20:45:00 +00:00
|
|
|
{{ typeName }}
|
2021-05-25 15:11:44 +00:00
|
|
|
</div>
|
|
|
|
<div class="col-auto q-px-xs" style="font-size: 10px">
|
|
|
|
{{ modelValue.comment }}
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<q-select
|
|
|
|
:model-value="modelValue.services"
|
2021-12-05 23:02:39 +00:00
|
|
|
:disable="!canAssignOther || modelValue.locked"
|
|
|
|
:options="options"
|
|
|
|
option-value="userid"
|
2021-05-25 15:11:44 +00:00
|
|
|
filled
|
|
|
|
multiple
|
2021-12-05 23:02:39 +00:00
|
|
|
use-input
|
2021-05-25 15:11:44 +00:00
|
|
|
label="Dienste"
|
2021-12-05 23:02:39 +00:00
|
|
|
behavior="dialog"
|
2021-05-25 15:11:44 +00:00
|
|
|
class="col-auto q-px-xs"
|
2021-12-05 23:02:39 +00:00
|
|
|
@filter="filterUsers"
|
|
|
|
@add="({ value }) => assign(value)"
|
|
|
|
@remove="({ value }) => unassign(value)"
|
2021-05-25 15:11:44 +00:00
|
|
|
>
|
2021-12-05 23:02:39 +00:00
|
|
|
<template #selected-item="{ opt, toggleOption }">
|
|
|
|
<service-user-chip :model-value="opt" removeable @remove="toggleOption" />
|
|
|
|
</template>
|
|
|
|
<template #option="{ opt, itemProps }">
|
|
|
|
<q-item v-bind="itemProps">
|
|
|
|
<q-item-section avatar>
|
|
|
|
<user-avatar :model-value="opt.userid" />
|
|
|
|
</q-item-section>
|
|
|
|
<q-item-section>{{ userDisplay(opt.userid) }}</q-item-section>
|
|
|
|
<q-item-section style="max-width: 10em" side>
|
|
|
|
<q-input
|
|
|
|
v-model.number="opt.value"
|
|
|
|
type="number"
|
|
|
|
min="0"
|
|
|
|
step="0.25"
|
|
|
|
dense
|
|
|
|
filled
|
|
|
|
@click.stop=""
|
|
|
|
/>
|
|
|
|
</q-item-section>
|
|
|
|
<q-item-section side>
|
|
|
|
<q-toggle v-model="opt.is_backup" label="Backup" left-label />
|
|
|
|
</q-item-section>
|
|
|
|
</q-item>
|
|
|
|
</template>
|
2021-05-25 15:11:44 +00:00
|
|
|
</q-select>
|
|
|
|
<div class="row col-12 justify-end">
|
2021-11-21 16:51:10 +00:00
|
|
|
<q-btn
|
|
|
|
v-if="!modelValue.locked && !isEnrolled && !isFull"
|
|
|
|
flat
|
|
|
|
color="primary"
|
|
|
|
label="Eintragen"
|
2021-12-05 23:02:39 +00:00
|
|
|
@click="assign()"
|
2021-11-21 16:51:10 +00:00
|
|
|
/>
|
|
|
|
<q-btn v-if="isEnrolled && !modelValue.locked" flat color="secondary" label="Optionen">
|
|
|
|
<q-menu auto-close>
|
|
|
|
<q-list style="min-width: 100px">
|
|
|
|
<q-item v-if="!isFull && canInvite" clickable @click="invite">
|
|
|
|
<q-item-section>Einladen</q-item-section>
|
|
|
|
</q-item>
|
|
|
|
<q-item clickable @click="transfer">
|
|
|
|
<q-item-section>Tauschen</q-item-section>
|
|
|
|
</q-item>
|
2021-12-05 23:02:39 +00:00
|
|
|
<q-item v-if="isBackup" clickable @click="backup(false)">
|
|
|
|
<q-tooltip>Backup zu vollem Dienst machen</q-tooltip>
|
|
|
|
<q-item-section>Dienst</q-item-section>
|
|
|
|
<q-item-section side><q-icon name="mdi-eye" /></q-item-section>
|
|
|
|
</q-item>
|
|
|
|
<q-item v-else clickable @click="backup(true)">
|
|
|
|
<q-tooltip>Nur als Backup eintragen</q-tooltip>
|
|
|
|
<q-item-section>Backup</q-item-section>
|
|
|
|
<q-item-section side><q-icon name="mdi-eye-off" /></q-item-section>
|
|
|
|
</q-item>
|
2021-11-21 16:51:10 +00:00
|
|
|
<q-separator />
|
2021-12-05 23:02:39 +00:00
|
|
|
<q-item clickable @click="unassign()">
|
2021-11-21 16:51:10 +00:00
|
|
|
<q-item-section class="text-negative">Austragen</q-item-section>
|
|
|
|
</q-item>
|
|
|
|
</q-list>
|
|
|
|
</q-menu>
|
|
|
|
</q-btn>
|
2021-05-25 15:11:44 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</q-card>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
2021-11-21 16:51:10 +00:00
|
|
|
import { date, useQuasar } from 'quasar';
|
2021-12-05 23:02:39 +00:00
|
|
|
import { defineComponent, onBeforeMount, computed, ref, PropType } from 'vue';
|
|
|
|
import { asHour, hasPermission, useMainStore, useUserStore } from '@flaschengeist/api';
|
2021-11-21 11:39:02 +00:00
|
|
|
import { useEventStore } from '../../../store';
|
2021-12-05 23:02:39 +00:00
|
|
|
import { PERMISSIONS } from '../../../permissions';
|
|
|
|
|
2021-11-21 16:51:10 +00:00
|
|
|
import TransferInviteDialog from './TransferInviteDialog.vue';
|
2021-12-05 23:02:39 +00:00
|
|
|
import ServiceUserChip from './ServiceUserChip.vue';
|
|
|
|
import { UserAvatar } from '@flaschengeist/api/components';
|
2021-05-25 15:11:44 +00:00
|
|
|
|
|
|
|
export default defineComponent({
|
|
|
|
name: 'JobSlot',
|
2021-12-05 23:02:39 +00:00
|
|
|
components: { ServiceUserChip, UserAvatar },
|
2021-05-25 15:11:44 +00:00
|
|
|
props: {
|
|
|
|
modelValue: {
|
|
|
|
required: true,
|
|
|
|
type: Object as PropType<FG.Job>,
|
|
|
|
},
|
|
|
|
eventId: {
|
|
|
|
required: true,
|
|
|
|
type: Number,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
emits: { 'update:modelValue': (v: FG.Job) => !!v },
|
|
|
|
setup(props, { emit }) {
|
2021-11-21 11:39:02 +00:00
|
|
|
const store = useEventStore();
|
2021-05-25 15:11:44 +00:00
|
|
|
const mainStore = useMainStore();
|
|
|
|
const userStore = useUserStore();
|
2021-11-21 11:39:02 +00:00
|
|
|
const quasar = useQuasar();
|
2021-05-25 15:11:44 +00:00
|
|
|
|
2021-12-05 23:02:39 +00:00
|
|
|
// Make sure users are loaded if we can assign them
|
|
|
|
onBeforeMount(async () => await userStore.getUsers());
|
2021-05-25 15:11:44 +00:00
|
|
|
|
2021-12-05 23:02:39 +00:00
|
|
|
/* Stuff used for general display */
|
|
|
|
// Get displayname of user
|
|
|
|
function userDisplay(id: string) {
|
|
|
|
return userStore.findUser(id)?.display_name || id;
|
2021-05-25 15:11:44 +00:00
|
|
|
}
|
|
|
|
|
2021-12-05 23:02:39 +00:00
|
|
|
// The name of the current job
|
2021-11-24 20:45:00 +00:00
|
|
|
const typeName = computed(() =>
|
|
|
|
typeof props.modelValue.type === 'object'
|
|
|
|
? props.modelValue.type.name
|
2021-12-05 23:02:39 +00:00
|
|
|
: store.jobTypes.find((j) => j.id === props.modelValue.type)?.name ||
|
|
|
|
'Unbekannter Diensttyp'
|
2021-11-24 20:45:00 +00:00
|
|
|
);
|
|
|
|
|
2021-12-05 23:02:39 +00:00
|
|
|
// The service of the current user if self assigned to the job
|
|
|
|
const service = computed(() =>
|
|
|
|
props.modelValue.services.find((service) => service.userid == mainStore.currentUser.userid)
|
2021-05-25 15:11:44 +00:00
|
|
|
);
|
|
|
|
|
2021-12-05 23:02:39 +00:00
|
|
|
// Weather the current user is assigned to the job
|
|
|
|
const isEnrolled = computed(() => service.value !== undefined);
|
|
|
|
|
|
|
|
// If the job has enough assigned services
|
2021-11-21 16:51:10 +00:00
|
|
|
const isFull = computed(
|
|
|
|
() =>
|
|
|
|
props.modelValue.services.map((s) => s.value).reduce((p, c) => p + c, 0) >=
|
|
|
|
props.modelValue.required_services
|
|
|
|
);
|
|
|
|
|
2021-12-05 23:02:39 +00:00
|
|
|
// If current user is only backup service
|
|
|
|
const isBackup = computed(() => service.value?.is_backup || false);
|
|
|
|
|
|
|
|
// If it is still possible to invite other users (= job is today or in the future)
|
2021-11-21 16:51:10 +00:00
|
|
|
const canInvite = computed(
|
|
|
|
() =>
|
|
|
|
(props.modelValue.end || props.modelValue.start) >
|
|
|
|
date.subtractFromDate(
|
|
|
|
date.buildDate({ hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }),
|
|
|
|
{ days: 1 }
|
|
|
|
)
|
|
|
|
);
|
2021-05-25 15:11:44 +00:00
|
|
|
|
2021-12-05 23:02:39 +00:00
|
|
|
// Assign user to a job
|
|
|
|
async function assign(service?: FG.Service) {
|
|
|
|
service = service || {
|
2021-05-25 15:11:44 +00:00
|
|
|
userid: mainStore.currentUser.userid,
|
|
|
|
is_backup: false,
|
2021-12-05 23:02:39 +00:00
|
|
|
value: 1,
|
2021-05-25 15:11:44 +00:00
|
|
|
};
|
|
|
|
try {
|
2021-12-05 23:02:39 +00:00
|
|
|
const job = await store.assignToJob(props.modelValue.id, service);
|
2021-05-25 15:11:44 +00:00
|
|
|
emit('update:modelValue', job);
|
|
|
|
} catch (error) {
|
|
|
|
console.warn(error);
|
2021-11-21 11:39:02 +00:00
|
|
|
quasar.notify({
|
2021-05-25 15:11:44 +00:00
|
|
|
group: false,
|
|
|
|
type: 'negative',
|
2021-11-24 20:47:14 +00:00
|
|
|
message: 'Fehler beim Ein- oder Austragen als Dienst',
|
2021-05-25 15:11:44 +00:00
|
|
|
timeout: 10000,
|
|
|
|
progress: true,
|
|
|
|
actions: [{ icon: 'mdi-close', color: 'white' }],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-05 23:02:39 +00:00
|
|
|
// open invite dialog (or transfer)
|
2021-11-21 16:51:10 +00:00
|
|
|
function invite(isInvite = true) {
|
|
|
|
quasar.dialog({
|
|
|
|
component: TransferInviteDialog,
|
|
|
|
componentProps: {
|
2021-11-24 20:47:14 +00:00
|
|
|
isInvite: isInvite,
|
2021-11-21 16:51:10 +00:00
|
|
|
job: props.modelValue,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-12-05 23:02:39 +00:00
|
|
|
/* Stuff needed if we can assign other user */
|
|
|
|
// Current user can assign other users
|
|
|
|
const canAssignOther = computed(() => hasPermission(PERMISSIONS.ASSIGN_OTHER));
|
|
|
|
|
|
|
|
// options shown in the select
|
|
|
|
const options = ref([] as FG.Service[]);
|
|
|
|
|
|
|
|
// users which are available (e.g. not already assigned)
|
|
|
|
const freeUsers = computed(() =>
|
|
|
|
userStore.users.filter((u) => props.modelValue.services.every((s) => s.userid !== u.userid))
|
|
|
|
);
|
|
|
|
|
|
|
|
// used to filter options based on user input
|
|
|
|
function filterUsers(
|
|
|
|
input: string,
|
|
|
|
doneFn: (
|
|
|
|
callbackFn: () => void,
|
|
|
|
afterFn?: (ref: { [index: string]: unknown }) => void
|
|
|
|
) => void,
|
|
|
|
abortFn: () => void
|
|
|
|
) {
|
|
|
|
if (freeUsers.value.length == 0) return abortFn();
|
|
|
|
|
|
|
|
// Filter the options
|
|
|
|
doneFn(() => {
|
|
|
|
// Skip filter options if input is too short
|
|
|
|
if (!input || input.length < 2) {
|
|
|
|
options.value = freeUsers.value.map<FG.Service>((u) => ({
|
|
|
|
userid: u.userid,
|
|
|
|
value: 1,
|
|
|
|
is_backup: false,
|
|
|
|
}));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Search matching string within all names
|
|
|
|
options.value = freeUsers.value
|
|
|
|
.filter((u) =>
|
|
|
|
input
|
|
|
|
.toLowerCase()
|
|
|
|
.split(' ')
|
|
|
|
.every(
|
|
|
|
(needle) =>
|
|
|
|
u.display_name.toLowerCase().indexOf(needle) > -1 ||
|
|
|
|
u.firstname.toLowerCase().indexOf(needle) > -1 ||
|
|
|
|
u.lastname.toLowerCase().indexOf(needle) > -1
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.map<FG.Service>((u) => ({ userid: u.userid, value: 1, is_backup: false }));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-05-25 15:11:44 +00:00
|
|
|
return {
|
2021-12-05 23:02:39 +00:00
|
|
|
assign,
|
|
|
|
unassign: (s?: FG.Service) => assign(Object.assign({}, s || service.value, { value: -1 })),
|
|
|
|
backup: (is_backup: boolean) => assign(Object.assign({}, service.value, { is_backup })),
|
|
|
|
canAssignOther,
|
2021-11-21 16:51:10 +00:00
|
|
|
canInvite,
|
2021-12-05 23:02:39 +00:00
|
|
|
filterUsers,
|
|
|
|
isBackup,
|
2021-05-25 15:11:44 +00:00
|
|
|
isEnrolled,
|
2021-11-21 16:51:10 +00:00
|
|
|
isFull,
|
|
|
|
invite: () => invite(true),
|
|
|
|
transfer: () => invite(false),
|
2021-11-24 20:45:00 +00:00
|
|
|
typeName,
|
2021-05-25 15:11:44 +00:00
|
|
|
userDisplay,
|
2021-12-05 23:02:39 +00:00
|
|
|
options,
|
2021-05-25 15:11:44 +00:00
|
|
|
asHour,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped></style>
|