Allow shifts without a role
Allow a shift to have no role associated with it in order to simplify conflict resolution around situations like a shift being created while the role it was assoiated with was deleted. This also allows for shifts that are freestanding to be created in case having a role doesn't make sense for it.
This commit is contained in:
parent
1d2edf7535
commit
5144bf2b37
5 changed files with 32 additions and 55 deletions
|
@ -39,12 +39,7 @@
|
|||
v-model="newShiftId"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SelectSingleEntity
|
||||
:entities="schedule.roles"
|
||||
v-model="newShiftRoleId"
|
||||
/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>
|
||||
Add at
|
||||
|
@ -139,12 +134,7 @@
|
|||
v-model="newShiftId"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SelectSingleEntity
|
||||
:entities="schedule.roles"
|
||||
v-model="newShiftRoleId"
|
||||
/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td colspan="2">
|
||||
<button
|
||||
type="button"
|
||||
|
@ -244,10 +234,6 @@ const newShiftEnd = computed({
|
|||
newShiftDuration.value = dropDay(end.diff(start)).toFormat("hh:mm");
|
||||
},
|
||||
});
|
||||
const newShiftRoleId = ref(props.roleId);
|
||||
watch(() => props.roleId, () => {
|
||||
newShiftRoleId.value = props.roleId;
|
||||
});
|
||||
|
||||
function endFromTime(start: DateTime, time: string) {
|
||||
let end = start.startOf("day").plus(Duration.fromISOTime(time, { locale: accountStore.activeLocale }));
|
||||
|
@ -291,11 +277,6 @@ function newShiftSlot(options: { start?: DateTime, end?: DateTime } = {}) {
|
|||
alert("Invalid shift");
|
||||
return;
|
||||
}
|
||||
const role = schedule.value.roles.get(newShiftRoleId.value!);
|
||||
if (!role) {
|
||||
alert("Invalid role");
|
||||
return;
|
||||
}
|
||||
let start;
|
||||
let end;
|
||||
const duration = durationFromTime(newShiftDuration.value);
|
||||
|
|
|
@ -131,10 +131,6 @@ function newShift() {
|
|||
alert(`Shift ${newShiftName.value} already exists`);
|
||||
return;
|
||||
}
|
||||
if (newShiftRoleId.value === undefined) {
|
||||
alert(`Invalid role`);
|
||||
return;
|
||||
}
|
||||
const zone = Info.normalizeZone(accountStore.activeTimezone);
|
||||
const locale = accountStore.activeLocale;
|
||||
const shift = ClientScheduleShift.create(
|
||||
|
|
|
@ -105,22 +105,21 @@
|
|||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-if="schedule.roles.size">
|
||||
<template v-if="roleGroups.size">
|
||||
<tr>
|
||||
<th>Shifts</th>
|
||||
<td :colSpan="totalColumns"></td>
|
||||
</tr>
|
||||
<template v-for="role in schedule.roles.values()" :key="role.id">
|
||||
<template v-for="[id, roleGroup] in roleGroups" :key="id">
|
||||
<tr
|
||||
v-if="roleGroups.has(role.id)"
|
||||
v-for="row, index in roleGroups.get(role.id)"
|
||||
v-for="row, index in roleGroup"
|
||||
:key="index"
|
||||
>
|
||||
<th
|
||||
v-if="index === 0"
|
||||
:rowSpan="roleGroups.get(role.id)!.length"
|
||||
:rowSpan="roleGroup.length"
|
||||
>
|
||||
{{ role.name }}
|
||||
{{ schedule.roles.get(id!)?.name }}
|
||||
</th>
|
||||
<td
|
||||
v-for="cell, index in row"
|
||||
|
@ -152,7 +151,7 @@ const oneMinMs = 60 * 1000;
|
|||
/** Point in time where a time slots starts or ends. */
|
||||
type Edge =
|
||||
| { type: "start" | "end", source: "event", slot: ClientScheduleEventSlot }
|
||||
| { type: "start" | "end", source: "shift", roleId: Id, slot: ClientScheduleShiftSlot }
|
||||
| { type: "start" | "end", source: "shift", roleId?: Id, slot: ClientScheduleShiftSlot }
|
||||
;
|
||||
|
||||
/** Point in time where multiple edges meet. */
|
||||
|
@ -163,7 +162,7 @@ type Span = {
|
|||
start: Junction;
|
||||
end: Junction,
|
||||
locations: Map<number, Set<ClientScheduleEventSlot>>,
|
||||
roles: Map<number, Set<ClientScheduleShiftSlot>>,
|
||||
roles: Map<number | undefined, Set<ClientScheduleShiftSlot>>,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -238,9 +237,10 @@ function* spansFromJunctions(
|
|||
const activeLocations = new Map(
|
||||
[...locations.keys()].map(id => [id, new Set<ClientScheduleEventSlot>()])
|
||||
);
|
||||
const activeRoles = new Map(
|
||||
[...roles.keys()].map(id => [id, new Set<ClientScheduleShiftSlot>()])
|
||||
const activeRoles = new Map<number | undefined, Set<ClientScheduleShiftSlot>>(
|
||||
[...roles.keys()].map(id => [id, new Set()]),
|
||||
);
|
||||
activeRoles.set(undefined, new Set());
|
||||
for (const [start, end] of pairs(junctions)) {
|
||||
for (const edge of start.edges) {
|
||||
if (edge.type === "start") {
|
||||
|
@ -249,7 +249,7 @@ function* spansFromJunctions(
|
|||
activeLocations.get(locationId)?.add(edge.slot)
|
||||
}
|
||||
} else if (edge.source === "shift") {
|
||||
activeRoles.get(edge.roleId)?.add(edge.slot)
|
||||
activeRoles.get(edge.roleId)?.add(edge.slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -403,7 +403,8 @@ function tableElementsFromStretches(
|
|||
const dayHeaders: DayHead[] = [];
|
||||
const hourHeaders: HourHead[]= [];
|
||||
const locationGroups = new Map<number, LocationRow[]>([...locations.keys()].map(id => [id, []]));
|
||||
const roleGroups = new Map<number, RoleRow[]>([...roles.keys()].map(id => [id, []]));
|
||||
const roleGroups = new Map<number | undefined, RoleRow[]>([...roles.keys()].map(id => [id, []]));
|
||||
roleGroups.set(undefined, []);
|
||||
const eventBySlotId = new Map([...events.values()].flatMap(event => [...event.slots.values()].map(slot => [slot.id, event])));
|
||||
const shiftBySlotId = new Map([...shifts.values()].flatMap?.(shift => [...shift.slots.values()].map(slot =>[slot.id, shift])));
|
||||
let totalColumns = 0;
|
||||
|
@ -443,7 +444,7 @@ function tableElementsFromStretches(
|
|||
row.push({ span: 0, isBreak, slot, event: eventBySlotId.get(slot.id) });
|
||||
}
|
||||
}
|
||||
function startRole(id: number, isBreak: boolean, newSlots = new Set<ClientScheduleShiftSlot>()) {
|
||||
function startRole(id: number | undefined, isBreak: boolean, newSlots = new Set<ClientScheduleShiftSlot>()) {
|
||||
const group = roleGroups.get(id)!;
|
||||
// Remove all slots that are no longer in the new slots.
|
||||
for (const row of group) {
|
||||
|
@ -481,9 +482,8 @@ function tableElementsFromStretches(
|
|||
row[row.length - 1].span += 1;
|
||||
}
|
||||
}
|
||||
for(const role of roles.values()) {
|
||||
const group = roleGroups.get(role.id)!;
|
||||
for (const row of group) {
|
||||
for (const roleGroup of roleGroups.values()) {
|
||||
for (const row of roleGroup) {
|
||||
row[row.length - 1].span += 1;
|
||||
}
|
||||
}
|
||||
|
@ -500,8 +500,8 @@ function tableElementsFromStretches(
|
|||
for (const location of locations.values()) {
|
||||
startLocation(location.id, false);
|
||||
}
|
||||
for (const role of roles.values()) {
|
||||
startRole(role.id, false);
|
||||
for (const roleId of roleGroups.keys()) {
|
||||
startRole(roleId, false);
|
||||
}
|
||||
} else {
|
||||
startColumnGroup(lastStretch.end, stretch.start, 1, true);
|
||||
|
@ -514,8 +514,8 @@ function tableElementsFromStretches(
|
|||
for(const location of locations.values()) {
|
||||
startLocation(location.id, true);
|
||||
}
|
||||
for(const role of roles.values()) {
|
||||
startRole(role.id, true);
|
||||
for (const roleId of roleGroups.keys()) {
|
||||
startRole(roleId, true);
|
||||
}
|
||||
pushColumn();
|
||||
|
||||
|
@ -526,8 +526,8 @@ function tableElementsFromStretches(
|
|||
for(const location of locations.values()) {
|
||||
startLocation(location.id, false);
|
||||
}
|
||||
for(const role of roles.values()) {
|
||||
startRole(role.id, false);
|
||||
for (const roleId of roleGroups.keys()) {
|
||||
startRole(roleId, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -544,12 +544,12 @@ function tableElementsFromStretches(
|
|||
startLocation(location.id, false, slots)
|
||||
}
|
||||
}
|
||||
for (const role of roles.values()) {
|
||||
const slots = cutSpan.roles.get(role.id) ?? new Set();
|
||||
const group = roleGroups.get(role.id)!;
|
||||
for (const roleId of roleGroups.keys()) {
|
||||
const slots = cutSpan.roles.get(roleId) ?? new Set();
|
||||
const group = roleGroups.get(roleId)!;
|
||||
const existing = new Set(group.map(row => row[row.length - 1].slot).filter(slot => slot));
|
||||
if (!setEquals(slots, existing)) {
|
||||
startRole(role.id, false, slots);
|
||||
startRole(roleId, false, slots);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ export const apiScheduleShiftSlotSchema = z.object({
|
|||
export type ApiScheduleShiftSlot = z.infer<typeof apiScheduleShiftSlotSchema>;
|
||||
|
||||
export const apiScheduleShiftSchema = defineApiEntity({
|
||||
roleId: idSchema,
|
||||
roleId: z.optional(idSchema),
|
||||
name: z.string(),
|
||||
description: z.optional(z.string()),
|
||||
slots: z.array(apiScheduleShiftSlotSchema),
|
||||
|
|
|
@ -515,7 +515,7 @@ export class ClientScheduleRole extends ClientEntity<ApiScheduleRole> {
|
|||
|
||||
export class ClientScheduleShift extends ClientEntity<ApiScheduleShift> {
|
||||
schedule!: ClientSchedule;
|
||||
serverRoleId: Id;
|
||||
serverRoleId: Id | undefined;
|
||||
serverName: string;
|
||||
serverDescription: string;
|
||||
serverSlotIds: Set<Id>;
|
||||
|
@ -524,7 +524,7 @@ export class ClientScheduleShift extends ClientEntity<ApiScheduleShift> {
|
|||
id: Id,
|
||||
updatedAt: DateTime,
|
||||
deleted: boolean,
|
||||
public roleId: Id,
|
||||
public roleId: Id | undefined,
|
||||
public name: string,
|
||||
public description: string,
|
||||
public slotIds: Set<Id>,
|
||||
|
@ -572,7 +572,7 @@ export class ClientScheduleShift extends ClientEntity<ApiScheduleShift> {
|
|||
static create(
|
||||
schedule: ClientSchedule,
|
||||
id: Id,
|
||||
roleId: Id,
|
||||
roleId: Id | undefined,
|
||||
name: string,
|
||||
description: string,
|
||||
slotIds: Set<Id>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue