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"
|
v-model="newShiftId"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td></td>
|
||||||
<SelectSingleEntity
|
|
||||||
:entities="schedule.roles"
|
|
||||||
v-model="newShiftRoleId"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>
|
<td>
|
||||||
Add at
|
Add at
|
||||||
|
@ -139,12 +134,7 @@
|
||||||
v-model="newShiftId"
|
v-model="newShiftId"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td></td>
|
||||||
<SelectSingleEntity
|
|
||||||
:entities="schedule.roles"
|
|
||||||
v-model="newShiftRoleId"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -244,10 +234,6 @@ const newShiftEnd = computed({
|
||||||
newShiftDuration.value = dropDay(end.diff(start)).toFormat("hh:mm");
|
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) {
|
function endFromTime(start: DateTime, time: string) {
|
||||||
let end = start.startOf("day").plus(Duration.fromISOTime(time, { locale: accountStore.activeLocale }));
|
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");
|
alert("Invalid shift");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const role = schedule.value.roles.get(newShiftRoleId.value!);
|
|
||||||
if (!role) {
|
|
||||||
alert("Invalid role");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let start;
|
let start;
|
||||||
let end;
|
let end;
|
||||||
const duration = durationFromTime(newShiftDuration.value);
|
const duration = durationFromTime(newShiftDuration.value);
|
||||||
|
|
|
@ -131,10 +131,6 @@ function newShift() {
|
||||||
alert(`Shift ${newShiftName.value} already exists`);
|
alert(`Shift ${newShiftName.value} already exists`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (newShiftRoleId.value === undefined) {
|
|
||||||
alert(`Invalid role`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const zone = Info.normalizeZone(accountStore.activeTimezone);
|
const zone = Info.normalizeZone(accountStore.activeTimezone);
|
||||||
const locale = accountStore.activeLocale;
|
const locale = accountStore.activeLocale;
|
||||||
const shift = ClientScheduleShift.create(
|
const shift = ClientScheduleShift.create(
|
||||||
|
|
|
@ -105,22 +105,21 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="schedule.roles.size">
|
<template v-if="roleGroups.size">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Shifts</th>
|
<th>Shifts</th>
|
||||||
<td :colSpan="totalColumns"></td>
|
<td :colSpan="totalColumns"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-for="role in schedule.roles.values()" :key="role.id">
|
<template v-for="[id, roleGroup] in roleGroups" :key="id">
|
||||||
<tr
|
<tr
|
||||||
v-if="roleGroups.has(role.id)"
|
v-for="row, index in roleGroup"
|
||||||
v-for="row, index in roleGroups.get(role.id)"
|
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
<th
|
<th
|
||||||
v-if="index === 0"
|
v-if="index === 0"
|
||||||
:rowSpan="roleGroups.get(role.id)!.length"
|
:rowSpan="roleGroup.length"
|
||||||
>
|
>
|
||||||
{{ role.name }}
|
{{ schedule.roles.get(id!)?.name }}
|
||||||
</th>
|
</th>
|
||||||
<td
|
<td
|
||||||
v-for="cell, index in row"
|
v-for="cell, index in row"
|
||||||
|
@ -152,7 +151,7 @@ const oneMinMs = 60 * 1000;
|
||||||
/** Point in time where a time slots starts or ends. */
|
/** Point in time where a time slots starts or ends. */
|
||||||
type Edge =
|
type Edge =
|
||||||
| { type: "start" | "end", source: "event", slot: ClientScheduleEventSlot }
|
| { 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. */
|
/** Point in time where multiple edges meet. */
|
||||||
|
@ -163,7 +162,7 @@ type Span = {
|
||||||
start: Junction;
|
start: Junction;
|
||||||
end: Junction,
|
end: Junction,
|
||||||
locations: Map<number, Set<ClientScheduleEventSlot>>,
|
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(
|
const activeLocations = new Map(
|
||||||
[...locations.keys()].map(id => [id, new Set<ClientScheduleEventSlot>()])
|
[...locations.keys()].map(id => [id, new Set<ClientScheduleEventSlot>()])
|
||||||
);
|
);
|
||||||
const activeRoles = new Map(
|
const activeRoles = new Map<number | undefined, Set<ClientScheduleShiftSlot>>(
|
||||||
[...roles.keys()].map(id => [id, new Set<ClientScheduleShiftSlot>()])
|
[...roles.keys()].map(id => [id, new Set()]),
|
||||||
);
|
);
|
||||||
|
activeRoles.set(undefined, new Set());
|
||||||
for (const [start, end] of pairs(junctions)) {
|
for (const [start, end] of pairs(junctions)) {
|
||||||
for (const edge of start.edges) {
|
for (const edge of start.edges) {
|
||||||
if (edge.type === "start") {
|
if (edge.type === "start") {
|
||||||
|
@ -249,7 +249,7 @@ function* spansFromJunctions(
|
||||||
activeLocations.get(locationId)?.add(edge.slot)
|
activeLocations.get(locationId)?.add(edge.slot)
|
||||||
}
|
}
|
||||||
} else if (edge.source === "shift") {
|
} 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 dayHeaders: DayHead[] = [];
|
||||||
const hourHeaders: HourHead[]= [];
|
const hourHeaders: HourHead[]= [];
|
||||||
const locationGroups = new Map<number, LocationRow[]>([...locations.keys()].map(id => [id, []]));
|
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 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])));
|
const shiftBySlotId = new Map([...shifts.values()].flatMap?.(shift => [...shift.slots.values()].map(slot =>[slot.id, shift])));
|
||||||
let totalColumns = 0;
|
let totalColumns = 0;
|
||||||
|
@ -443,7 +444,7 @@ function tableElementsFromStretches(
|
||||||
row.push({ span: 0, isBreak, slot, event: eventBySlotId.get(slot.id) });
|
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)!;
|
const group = roleGroups.get(id)!;
|
||||||
// Remove all slots that are no longer in the new slots.
|
// Remove all slots that are no longer in the new slots.
|
||||||
for (const row of group) {
|
for (const row of group) {
|
||||||
|
@ -481,9 +482,8 @@ function tableElementsFromStretches(
|
||||||
row[row.length - 1].span += 1;
|
row[row.length - 1].span += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(const role of roles.values()) {
|
for (const roleGroup of roleGroups.values()) {
|
||||||
const group = roleGroups.get(role.id)!;
|
for (const row of roleGroup) {
|
||||||
for (const row of group) {
|
|
||||||
row[row.length - 1].span += 1;
|
row[row.length - 1].span += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -500,8 +500,8 @@ function tableElementsFromStretches(
|
||||||
for (const location of locations.values()) {
|
for (const location of locations.values()) {
|
||||||
startLocation(location.id, false);
|
startLocation(location.id, false);
|
||||||
}
|
}
|
||||||
for (const role of roles.values()) {
|
for (const roleId of roleGroups.keys()) {
|
||||||
startRole(role.id, false);
|
startRole(roleId, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
startColumnGroup(lastStretch.end, stretch.start, 1, true);
|
startColumnGroup(lastStretch.end, stretch.start, 1, true);
|
||||||
|
@ -514,8 +514,8 @@ function tableElementsFromStretches(
|
||||||
for(const location of locations.values()) {
|
for(const location of locations.values()) {
|
||||||
startLocation(location.id, true);
|
startLocation(location.id, true);
|
||||||
}
|
}
|
||||||
for(const role of roles.values()) {
|
for (const roleId of roleGroups.keys()) {
|
||||||
startRole(role.id, true);
|
startRole(roleId, true);
|
||||||
}
|
}
|
||||||
pushColumn();
|
pushColumn();
|
||||||
|
|
||||||
|
@ -526,8 +526,8 @@ function tableElementsFromStretches(
|
||||||
for(const location of locations.values()) {
|
for(const location of locations.values()) {
|
||||||
startLocation(location.id, false);
|
startLocation(location.id, false);
|
||||||
}
|
}
|
||||||
for(const role of roles.values()) {
|
for (const roleId of roleGroups.keys()) {
|
||||||
startRole(role.id, false);
|
startRole(roleId, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,12 +544,12 @@ function tableElementsFromStretches(
|
||||||
startLocation(location.id, false, slots)
|
startLocation(location.id, false, slots)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const role of roles.values()) {
|
for (const roleId of roleGroups.keys()) {
|
||||||
const slots = cutSpan.roles.get(role.id) ?? new Set();
|
const slots = cutSpan.roles.get(roleId) ?? new Set();
|
||||||
const group = roleGroups.get(role.id)!;
|
const group = roleGroups.get(roleId)!;
|
||||||
const existing = new Set(group.map(row => row[row.length - 1].slot).filter(slot => slot));
|
const existing = new Set(group.map(row => row[row.length - 1].slot).filter(slot => slot));
|
||||||
if (!setEquals(slots, existing)) {
|
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 type ApiScheduleShiftSlot = z.infer<typeof apiScheduleShiftSlotSchema>;
|
||||||
|
|
||||||
export const apiScheduleShiftSchema = defineApiEntity({
|
export const apiScheduleShiftSchema = defineApiEntity({
|
||||||
roleId: idSchema,
|
roleId: z.optional(idSchema),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.optional(z.string()),
|
description: z.optional(z.string()),
|
||||||
slots: z.array(apiScheduleShiftSlotSchema),
|
slots: z.array(apiScheduleShiftSlotSchema),
|
||||||
|
|
|
@ -515,7 +515,7 @@ export class ClientScheduleRole extends ClientEntity<ApiScheduleRole> {
|
||||||
|
|
||||||
export class ClientScheduleShift extends ClientEntity<ApiScheduleShift> {
|
export class ClientScheduleShift extends ClientEntity<ApiScheduleShift> {
|
||||||
schedule!: ClientSchedule;
|
schedule!: ClientSchedule;
|
||||||
serverRoleId: Id;
|
serverRoleId: Id | undefined;
|
||||||
serverName: string;
|
serverName: string;
|
||||||
serverDescription: string;
|
serverDescription: string;
|
||||||
serverSlotIds: Set<Id>;
|
serverSlotIds: Set<Id>;
|
||||||
|
@ -524,7 +524,7 @@ export class ClientScheduleShift extends ClientEntity<ApiScheduleShift> {
|
||||||
id: Id,
|
id: Id,
|
||||||
updatedAt: DateTime,
|
updatedAt: DateTime,
|
||||||
deleted: boolean,
|
deleted: boolean,
|
||||||
public roleId: Id,
|
public roleId: Id | undefined,
|
||||||
public name: string,
|
public name: string,
|
||||||
public description: string,
|
public description: string,
|
||||||
public slotIds: Set<Id>,
|
public slotIds: Set<Id>,
|
||||||
|
@ -572,7 +572,7 @@ export class ClientScheduleShift extends ClientEntity<ApiScheduleShift> {
|
||||||
static create(
|
static create(
|
||||||
schedule: ClientSchedule,
|
schedule: ClientSchedule,
|
||||||
id: Id,
|
id: Id,
|
||||||
roleId: Id,
|
roleId: Id | undefined,
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
slotIds: Set<Id>,
|
slotIds: Set<Id>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue