Add assigment of crew to events and shifts
This commit is contained in:
parent
0aff9cc94a
commit
cef6b13dd1
7 changed files with 153 additions and 3 deletions
62
components/AssignedCrew.vue
Normal file
62
components/AssignedCrew.vue
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<template v-for="account in assigned">
|
||||||
|
{{ account.name }}
|
||||||
|
<button
|
||||||
|
v-if="edit"
|
||||||
|
type="button"
|
||||||
|
@click="assignedIds = assignedIds.filter(id => id !== account.id)"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
|
{{ " " }}
|
||||||
|
</template>
|
||||||
|
<input
|
||||||
|
v-if="edit"
|
||||||
|
type="text"
|
||||||
|
@change="addCrew"
|
||||||
|
v-model="addName"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{
|
||||||
|
edit?: boolean
|
||||||
|
}>();
|
||||||
|
const { data: accounts } = useAccounts();
|
||||||
|
const accountsById = computed(() => new Map(accounts.value?.map?.(a => [a.id, a])));
|
||||||
|
const assignedIds = defineModel<number[]>({ required: true });
|
||||||
|
const assigned = computed(
|
||||||
|
() => assignedIds.value.map(
|
||||||
|
id => accountsById.value.get(id) ?? { id, name: String(id) }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const crewByName = computed(() => new Map(
|
||||||
|
accounts.value
|
||||||
|
?.filter?.(a => a.type === "crew" || a.type === "admin")
|
||||||
|
?.map?.(a => [a.name, a])
|
||||||
|
));
|
||||||
|
const addName = ref("");
|
||||||
|
function addCrew() {
|
||||||
|
if (!addName.value)
|
||||||
|
return;
|
||||||
|
const account = crewByName.value.get(addName.value);
|
||||||
|
if (account) {
|
||||||
|
if (!assignedIds.value.some(id => id === account.id)) {
|
||||||
|
assignedIds.value = [...assignedIds.value, account.id];
|
||||||
|
} else {
|
||||||
|
alert(`${addName.value} has already been added`);
|
||||||
|
}
|
||||||
|
addName.value = "";
|
||||||
|
} else {
|
||||||
|
alert(`No crew account by the name ${addName.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input {
|
||||||
|
width: 3em;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -10,6 +10,7 @@
|
||||||
<th>event</th>
|
<th>event</th>
|
||||||
<th>s</th>
|
<th>s</th>
|
||||||
<th>location</th>
|
<th>location</th>
|
||||||
|
<th>assigned</th>
|
||||||
<th v-if="edit"></th>
|
<th v-if="edit"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -53,6 +54,7 @@
|
||||||
>{{ location.name }}</option>
|
>{{ location.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
Add at
|
Add at
|
||||||
<button
|
<button
|
||||||
|
@ -108,6 +110,13 @@
|
||||||
>{{ location.name }}</option>
|
>{{ location.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<AssignedCrew
|
||||||
|
:edit="true"
|
||||||
|
:modelValue="es.assigned"
|
||||||
|
@update:modelValue="editEventSlot(es, { assigned: $event })"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
:disabled="removed.has(es.id)"
|
:disabled="removed.has(es.id)"
|
||||||
|
@ -147,6 +156,8 @@
|
||||||
v-model="newEventName"
|
v-model="newEventName"
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -176,6 +187,7 @@
|
||||||
<td>{{ es.name }}</td>
|
<td>{{ es.name }}</td>
|
||||||
<td>{{ status(es) }}</td>
|
<td>{{ status(es) }}</td>
|
||||||
<td>{{ es.location }}</td>
|
<td>{{ es.location }}</td>
|
||||||
|
<td><AssignedCrew :modelValue="es.assigned" :edit="false" /></td>
|
||||||
</template>
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
@ -225,6 +237,7 @@ interface EventSlot {
|
||||||
origLocation: string,
|
origLocation: string,
|
||||||
name: string,
|
name: string,
|
||||||
location: string,
|
location: string,
|
||||||
|
assigned: number[],
|
||||||
start: DateTime,
|
start: DateTime,
|
||||||
end: DateTime,
|
end: DateTime,
|
||||||
}
|
}
|
||||||
|
@ -332,6 +345,7 @@ function mergeSlot(event: ScheduleEvent, eventSlot: EventSlot): ScheduleEvent {
|
||||||
return {
|
return {
|
||||||
...s,
|
...s,
|
||||||
locations: [...locations],
|
locations: [...locations],
|
||||||
|
assigned: eventSlot.assigned.length ? eventSlot.assigned : undefined,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
};
|
};
|
||||||
|
@ -349,6 +363,7 @@ function mergeSlot(event: ScheduleEvent, eventSlot: EventSlot): ScheduleEvent {
|
||||||
slots: [...event.slots, {
|
slots: [...event.slots, {
|
||||||
id: oldSlot ? oldSlot.id : `${event.id}-${nextId}`,
|
id: oldSlot ? oldSlot.id : `${event.id}-${nextId}`,
|
||||||
locations: [eventSlot.location],
|
locations: [eventSlot.location],
|
||||||
|
assigned: eventSlot.assigned.length ? eventSlot.assigned : undefined,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
}],
|
}],
|
||||||
|
@ -487,6 +502,7 @@ function editEventSlot(
|
||||||
duration?: string,
|
duration?: string,
|
||||||
name?: string,
|
name?: string,
|
||||||
location?: string,
|
location?: string,
|
||||||
|
assigned?: number[],
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (edits.start) {
|
if (edits.start) {
|
||||||
|
@ -522,6 +538,12 @@ function editEventSlot(
|
||||||
location: edits.location,
|
location: edits.location,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (edits.assigned !== undefined) {
|
||||||
|
eventSlot = {
|
||||||
|
...eventSlot,
|
||||||
|
assigned: edits.assigned,
|
||||||
|
};
|
||||||
|
}
|
||||||
const change = { op: "set" as const, data: eventSlot };
|
const change = { op: "set" as const, data: eventSlot };
|
||||||
changes.value = replaceChange(change, changes.value);
|
changes.value = replaceChange(change, changes.value);
|
||||||
}
|
}
|
||||||
|
@ -568,6 +590,7 @@ function newEventSlot(options: { start?: DateTime, end?: DateTime } = {}) {
|
||||||
name,
|
name,
|
||||||
origLocation: location,
|
origLocation: location,
|
||||||
location,
|
location,
|
||||||
|
assigned: [],
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
@ -612,6 +635,7 @@ const eventSlots = computed(() => {
|
||||||
slot,
|
slot,
|
||||||
name: event.name,
|
name: event.name,
|
||||||
location,
|
location,
|
||||||
|
assigned: slot.assigned ?? [],
|
||||||
origLocation: location,
|
origLocation: location,
|
||||||
start: DateTime.fromISO(slot.start, { zone: timezone.value }),
|
start: DateTime.fromISO(slot.start, { zone: timezone.value }),
|
||||||
end: DateTime.fromISO(slot.end, { zone: timezone.value }),
|
end: DateTime.fromISO(slot.end, { zone: timezone.value }),
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
<th>shift</th>
|
<th>shift</th>
|
||||||
<th>s</th>
|
<th>s</th>
|
||||||
<th>role</th>
|
<th>role</th>
|
||||||
|
<th>assigned</th>
|
||||||
<th v-if="edit"></th>
|
<th v-if="edit"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -53,6 +54,7 @@
|
||||||
>{{ role.name }}</option>
|
>{{ role.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
Add at
|
Add at
|
||||||
<button
|
<button
|
||||||
|
@ -108,6 +110,13 @@
|
||||||
>{{ role.name }}</option>
|
>{{ role.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<AssignedCrew
|
||||||
|
:edit="true"
|
||||||
|
:modelValue="ss.assigned"
|
||||||
|
@update:modelValue="editShiftSlot(ss, { assigned: $event })"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
:disabled="removed.has(ss.id)"
|
:disabled="removed.has(ss.id)"
|
||||||
|
@ -147,7 +156,7 @@
|
||||||
v-model="newShiftName"
|
v-model="newShiftName"
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
<td colspan="2">
|
<td colspan="3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="newShiftSlot()"
|
@click="newShiftSlot()"
|
||||||
|
@ -176,6 +185,7 @@
|
||||||
<td>{{ ss.name }}</td>
|
<td>{{ ss.name }}</td>
|
||||||
<td>{{ status(ss) }}</td>
|
<td>{{ status(ss) }}</td>
|
||||||
<td>{{ ss.role }}</td>
|
<td>{{ ss.role }}</td>
|
||||||
|
<td><AssignedCrew :modelValue="ss.assigned" :edit="false" /></td>
|
||||||
</template>
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
@ -225,6 +235,7 @@ interface ShiftSlot {
|
||||||
origRole: string,
|
origRole: string,
|
||||||
name: string,
|
name: string,
|
||||||
role: string,
|
role: string,
|
||||||
|
assigned: number[],
|
||||||
start: DateTime,
|
start: DateTime,
|
||||||
end: DateTime,
|
end: DateTime,
|
||||||
}
|
}
|
||||||
|
@ -299,6 +310,7 @@ function mergeSlot(shift: Shift, shiftSlot: ShiftSlot): Shift {
|
||||||
})) + 1;
|
})) + 1;
|
||||||
const start = shiftSlot.start.toUTC().toISO({ suppressSeconds: true })!;
|
const start = shiftSlot.start.toUTC().toISO({ suppressSeconds: true })!;
|
||||||
const end = shiftSlot.end.toUTC().toISO({ suppressSeconds: true })!;
|
const end = shiftSlot.end.toUTC().toISO({ suppressSeconds: true })!;
|
||||||
|
const assigned = shiftSlot.assigned.length ? shiftSlot.assigned : undefined;
|
||||||
|
|
||||||
if (shift.role !== shiftSlot.role) {
|
if (shift.role !== shiftSlot.role) {
|
||||||
console.warn(`Attempt to add slot id=${shiftSlot.id} role=${shiftSlot.role} to shift id=${shift.id} role=${shift.role}`);
|
console.warn(`Attempt to add slot id=${shiftSlot.id} role=${shiftSlot.role} to shift id=${shift.id} role=${shift.role}`);
|
||||||
|
@ -308,7 +320,7 @@ function mergeSlot(shift: Shift, shiftSlot: ShiftSlot): Shift {
|
||||||
if (oldSlot && oldSlot.id === shiftSlot.id) {
|
if (oldSlot && oldSlot.id === shiftSlot.id) {
|
||||||
return {
|
return {
|
||||||
...shift,
|
...shift,
|
||||||
slots: shift.slots.map(s => s.id !== oldSlot.id ? s : { ...s, start, end, }),
|
slots: shift.slots.map(s => s.id !== oldSlot.id ? s : { ...s, assigned, start, end, }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,6 +329,7 @@ function mergeSlot(shift: Shift, shiftSlot: ShiftSlot): Shift {
|
||||||
...shift,
|
...shift,
|
||||||
slots: [...(oldSlot ? shift.slots.filter(s => s.id !== oldSlot.id) : shift.slots), {
|
slots: [...(oldSlot ? shift.slots.filter(s => s.id !== oldSlot.id) : shift.slots), {
|
||||||
id: oldSlot ? oldSlot.id : `${shift.id}-${nextId}`,
|
id: oldSlot ? oldSlot.id : `${shift.id}-${nextId}`,
|
||||||
|
assigned,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
}],
|
}],
|
||||||
|
@ -459,6 +472,7 @@ function editShiftSlot(
|
||||||
duration?: string,
|
duration?: string,
|
||||||
name?: string,
|
name?: string,
|
||||||
role?: string,
|
role?: string,
|
||||||
|
assigned?: number[],
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (edits.start) {
|
if (edits.start) {
|
||||||
|
@ -505,6 +519,12 @@ function editShiftSlot(
|
||||||
changes.value = changesCopy;
|
changes.value = changesCopy;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (edits.assigned !== undefined) {
|
||||||
|
shiftSlot = {
|
||||||
|
...shiftSlot,
|
||||||
|
assigned: edits.assigned,
|
||||||
|
};
|
||||||
|
}
|
||||||
const change = { op: "set" as const, data: shiftSlot };
|
const change = { op: "set" as const, data: shiftSlot };
|
||||||
changes.value = replaceChange(change, changes.value);
|
changes.value = replaceChange(change, changes.value);
|
||||||
}
|
}
|
||||||
|
@ -551,6 +571,7 @@ function newShiftSlot(options: { start?: DateTime, end?: DateTime } = {}) {
|
||||||
name,
|
name,
|
||||||
origRole: role,
|
origRole: role,
|
||||||
role,
|
role,
|
||||||
|
assigned: [],
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
@ -594,6 +615,7 @@ const shiftSlots = computed(() => {
|
||||||
slot,
|
slot,
|
||||||
name: shift.name,
|
name: shift.name,
|
||||||
role: shift.role,
|
role: shift.role,
|
||||||
|
assigned: slot.assigned ?? [],
|
||||||
origRole: shift.role,
|
origRole: shift.role,
|
||||||
start: DateTime.fromISO(slot.start, { zone: timezone.value }),
|
start: DateTime.fromISO(slot.start, { zone: timezone.value }),
|
||||||
end: DateTime.fromISO(slot.end, { zone: timezone.value }),
|
end: DateTime.fromISO(slot.end, { zone: timezone.value }),
|
||||||
|
|
9
composables/accounts.ts
Normal file
9
composables/accounts.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export const useAccounts = () => useFetch(
|
||||||
|
"/api/accounts",
|
||||||
|
{
|
||||||
|
transform: (input) => input === undefined ? false as any as null: input,
|
||||||
|
getCachedData(key, nuxtApp) {
|
||||||
|
return nuxtApp.payload.data[key];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
23
server/api/accounts/index.get.ts
Normal file
23
server/api/accounts/index.get.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { readAccounts } from "~/server/database"
|
||||||
|
import { requireAccountSession } from "~/server/utils/session";
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const session = await requireAccountSession(event);
|
||||||
|
const accounts = await readAccounts();
|
||||||
|
const account = accounts.find(a => a.id === session.accountId);
|
||||||
|
if (!account) {
|
||||||
|
throw new Error("Account does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account.type === "admin") {
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
if (account.type === "crew") {
|
||||||
|
return accounts.filter(a => a.type === "crew" || a.type === "admin");
|
||||||
|
}
|
||||||
|
throw createError({
|
||||||
|
status: 403,
|
||||||
|
statusText: "Forbidden",
|
||||||
|
message: "You do not have permission to list accounts",
|
||||||
|
});
|
||||||
|
})
|
|
@ -29,6 +29,14 @@ export function canSeeCrew(accountType: string | undefined) {
|
||||||
export function filterSchedule(schedule: Schedule): Schedule {
|
export function filterSchedule(schedule: Schedule): Schedule {
|
||||||
return {
|
return {
|
||||||
locations: schedule.locations,
|
locations: schedule.locations,
|
||||||
events: schedule.events.filter(event => !event.crew),
|
events: schedule.events
|
||||||
|
.filter(event => !event.crew)
|
||||||
|
.map(event => ({
|
||||||
|
...event,
|
||||||
|
slots: event.slots.map(slot => ({
|
||||||
|
...slot,
|
||||||
|
assigned: undefined,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
shared/types/schedule.d.ts
vendored
2
shared/types/schedule.d.ts
vendored
|
@ -20,6 +20,7 @@ export interface TimeSlot {
|
||||||
start: string,
|
start: string,
|
||||||
end: string,
|
end: string,
|
||||||
locations: string[],
|
locations: string[],
|
||||||
|
assigned?: number[],
|
||||||
interested?: number,
|
interested?: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +42,7 @@ export interface ShiftSlot {
|
||||||
id: string,
|
id: string,
|
||||||
start: string,
|
start: string,
|
||||||
end: string,
|
end: string,
|
||||||
|
assigned?: number[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Schedule {
|
export interface Schedule {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue