Refactor API types and sync logic
Rename and refactor the types passed over the API to be based on an entity that's either living or a tombstone. A living entity has a deleted property that's either undefined or false, while a tombstone has a deleted property set to true. All entities have a numeric id and an updatedAt timestamp. To sync entities, an array of replacements are passed around. Living entities are replaced with tombstones when they're deleted. And tombstones are replaced with living entities when restored.
This commit is contained in:
parent
251e83f640
commit
fe06d0d6bd
36 changed files with 1242 additions and 834 deletions
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="schedule.deleted">
|
||||
Error: Unexpected deleted schedule.
|
||||
</div>
|
||||
<div v-else>
|
||||
<Timetable :schedule="schedulePreview" :eventSlotFilter :shiftSlotFilter />
|
||||
<table>
|
||||
<thead>
|
||||
|
@ -47,7 +50,7 @@
|
|||
v-model="newEventLocation"
|
||||
>
|
||||
<option
|
||||
v-for="location in schedule.locations"
|
||||
v-for="location in schedule.locations?.filter(l => !l.deleted)"
|
||||
:key="location.id"
|
||||
:value="location.id"
|
||||
:selected="location.id === newEventLocation"
|
||||
|
@ -99,14 +102,14 @@
|
|||
<td>{{ status(es) }}</td>
|
||||
<td>
|
||||
<select
|
||||
:value="es.location"
|
||||
@change="editEventSlot(es, { location: ($event as any).target.value })"
|
||||
:value="es.locationId"
|
||||
@change="editEventSlot(es, { locationId: parseInt(($event as any).target.value) })"
|
||||
>
|
||||
<option
|
||||
v-for="location in schedule.locations"
|
||||
v-for="location in schedule.locations?.filter(l => !l.deleted)"
|
||||
:key="location.id"
|
||||
:value="location.id"
|
||||
:selected="location.id === es.location"
|
||||
:selected="location.id === es.locationId"
|
||||
>{{ location.name }}</option>
|
||||
</select>
|
||||
</td>
|
||||
|
@ -124,7 +127,7 @@
|
|||
@click="delEventSlot(es)"
|
||||
>Remove</button>
|
||||
<button
|
||||
v-if="changes.some(c => c.data.id === es.id)"
|
||||
v-if="changes.some(c => c.id === es.id)"
|
||||
type="button"
|
||||
@click="revertEventSlot(es.id)"
|
||||
>Revert</button>
|
||||
|
@ -186,7 +189,7 @@
|
|||
<td>{{ es.end.diff(es.start).toFormat('hh:mm') }}</td>
|
||||
<td>{{ es.name }}</td>
|
||||
<td>{{ status(es) }}</td>
|
||||
<td>{{ es.location }}</td>
|
||||
<td>{{ es.locationId }}</td>
|
||||
<td><AssignedCrew :modelValue="es.assigned" :edit="false" /></td>
|
||||
</template>
|
||||
</tr>
|
||||
|
@ -205,13 +208,13 @@
|
|||
<b>EventSlot changes</b>
|
||||
<ol>
|
||||
<li v-for="change in changes">
|
||||
<pre><code>{{ JSON.stringify((({ event, slot, ...data }) => ({ op: change.op, data }))(change.data as any), undefined, " ") }}</code></pre>
|
||||
<pre><code>{{ JSON.stringify((({ event, slot, ...data }) => data)(change as any), undefined, " ") }}</code></pre>
|
||||
</li>
|
||||
</ol>
|
||||
<b>ScheduleEvent changes</b>
|
||||
<ol>
|
||||
<li v-for="change in scheduleChanges">
|
||||
<pre><code>{{ JSON.stringify((({ event, slot, ...data }) => ({ op: change.op, data }))(change.data as any), undefined, " ") }}</code></pre>
|
||||
<pre><code>{{ JSON.stringify((({ event, slot, ...data }) => data)(change as any), undefined, " ") }}</code></pre>
|
||||
</li>
|
||||
</ol>
|
||||
</details>
|
||||
|
@ -220,25 +223,28 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import type { ChangeRecord, Schedule, ScheduleEvent, ScheduleLocation, ShiftSlot, TimeSlot } from '~/shared/types/schedule';
|
||||
import { applyChangeArray } from '~/shared/utils/changes';
|
||||
import { enumerate, pairs, toId } from '~/shared/utils/functions';
|
||||
import type { ApiSchedule, ApiScheduleEvent, ApiScheduleEventSlot, ApiScheduleShift, ApiScheduleShiftSlot } from '~/shared/types/api';
|
||||
import type { Entity } from '~/shared/types/common';
|
||||
import { enumerate, pairs } from '~/shared/utils/functions';
|
||||
import { applyUpdatesToArray } from '~/shared/utils/update';
|
||||
|
||||
const props = defineProps<{
|
||||
edit?: boolean,
|
||||
location?: string,
|
||||
eventSlotFilter?: (slot: TimeSlot) => boolean,
|
||||
shiftSlotFilter?: (slot: ShiftSlot) => boolean,
|
||||
locationId?: number,
|
||||
eventSlotFilter?: (slot: ApiScheduleEventSlot) => boolean,
|
||||
shiftSlotFilter?: (slot: ApiScheduleShiftSlot) => boolean,
|
||||
}>();
|
||||
|
||||
interface EventSlot {
|
||||
type: "slot",
|
||||
id: string,
|
||||
event?: ScheduleEvent,
|
||||
slot?: TimeSlot,
|
||||
origLocation: string,
|
||||
id: number,
|
||||
updatedAt: string,
|
||||
deleted?: boolean,
|
||||
event?: Extract<ApiScheduleEvent, { deleted?: false }>,
|
||||
slot?: ApiScheduleEventSlot,
|
||||
origLocation: number,
|
||||
name: string,
|
||||
location: string,
|
||||
locationId: number,
|
||||
assigned: number[],
|
||||
start: DateTime,
|
||||
end: DateTime,
|
||||
|
@ -250,57 +256,69 @@ interface Gap {
|
|||
event?: undefined,
|
||||
slot?: undefined,
|
||||
name?: undefined,
|
||||
location?: string,
|
||||
locationId?: number,
|
||||
start: DateTime,
|
||||
end: DateTime,
|
||||
}
|
||||
|
||||
function status(eventSlot: EventSlot) {
|
||||
if (schedule.value.deleted) {
|
||||
throw new Error("Unexpected deleted schedule");
|
||||
}
|
||||
if (
|
||||
!eventSlot.event
|
||||
|| eventSlot.event.name !== eventSlot.name
|
||||
) {
|
||||
const event = schedule.value.events.find(event => event.name === eventSlot.name);
|
||||
const event = schedule.value.events?.find(event => !event.deleted && event.name === eventSlot.name);
|
||||
return event ? "L" : "N";
|
||||
}
|
||||
return eventSlot.event.slots.length === 1 ? "" : eventSlot.event.slots.length;
|
||||
}
|
||||
|
||||
// Filter out set records where a del record exists for the same id.
|
||||
function filterSetOps<T extends { op: "set" | "del", data: { id: string }}>(changes: T[]) {
|
||||
const deleteIds = new Set(changes.filter(c => c.op === "del").map(c => c.data.id));
|
||||
return changes.filter(c => c.op !== "set" || !deleteIds.has(c.data.id));
|
||||
function filterSetOps<T extends Entity>(changes: T[]) {
|
||||
const deleteIds = new Set(changes.filter(c => c.deleted).map(c => c.id));
|
||||
return changes.filter(c => c.deleted || !deleteIds.has(c.id));
|
||||
}
|
||||
|
||||
function findEvent(
|
||||
eventSlot: EventSlot,
|
||||
changes: ChangeRecord<ScheduleEvent>[],
|
||||
schedule: Schedule,
|
||||
changes: ApiScheduleEvent[],
|
||||
schedule: ApiSchedule,
|
||||
) {
|
||||
let setEvent = changes.find(
|
||||
c => c.op === "set" && c.data.name === eventSlot.name
|
||||
)?.data as ScheduleEvent | undefined;
|
||||
if (schedule.deleted) {
|
||||
throw new Error("Unexpected deleted schedule");
|
||||
}
|
||||
let setEvent = changes.filter(
|
||||
c => !c.deleted
|
||||
).find(
|
||||
c => c.name === eventSlot.name
|
||||
);
|
||||
if (!setEvent && eventSlot.event && eventSlot.event.name === eventSlot.name) {
|
||||
setEvent = eventSlot.event;
|
||||
}
|
||||
if (!setEvent) {
|
||||
setEvent = schedule.events.find(e => e.name === eventSlot.name);
|
||||
setEvent = schedule.events?.filter(e => !e.deleted).find(e => e.name === eventSlot.name);
|
||||
}
|
||||
let delEvent;
|
||||
if (eventSlot.event) {
|
||||
delEvent = changes.find(
|
||||
c => c.op === "set" && c.data.name === eventSlot.event!.name
|
||||
)?.data as ScheduleEvent | undefined;
|
||||
delEvent = changes.filter(c => !c.deleted).find(
|
||||
c => c.name === eventSlot.event!.name
|
||||
);
|
||||
if (!delEvent) {
|
||||
delEvent = schedule.events.find(e => e.name === eventSlot.event!.name);
|
||||
delEvent = schedule.events?.filter(e => !e.deleted).find(e => e.name === eventSlot.event!.name);
|
||||
}
|
||||
}
|
||||
return { setEvent, delEvent };
|
||||
}
|
||||
|
||||
function removeSlotLocation(event: ScheduleEvent, oldSlot: TimeSlot, location: string) {
|
||||
function removeSlotLocation(
|
||||
event: Extract<ApiScheduleEvent, { deleted?: false }>,
|
||||
oldSlot: ApiScheduleEventSlot,
|
||||
locationId: number
|
||||
) {
|
||||
// If location is an exact match remove the whole slot
|
||||
if (oldSlot.locations.length === 1 && oldSlot.locations[0] === location) {
|
||||
if (oldSlot.locationIds.length === 1 && oldSlot.locationIds[0] === locationId) {
|
||||
return {
|
||||
...event,
|
||||
slots: event.slots.filter(s => s.id !== oldSlot.id),
|
||||
|
@ -312,18 +330,21 @@ function removeSlotLocation(event: ScheduleEvent, oldSlot: TimeSlot, location: s
|
|||
slots: event.slots.map(
|
||||
s => s.id !== oldSlot.id ? s : {
|
||||
...s,
|
||||
locations: s.locations.filter(l => l !== location)
|
||||
locationIds: s.locationIds.filter(id => id !== locationId)
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function mergeSlot(event: ScheduleEvent, eventSlot: EventSlot): ScheduleEvent {
|
||||
function mergeSlot(
|
||||
event: Extract<ApiScheduleEvent, { deleted?: false }>,
|
||||
eventSlot: EventSlot,
|
||||
): Extract<ApiScheduleEvent, { deleted?: false }> {
|
||||
if (schedule.value.deleted) {
|
||||
throw new Error("Unexpected deleted schedule");
|
||||
}
|
||||
const oldSlot = event.slots.find(s => s.id === eventSlot.id);
|
||||
const nextId = Math.max(-1, ...event.slots.map(s => {
|
||||
const id = /-(\d+)$/.exec(s.id)?.[1];
|
||||
return id ? parseInt(id) : 0;
|
||||
})) + 1;
|
||||
const nextId = Math.max(0, ...schedule.value.events?.filter(e => !e.deleted).flatMap(e => e.slots.map(slot => slot.id)) ?? []) + 1;
|
||||
const start = eventSlot.start.toUTC().toISO({ suppressSeconds: true })!;
|
||||
const end = eventSlot.end.toUTC().toISO({ suppressSeconds: true })!;
|
||||
|
||||
|
@ -332,7 +353,7 @@ function mergeSlot(event: ScheduleEvent, eventSlot: EventSlot): ScheduleEvent {
|
|||
oldSlot
|
||||
&& oldSlot.id === eventSlot.id
|
||||
&& (
|
||||
oldSlot.locations.length <= 1
|
||||
oldSlot.locationIds.length <= 1
|
||||
|| oldSlot.start === start && oldSlot.end === end
|
||||
)
|
||||
) {
|
||||
|
@ -341,12 +362,12 @@ function mergeSlot(event: ScheduleEvent, eventSlot: EventSlot): ScheduleEvent {
|
|||
slots: event.slots.map(s => {
|
||||
if (s.id !== oldSlot.id)
|
||||
return s;
|
||||
const locations = new Set(s.locations);
|
||||
locations.delete(eventSlot.origLocation)
|
||||
locations.add(eventSlot.location);
|
||||
const locationIds = new Set(s.locationIds);
|
||||
locationIds.delete(eventSlot.origLocation)
|
||||
locationIds.add(eventSlot.locationId);
|
||||
return {
|
||||
...s,
|
||||
locations: [...locations],
|
||||
locationIds: [...locationIds],
|
||||
assigned: eventSlot.assigned.length ? eventSlot.assigned : undefined,
|
||||
start,
|
||||
end,
|
||||
|
@ -363,8 +384,8 @@ function mergeSlot(event: ScheduleEvent, eventSlot: EventSlot): ScheduleEvent {
|
|||
return {
|
||||
...event,
|
||||
slots: [...event.slots, {
|
||||
id: oldSlot ? oldSlot.id : `${event.id}-${nextId}`,
|
||||
locations: [eventSlot.location],
|
||||
id: oldSlot ? oldSlot.id : nextId,
|
||||
locationIds: [eventSlot.locationId],
|
||||
assigned: eventSlot.assigned.length ? eventSlot.assigned : undefined,
|
||||
start,
|
||||
end,
|
||||
|
@ -373,31 +394,29 @@ function mergeSlot(event: ScheduleEvent, eventSlot: EventSlot): ScheduleEvent {
|
|||
}
|
||||
|
||||
const scheduleChanges = computed(() => {
|
||||
let eventChanges: ChangeRecord<ScheduleEvent>[] = [];
|
||||
let eventChanges: Extract<ApiScheduleEvent, { deleted?: false}>[] = [];
|
||||
for (const change of filterSetOps(changes.value)) {
|
||||
if (change.op === "set") {
|
||||
let { setEvent, delEvent } = findEvent(change.data, eventChanges, schedule.value);
|
||||
if (!change.deleted) {
|
||||
let { setEvent, delEvent } = findEvent(change, eventChanges, schedule.value);
|
||||
if (delEvent && delEvent !== setEvent) {
|
||||
eventChanges = removeSlot(eventChanges, delEvent, change.data);
|
||||
eventChanges = removeSlot(eventChanges, delEvent, change);
|
||||
}
|
||||
if (!setEvent) {
|
||||
setEvent = {
|
||||
id: toId(change.data.name),
|
||||
name: change.data.name,
|
||||
id: Math.floor(Math.random() * -1000), // XXX This wont work.
|
||||
updatedAt: "",
|
||||
name: change.name,
|
||||
crew: true,
|
||||
slots: [],
|
||||
};
|
||||
}
|
||||
|
||||
eventChanges = replaceChange({
|
||||
op: "set",
|
||||
data: mergeSlot(setEvent, change.data),
|
||||
}, eventChanges);
|
||||
eventChanges = replaceChange(mergeSlot(setEvent, change), eventChanges);
|
||||
|
||||
} else if (change.op === "del") {
|
||||
let { delEvent } = findEvent(change.data, eventChanges, schedule.value);
|
||||
} else if (change.deleted) {
|
||||
let { delEvent } = findEvent(change, eventChanges, schedule.value);
|
||||
if (delEvent) {
|
||||
eventChanges = removeSlot(eventChanges, delEvent, change.data);
|
||||
eventChanges = removeSlot(eventChanges, delEvent, change);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -405,21 +424,28 @@ const scheduleChanges = computed(() => {
|
|||
});
|
||||
|
||||
const schedulePreview = computed(() => {
|
||||
const events = [...schedule.value.events]
|
||||
applyChangeArray(scheduleChanges.value, events);
|
||||
if (schedule.value.deleted) {
|
||||
throw new Error("Unexpected deleted schedule");
|
||||
}
|
||||
const events = [...schedule.value.events ?? []]
|
||||
applyUpdatesToArray(scheduleChanges.value, events);
|
||||
return {
|
||||
...schedule.value,
|
||||
events,
|
||||
};
|
||||
});
|
||||
|
||||
function removeSlot(eventChanges: ChangeRecord<ScheduleEvent>[], event: ScheduleEvent, eventSlot: EventSlot) {
|
||||
function removeSlot(
|
||||
eventChanges: Extract<ApiScheduleEvent, { deleted?: false }>[],
|
||||
event: Extract<ApiScheduleEvent, { deleted?: false }>,
|
||||
eventSlot: EventSlot,
|
||||
) {
|
||||
let oldSlot = event.slots.find(s => s.id === eventSlot.id);
|
||||
if (oldSlot) {
|
||||
eventChanges = replaceChange({
|
||||
op: "set",
|
||||
data: removeSlotLocation(event, oldSlot, eventSlot.origLocation),
|
||||
}, eventChanges);
|
||||
eventChanges = replaceChange(
|
||||
removeSlotLocation(event, oldSlot, eventSlot.origLocation),
|
||||
eventChanges,
|
||||
);
|
||||
}
|
||||
return eventChanges;
|
||||
}
|
||||
|
@ -427,17 +453,15 @@ function removeSlot(eventChanges: ChangeRecord<ScheduleEvent>[], event: Schedule
|
|||
const accountStore = useAccountStore();
|
||||
const schedule = await useSchedule();
|
||||
|
||||
type EventSlotChange = { op: "set" | "del", data: EventSlot } ;
|
||||
const changes = ref<EventSlot[]>([]);
|
||||
const removed = computed(() => new Set(changes.value.filter(c => c.deleted).map(c => c.id)));
|
||||
|
||||
const changes = ref<EventSlotChange[]>([]);
|
||||
const removed = computed(() => new Set(changes.value.filter(c => c.op === "del").map(c => c.data.id)));
|
||||
|
||||
function replaceChange<T extends { op: "set" | "del", data: { id: string }}>(
|
||||
function replaceChange<T extends Entity>(
|
||||
change: T,
|
||||
changes: T[],
|
||||
) {
|
||||
const index = changes.findIndex(item => (
|
||||
item.op === change.op && item.data.id === change.data.id
|
||||
item.deleted === change.deleted && item.id === change.id
|
||||
));
|
||||
const copy = [...changes];
|
||||
if (index !== -1)
|
||||
|
@ -446,8 +470,8 @@ function replaceChange<T extends { op: "set" | "del", data: { id: string }}>(
|
|||
copy.push(change);
|
||||
return copy;
|
||||
}
|
||||
function revertChange<T extends { op: "set" | "del", data: { id: string }}>(id: string, changes: T[]) {
|
||||
return changes.filter(change => change.data.id !== id);
|
||||
function revertChange<T extends Entity>(id: number, changes: T[]) {
|
||||
return changes.filter(change => change.id !== id);
|
||||
}
|
||||
|
||||
const oneDayMs = 24 * 60 * 60 * 1000;
|
||||
|
@ -472,9 +496,9 @@ const newEventEnd = computed({
|
|||
newEventDuration.value = dropDay(end.diff(start)).toFormat("hh:mm");
|
||||
},
|
||||
});
|
||||
const newEventLocation = ref(props.location);
|
||||
watch(() => props.location, () => {
|
||||
newEventLocation.value = props.location;
|
||||
const newEventLocation = ref(props.locationId);
|
||||
watch(() => props.locationId, () => {
|
||||
newEventLocation.value = props.locationId;
|
||||
});
|
||||
|
||||
function endFromTime(start: DateTime, time: string) {
|
||||
|
@ -499,7 +523,7 @@ function editEventSlot(
|
|||
end?: string,
|
||||
duration?: string,
|
||||
name?: string,
|
||||
location?: string,
|
||||
locationId?: number,
|
||||
assigned?: number[],
|
||||
}
|
||||
) {
|
||||
|
@ -512,7 +536,6 @@ function editEventSlot(
|
|||
};
|
||||
}
|
||||
if (edits.end !== undefined) {
|
||||
|
||||
eventSlot = {
|
||||
...eventSlot,
|
||||
end: endFromTime(eventSlot.start, edits.end),
|
||||
|
@ -530,10 +553,10 @@ function editEventSlot(
|
|||
name: edits.name,
|
||||
};
|
||||
}
|
||||
if (edits.location !== undefined) {
|
||||
if (edits.locationId !== undefined) {
|
||||
eventSlot = {
|
||||
...eventSlot,
|
||||
location: edits.location,
|
||||
locationId: edits.locationId,
|
||||
};
|
||||
}
|
||||
if (edits.assigned !== undefined) {
|
||||
|
@ -542,20 +565,22 @@ function editEventSlot(
|
|||
assigned: edits.assigned,
|
||||
};
|
||||
}
|
||||
const change = { op: "set" as const, data: eventSlot };
|
||||
changes.value = replaceChange(change, changes.value);
|
||||
changes.value = replaceChange(eventSlot, changes.value);
|
||||
}
|
||||
function delEventSlot(eventSlot: EventSlot) {
|
||||
const change = { op: "del" as const, data: eventSlot };
|
||||
const change = {
|
||||
...eventSlot,
|
||||
deleted: true,
|
||||
};
|
||||
changes.value = replaceChange(change, changes.value);
|
||||
}
|
||||
function revertEventSlot(id: string) {
|
||||
function revertEventSlot(id: number) {
|
||||
changes.value = revertChange(id, changes.value);
|
||||
}
|
||||
function newEventSlot(options: { start?: DateTime, end?: DateTime } = {}) {
|
||||
const name = newEventName.value;
|
||||
const location = newEventLocation.value;
|
||||
if (!location) {
|
||||
const locationId = newEventLocation.value;
|
||||
if (!locationId) {
|
||||
alert("Invalid location");
|
||||
return;
|
||||
}
|
||||
|
@ -580,27 +605,30 @@ function newEventSlot(options: { start?: DateTime, end?: DateTime } = {}) {
|
|||
alert("Invalid start and/or end time");
|
||||
return;
|
||||
}
|
||||
const change: ChangeRecord<EventSlot> = {
|
||||
op: "set" as const,
|
||||
data: {
|
||||
type: "slot",
|
||||
id: `$new-${Date.now()}`,
|
||||
name,
|
||||
origLocation: location,
|
||||
location,
|
||||
assigned: [],
|
||||
start,
|
||||
end,
|
||||
},
|
||||
const change: EventSlot = {
|
||||
type: "slot",
|
||||
updatedAt: "",
|
||||
id: Math.floor(Math.random() * -1000), // XXX this wont work.
|
||||
name,
|
||||
origLocation: locationId,
|
||||
locationId,
|
||||
assigned: [],
|
||||
start,
|
||||
end,
|
||||
};
|
||||
newEventName.value = "";
|
||||
changes.value = replaceChange(change, changes.value);
|
||||
}
|
||||
|
||||
async function saveEventSlots() {
|
||||
try {
|
||||
await $fetch("/api/schedule", {
|
||||
method: "PATCH",
|
||||
body: { events: scheduleChanges.value },
|
||||
body: {
|
||||
id: 111,
|
||||
updatedAt: "",
|
||||
events: scheduleChanges.value,
|
||||
} satisfies ApiSchedule,
|
||||
});
|
||||
changes.value = [];
|
||||
} catch (err: any) {
|
||||
|
@ -620,30 +648,36 @@ function gapFormat(gap: Gap) {
|
|||
}
|
||||
|
||||
const eventSlots = computed(() => {
|
||||
if (schedule.value.deleted) {
|
||||
throw new Error("Unexpected deleted schedule");
|
||||
}
|
||||
const data: (EventSlot | Gap)[] = [];
|
||||
for (const event of schedule.value.events) {
|
||||
for (const event of schedule.value.events ?? []) {
|
||||
if (event.deleted)
|
||||
continue;
|
||||
for (const slot of event.slots) {
|
||||
if (props.eventSlotFilter && !props.eventSlotFilter(slot))
|
||||
continue;
|
||||
for (const location of slot.locations) {
|
||||
if (props.location !== undefined && location !== props.location)
|
||||
for (const locationId of slot.locationIds) {
|
||||
if (props.locationId !== undefined && locationId !== props.locationId)
|
||||
continue;
|
||||
data.push({
|
||||
type: "slot",
|
||||
id: slot.id,
|
||||
updatedAt: "",
|
||||
event,
|
||||
slot,
|
||||
name: event.name,
|
||||
location,
|
||||
locationId,
|
||||
assigned: slot.assigned ?? [],
|
||||
origLocation: location,
|
||||
origLocation: locationId,
|
||||
start: DateTime.fromISO(slot.start, { zone: accountStore.activeTimezone, locale: "en-US" }),
|
||||
end: DateTime.fromISO(slot.end, { zone: accountStore.activeTimezone, locale: "en-US" }),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
applyChangeArray(changes.value.filter(change => change.op === "set"), data as EventSlot[]);
|
||||
applyUpdatesToArray(changes.value.filter(c => !c.deleted), data as EventSlot[]);
|
||||
data.sort((a, b) => a.start.toMillis() - b.start.toMillis() || a.end.toMillis() - b.end.toMillis());
|
||||
|
||||
// Insert gaps
|
||||
|
@ -654,7 +688,7 @@ const eventSlots = computed(() => {
|
|||
if (maxEnd < second.start.toMillis()) {
|
||||
gaps.push([index, {
|
||||
type: "gap",
|
||||
location: props.location,
|
||||
locationId: props.locationId,
|
||||
start: DateTime.fromMillis(maxEnd, { locale: "en-US" }),
|
||||
end: second.start,
|
||||
}]);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue