Refactor ClientSchedule to mutable types
All checks were successful
/ build (push) Successful in 1m30s
/ deploy (push) Successful in 16s

Use a single mutable location, event, slot, etc, for each unique
resource that keeps track of the local editable client copy and
the server copy of the data contained in it.

This makes it much simpler to update these data structures as I can take
advantage of the v-model bindings in Vue.js and work with the system
instead of against it.
This commit is contained in:
Hornwitser 2025-06-23 22:46:39 +02:00
parent d48fb035b4
commit e3ff872b5c
16 changed files with 1213 additions and 1125 deletions

View file

@ -91,41 +91,39 @@
<td>
<input
type="text"
:value="es.name"
@input="editEvent(es, { name: ($event as any).target.value })"
v-model="es.event.name"
>
</td>
<td>{{ status(es) }}</td>
<td>
<select
:value="es.location.id"
:value="es.locationId"
@change="editEventSlot(es, { locationId: parseInt(($event as any).target.value) })"
>
<option
v-for="location in schedule.locations.values()"
:key="location.id"
:value="location.id"
:selected="location.id === es.location.id"
:selected="location.id === es.locationId"
>{{ location.name }}</option>
</select>
</td>
<td>
<AssignedCrew
:edit="true"
:modelValue="es.assigned"
@update:modelValue="editEventSlot(es, { assigned: $event })"
v-model="es.slot.assigned"
/>
</td>
<td>
<button
:disabled="es.deleted"
type="button"
@click="editEventSlot(es, { deleted: true })"
@click="es.slot.deleted = true"
>Remove</button>
<button
v-if="schedule.isModifiedEventSlot(es.id)"
v-if="es.slot.isModified()"
type="button"
@click="revertEventSlot(es.id)"
@click="es.event.discardSlot(es.slot.id)"
>Revert</button>
</td>
</template>
@ -185,7 +183,7 @@
<td>{{ es.end.diff(es.start).toFormat('hh:mm') }}</td>
<td>{{ es.name }}</td>
<td>{{ status(es) }}</td>
<td>{{ es.location.id }}</td>
<td>{{ es.locationId }}</td>
<td><AssignedCrew :modelValue="es.assigned" :edit="false" /></td>
</template>
</tr>
@ -213,7 +211,7 @@ interface EventSlot {
event: ClientScheduleEvent,
slot: ClientScheduleEventSlot,
name: string,
location: ClientScheduleLocation,
locationId: Id,
assigned: Set<Id>,
start: DateTime,
end: DateTime,
@ -290,48 +288,30 @@ function durationFromTime(time: string) {
return duration;
}
const newEventName = ref("");
function editEvent(
eventSlot: EventSlot,
edits: Parameters<ClientSchedule["editEvent"]>[1],
) {
schedule.value.editEvent(eventSlot.event, edits);
}
function editEventSlot(
eventSlot: EventSlot,
edits: {
deleted?: boolean,
start?: string,
end?: string,
duration?: string,
locationId?: Id,
assigned?: Set<Id>,
}
) {
const computedEdits: Parameters<ClientSchedule["editEventSlot"]>[1] = {
deleted: edits.deleted,
assigned: edits.assigned,
};
if (edits.start) {
const start = DateTime.fromISO(edits.start, { zone: accountStore.activeTimezone, locale: accountStore.activeLocale });
computedEdits.start = start;
computedEdits.end = start.plus(eventSlot.end.diff(eventSlot.start));
eventSlot.slot.start = start;
eventSlot.slot.end = start.plus(eventSlot.end.diff(eventSlot.start));
}
if (edits.end !== undefined) {
computedEdits.end = endFromTime(eventSlot.start, edits.end);
eventSlot.slot.end = endFromTime(eventSlot.start, edits.end);
}
if (edits.duration !== undefined) {
computedEdits.end = eventSlot.start.plus(durationFromTime(edits.duration));
eventSlot.slot.end = eventSlot.start.plus(durationFromTime(edits.duration));
}
if (edits.locationId !== undefined) {
const location = schedule.value.locations.get(edits.locationId);
if (location)
computedEdits.locations = [location];
eventSlot.slot.locationIds = new Set([edits.locationId]);
}
schedule.value.editEventSlot(eventSlot.slot, computedEdits);
}
function revertEventSlot(id: Id) {
schedule.value.restoreEventSlot(id);
}
function newEventSlot(options: { start?: DateTime, end?: DateTime } = {}) {
const name = newEventName.value;
@ -341,8 +321,7 @@ function newEventSlot(options: { start?: DateTime, end?: DateTime } = {}) {
alert("Invalid event");
return;
}
const location = schedule.value.locations.get(newEventLocation.value!);
if (!location) {
if (newEventLocation.value === undefined) {
alert("Invalid location");
return;
}
@ -367,18 +346,19 @@ function newEventSlot(options: { start?: DateTime, end?: DateTime } = {}) {
alert("Invalid start and/or end time");
return;
}
const slot = new ClientScheduleEventSlot(
const slot = ClientScheduleEventSlot.create(
schedule.value,
schedule.value.nextClientId--,
false,
event.id,
start,
end,
[location],
new Set([newEventLocation.value]),
new Set(),
0,
);
schedule.value.eventSlots.set(slot.id, slot);
event.slotIds.add(slot.id);
newEventName.value = "";
schedule.value.setEventSlot(slot);
}
const oneHourMs = 60 * 60 * 1000;
@ -399,8 +379,8 @@ const eventSlots = computed(() => {
for (const slot of event.slots.values()) {
if (props.eventSlotFilter && !props.eventSlotFilter(slot))
continue;
for (const location of slot.locations) {
if (props.locationId !== undefined && location.id !== props.locationId)
for (const locationId of slot.locationIds) {
if (props.locationId !== undefined && locationId !== props.locationId)
continue;
data.push({
type: "slot",
@ -409,7 +389,7 @@ const eventSlots = computed(() => {
event,
slot,
name: event.name,
location,
locationId,
assigned: slot.assigned ?? [],
start: slot.start,
end: slot.end,