diff --git a/components/Timetable.vue b/components/Timetable.vue
index 989086c..78faac4 100644
--- a/components/Timetable.vue
+++ b/components/Timetable.vue
@@ -38,7 +38,7 @@
-
+
@@ -83,16 +83,25 @@
-
- {{ location.name }} |
-
+ |
- {{ row.title }}
+ {{ location.name }}
+ |
+
+ {{ cell.event?.name }}
|
@@ -102,16 +111,25 @@
|
-
- {{ role.name }} |
-
+ |
- {{ row.title }}
+ {{ role.name }}
+ |
+
+ {{ cell.shift?.name }}
|
@@ -376,20 +394,22 @@ function tableElementsFromStretches(
type Col = { minutes?: number };
type DayHead = { span: number, isBreak: boolean, content?: string }
type HourHead = { span: number, isBreak: boolean, isDayShift: boolean, content?: string }
- type LocationCell = { span: number, slots: Set, title: string, crew?: boolean }
- type RoleCell = { span: number, slots: Set, title: string };
- type ColumnGroup = { start: number, end: number, width: number, className?: string, cols: Col[] };
+ type LocationCell = { span: number, isBreak: boolean, slot?: ClientScheduleEventSlot, event?: ClientScheduleEvent };
+ type LocationRow = LocationCell[];
+ type RoleCell = { span: number, isBreak: boolean, slot?: ClientScheduleShiftSlot, shift?: ClientScheduleShift };
+ type RoleRow = RoleCell[];
+ type ColumnGroup = { start: number, end: number, width: number, isBreak: boolean, cols: Col[] };
const columnGroups: ColumnGroup[] = [];
const dayHeaders: DayHead[] = [];
const hourHeaders: HourHead[]= [];
- const locationRows = new Map([...locations.keys()].map(id => [id, []]));
- const roleRows = new Map([...roles.keys()].map(id => [id, []]));
+ const locationGroups = new Map([...locations.keys()].map(id => [id, []]));
+ const roleGroups = new Map([...roles.keys()].map(id => [id, []]));
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;
- function startColumnGroup(start: number, end: number, width: number, className?: string) {
- columnGroups.push({ start, end, width, className, cols: []})
+ function startColumnGroup(start: number, end: number, width: number, isBreak: boolean) {
+ columnGroups.push({ start, end, width, isBreak, cols: []})
}
function startDay(isBreak: boolean, content?: string) {
dayHeaders.push({ span: 0, isBreak, content })
@@ -397,22 +417,58 @@ function tableElementsFromStretches(
function startHour(isBreak: boolean, content?: string, isDayShift = false) {
hourHeaders.push({ span: 0, isBreak, isDayShift, content })
}
- function startLocation(id: number, slots = new Set()) {
- const rows = locationRows.get(id)!;
- if (rows.length) {
- const row = rows[rows.length - 1];
- row.title = [...row.slots].map(slot => eventBySlotId.get(slot.id)!.name).join(", ");
- row.crew = [...row.slots].every(slot => eventBySlotId.get(slot.id)!.crew);
+ function startLocation(id: number, isBreak: boolean, newSlots = new Set()) {
+ const group = locationGroups.get(id)!;
+ // Remove all slots that are no longer in the new slots.
+ for (const row of group) {
+ const cell = row[row.length - 1];
+ if (cell.isBreak !== isBreak || cell.slot && !newSlots.has(cell.slot))
+ row.push({ span: 0, isBreak, slot: undefined, event: undefined });
+ }
+ const existingSlots = new Set(group.map(row => row[row.length - 1].slot).filter(slot => slot));
+ // Add all new slots that do not already exist.
+ for (const slot of newSlots.difference(existingSlots)) {
+ let row = group.find(row => !row[row.length - 1].slot);
+ if (!row) {
+ row = columnGroups.map(
+ colGroup => ({
+ span: colGroup.cols.length,
+ isBreak: colGroup.isBreak,
+ slot: undefined,
+ event: undefined
+ })
+ );
+ group.push(row);
+ }
+ row.push({ span: 0, isBreak, slot, event: eventBySlotId.get(slot.id) });
}
- rows.push({ span: 0, slots, title: "" });
}
- function startRole(id: number, slots = new Set()) {
- const rows = roleRows.get(id)!;
- if (rows.length) {
- const row = rows[rows.length - 1];
- row.title = [...row.slots].map(slot => shiftBySlotId.get(slot.id)!.name).join(", ");
+ function startRole(id: number, isBreak: boolean, newSlots = new Set()) {
+ const group = roleGroups.get(id)!;
+ // Remove all slots that are no longer in the new slots.
+ for (const row of group) {
+ const cell = row[row.length - 1];
+ if (cell.isBreak !== isBreak || cell.slot && !newSlots.has(cell.slot)) {
+ row.push({ span: 0, isBreak, slot: undefined, shift: undefined });
+ }
+ }
+ const existingSlots = new Set(group.map(row => row[row.length - 1].slot).filter(slot => slot));
+ // Add all new slots that do not already exist.
+ for (const slot of newSlots.difference(existingSlots)) {
+ let row = group.find(row => !row[row.length - 1].slot);
+ if (!row) {
+ row = columnGroups.map(
+ colGroup => ({
+ span: colGroup.cols.length,
+ isBreak: colGroup.isBreak,
+ slot: undefined,
+ shift: undefined
+ })
+ );
+ group.push(row);
+ }
+ row.push({ span: 0, isBreak, slot, shift: shiftBySlotId.get(slot.id) });
}
- rows.push({ span: 0, slots, title: "" });
}
function pushColumn(minutes?: number) {
totalColumns += 1;
@@ -420,12 +476,16 @@ function tableElementsFromStretches(
dayHeaders[dayHeaders.length - 1].span += 1;
hourHeaders[hourHeaders.length - 1].span += 1;
for(const location of locations.values()) {
- const row = locationRows.get(location.id)!;
- row[row.length - 1].span += 1;
+ const group = locationGroups.get(location.id)!;
+ for (const row of group) {
+ row[row.length - 1].span += 1;
+ }
}
for(const role of roles.values()) {
- const row = roleRows.get(role.id)!;
- row[row.length - 1].span += 1;
+ const group = roleGroups.get(role.id)!;
+ for (const row of group) {
+ row[row.length - 1].span += 1;
+ }
}
}
@@ -434,17 +494,17 @@ function tableElementsFromStretches(
stretch = padStretch(stretch, timezone);
const startDate = DateTime.fromMillis(stretch.start, { zone: timezone, locale: accountStore.activeLocale });
if (!lastStretch) {
- startColumnGroup(stretch.start, stretch.end, (stretch.end - stretch.start) / oneHourMs);
+ startColumnGroup(stretch.start, stretch.end, (stretch.end - stretch.start) / oneHourMs, false);
startDay(false, startDate.toFormat("yyyy-LL-dd"));
startHour(false, startDate.toFormat("HH:mm"));
- for(const location of locations.values()) {
- startLocation(location.id);
+ for (const location of locations.values()) {
+ startLocation(location.id, false);
}
- for(const role of roles.values()) {
- startRole(role.id);
+ for (const role of roles.values()) {
+ startRole(role.id, false);
}
} else {
- startColumnGroup(lastStretch.end, stretch.start, 1, "break");
+ startColumnGroup(lastStretch.end, stretch.start, 1, true);
const dayName = startDate.toFormat("yyyy-LL-dd");
const lastDayHeader = dayHeaders[dayHeaders.length - 1]
const sameDay = dayName === lastDayHeader.content && lastDayHeader.span;
@@ -452,22 +512,22 @@ function tableElementsFromStretches(
startDay(true);
startHour(true, "break");
for(const location of locations.values()) {
- startLocation(location.id);
+ startLocation(location.id, true);
}
for(const role of roles.values()) {
- startRole(role.id);
+ startRole(role.id, true);
}
pushColumn();
- startColumnGroup(stretch.start, stretch.end, (stretch.end - stretch.start) / oneHourMs);
+ startColumnGroup(stretch.start, stretch.end, (stretch.end - stretch.start) / oneHourMs, false);
if (!sameDay)
startDay(false, dayName);
startHour(false, startDate.toFormat("HH:mm"));
for(const location of locations.values()) {
- startLocation(location.id);
+ startLocation(location.id, false);
}
for(const role of roles.values()) {
- startRole(role.id);
+ startRole(role.id, false);
}
}
@@ -477,19 +537,19 @@ function tableElementsFromStretches(
const durationMs = end - cutSpan.start.ts;
for (const location of locations.values()) {
- const rows = locationRows.get(location.id)!;
- const row = rows[rows.length - 1];
const slots = cutSpan.locations.get(location.id) ?? new Set();
- if (!setEquals(slots, row.slots)) {
- startLocation(location.id, slots);
+ const group = locationGroups.get(location.id)!;
+ const existing = new Set(group.map(row => row[row.length - 1].slot).filter(slot => slot));
+ if (!setEquals(slots, existing)) {
+ startLocation(location.id, false, slots)
}
}
for (const role of roles.values()) {
- const rows = roleRows.get(role.id)!;
- const row = rows[rows.length - 1];
const slots = cutSpan.roles.get(role.id) ?? new Set();
- if (!setEquals(slots, row.slots)) {
- startRole(role.id, slots);
+ const group = roleGroups.get(role.id)!;
+ const existing = new Set(group.map(row => row[row.length - 1].slot).filter(slot => slot));
+ if (!setEquals(slots, existing)) {
+ startRole(role.id, false, slots);
}
}
@@ -522,13 +582,17 @@ function tableElementsFromStretches(
columnGroups,
dayHeaders: dayHeaders.filter(day => day.span),
hourHeaders: hourHeaders.filter(hour => hour.span),
- locationRows: new Map([...locationRows]
- .filter(([_, cells]) => cells.some(cell => cell.slots.size))
- .map(([id, cells]) => [id, cells.filter(cell => cell.span)]))
+ locationGroups: new Map([...locationGroups]
+ .filter(([_, rows]) => rows.length)
+ .map(([id, rows]) => [
+ id, rows.map(row => row.filter(cell => cell.span))
+ ]))
,
- roleRows: new Map([...roleRows]
- .filter(([_, cells]) => cells.some(cell => cell.slots.size))
- .map(([id, cells]) => [id, cells.filter(cell => cell.span)]))
+ roleGroups: new Map([...roleGroups]
+ .filter(([_, rows]) => rows.length)
+ .map(([id, rows]) => [
+ id, rows.map(row => row.filter(cell => cell.span))
+ ]))
,
eventBySlotId,
};
@@ -579,8 +643,8 @@ const totalColumns = computed(() => elements.value.totalColumns);
const columnGroups = computed(() => elements.value.columnGroups);
const dayHeaders = computed(() => elements.value.dayHeaders);
const hourHeaders = computed(() => elements.value.hourHeaders);
-const locationRows = computed(() => elements.value.locationRows);
-const roleRows = computed(() => elements.value.roleRows);
+const locationGroups = computed(() => elements.value.locationGroups);
+const roleGroups = computed(() => elements.value.roleGroups);
const now = useState(() => Math.round(Date.now() / oneMinMs) * oneMinMs);
const interval = ref();