Add editing of client schedule entities
Add utility methods to more easily edit the fields of a single entity in the schedule, along with a modification flag and is modified utility to check for changes having been made.
This commit is contained in:
parent
faffe48706
commit
61734d4152
2 changed files with 192 additions and 84 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { ClientSchedule, ClientScheduleEventSlot, ClientScheduleLocation, toIso } from "./client-schedule";
|
import { ClientEntity, ClientSchedule, ClientScheduleEventSlot, ClientScheduleLocation, toIso } from "./client-schedule";
|
||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test } from "vitest";
|
||||||
import type { ApiSchedule } from "~/shared/types/api";
|
import type { ApiSchedule } from "~/shared/types/api";
|
||||||
import type { Living } from "~/shared/types/common";
|
import type { Living } from "~/shared/types/common";
|
||||||
|
@ -198,87 +198,89 @@ describe("class ClientSchedule", () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test("create location", () => {
|
const entityTests: [string, (schedule: ClientSchedule) => ClientEntity][] = [
|
||||||
|
[
|
||||||
|
"location",
|
||||||
|
() => new ClientScheduleLocation(3, now, false, "New location", "")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"event",
|
||||||
|
() => new ClientScheduleEvent(3, now, false, "New location", false, "", false, "", 0, [])
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"role",
|
||||||
|
() => new ClientScheduleRole(3, now, false, "New location", "")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"shift",
|
||||||
|
(schedule) => new ClientScheduleShift(3, now, false, schedule.roles.get(1)!, "New location", "", [])
|
||||||
|
],
|
||||||
|
] as const;
|
||||||
|
for (const [name, create] of entityTests) {
|
||||||
|
describe(name, () => {
|
||||||
|
const Name = name[0].toUpperCase() + name.slice(1);
|
||||||
|
test(`create`, () => {
|
||||||
const schedule = fixtureClientSchedule();
|
const schedule = fixtureClientSchedule();
|
||||||
const location = new ClientScheduleLocation(3, now, false, "New location", "");
|
const entity = create(schedule);
|
||||||
schedule.setLocation(location);
|
expect(schedule.modified).toBe(false);
|
||||||
expect(schedule.originalLocations.get(3)).toBe(undefined);
|
// Create
|
||||||
expect(schedule.locations.get(3)).toBe(location);
|
(schedule as any)[`set${Name}`](entity);
|
||||||
|
// Check
|
||||||
|
expect(schedule.modified).toBe(true);
|
||||||
|
expect((schedule as any)[`isModified${Name}`](entity.id)).toBe(true);
|
||||||
|
expect((schedule as any)[`original${Name}s`].get(entity.id)).toBe(undefined);
|
||||||
|
expect((schedule as any)[`${name}s`].get(entity.id)).toBe(entity);
|
||||||
});
|
});
|
||||||
|
test("edit", () => {
|
||||||
test("update location", () => {
|
|
||||||
const schedule = fixtureClientSchedule();
|
const schedule = fixtureClientSchedule();
|
||||||
const original = schedule.locations.get(1)!;
|
const original = (schedule as any)[`${name}s`].get(1);
|
||||||
const copy = original.clone();
|
expect(schedule.modified).toBe(false);
|
||||||
copy.name = "Modified Location";
|
expect((schedule as any)[`isModified${Name}`](1)).toBe(false);
|
||||||
schedule.setLocation(copy);
|
// Edit
|
||||||
expect(schedule.originalLocations.get(1)).toBe(original);
|
(schedule as any)[`edit${Name}`](original, { name: `Modified ${name}` })
|
||||||
expect(schedule.locations.get(1)).toBe(copy);
|
// Check
|
||||||
expect(schedule.events.get(1)!.slots[0].locations[0]).toBe(copy);
|
expect(schedule.modified).toBe(true);
|
||||||
|
expect((schedule as any)[`isModified${Name}`](1)).toBe(true);
|
||||||
|
expect((schedule as any)[`original${Name}s`].get(1)).toBe(original);
|
||||||
|
if (name === "location") {
|
||||||
|
expect(schedule.events.get(1)!.slots[0].locations[0]).toBe(schedule.locations.get(1));
|
||||||
|
} else if (name === "role") {
|
||||||
|
expect(schedule.shifts.get(1)!.role).toBe(schedule.roles.get(1));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
if (name === "location") {
|
||||||
test("delete location in use throws", () => {
|
test("delete location in use throws", () => {
|
||||||
const schedule = fixtureClientSchedule();
|
const schedule = fixtureClientSchedule();
|
||||||
const original = schedule.locations.get(1)!;
|
|
||||||
const copy = original.clone();
|
|
||||||
copy.deleted = true;
|
|
||||||
expect(
|
expect(
|
||||||
() => { schedule.setLocation(copy); }
|
() => { schedule.editLocation(schedule.locations.get(1)!, { deleted: true }); }
|
||||||
).toThrow(new Error('Cannot delete location, event "Up" depends on it'));
|
).toThrow(new Error('Cannot delete location, event "Up" depends on it'));
|
||||||
});
|
});
|
||||||
|
} else if (name === "role") {
|
||||||
test("delete location", () => {
|
|
||||||
const schedule = fixtureClientSchedule();
|
|
||||||
const event = schedule.events.get(1)!.clone();
|
|
||||||
event.slots = [];
|
|
||||||
schedule.setEvent(event);
|
|
||||||
const original = schedule.locations.get(1)!;
|
|
||||||
const copy = original.clone();
|
|
||||||
copy.deleted = true;
|
|
||||||
schedule.setLocation(copy);
|
|
||||||
expect(schedule.originalLocations.get(1)).toBe(original);
|
|
||||||
expect(schedule.locations.get(1)).toBe(copy);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("create role", () => {
|
|
||||||
const schedule = fixtureClientSchedule();
|
|
||||||
const role = new ClientScheduleRole(3, now, false, "New role", "");
|
|
||||||
schedule.setRole(role);
|
|
||||||
expect(schedule.originalRoles.get(3)).toBe(undefined);
|
|
||||||
expect(schedule.roles.get(3)).toBe(role);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("update role", () => {
|
|
||||||
const schedule = fixtureClientSchedule();
|
|
||||||
const original = schedule.roles.get(1)!;
|
|
||||||
const copy = original.clone();
|
|
||||||
copy.name = "Modified Role";
|
|
||||||
schedule.setRole(copy);
|
|
||||||
expect(schedule.originalRoles.get(1)).toBe(original);
|
|
||||||
expect(schedule.roles.get(1)).toBe(copy);
|
|
||||||
expect(schedule.shifts.get(1)!.role).toBe(copy);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("delete role in use throws", () => {
|
test("delete role in use throws", () => {
|
||||||
const schedule = fixtureClientSchedule();
|
const schedule = fixtureClientSchedule();
|
||||||
const original = schedule.roles.get(1)!;
|
|
||||||
const copy = original.clone();
|
|
||||||
copy.deleted = true;
|
|
||||||
expect(
|
expect(
|
||||||
() => { schedule.setRole(copy); }
|
() => { schedule.editRole(schedule.roles.get(1)!, { deleted: true }); }
|
||||||
).toThrow(new Error('Cannot delete role, shift "White" depends on it'));
|
).toThrow(new Error('Cannot delete role, shift "White" depends on it'));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
test("delete role", () => {
|
test("delete", () => {
|
||||||
const schedule = fixtureClientSchedule();
|
const schedule = fixtureClientSchedule();
|
||||||
const shift = schedule.shifts.get(1)!.clone();
|
const original = (schedule as any)[`${name}s`].get(1);
|
||||||
shift.role = schedule.roles.get(2)!;
|
expect(schedule.modified).toBe(false);
|
||||||
schedule.setShift(shift);
|
expect((schedule as any)[`isModified${Name}`](1)).toBe(false);
|
||||||
const original = schedule.roles.get(1)!;
|
// Delete
|
||||||
const copy = original.clone();
|
if (name === "location") {
|
||||||
copy.deleted = true;
|
schedule.editEvent(schedule.events.get(1)!, { deleted: true });
|
||||||
schedule.setRole(copy);
|
} else if (name === "role") {
|
||||||
expect(schedule.originalRoles.get(1)).toBe(original);
|
schedule.editShift(schedule.shifts.get(1)!, { deleted: true });
|
||||||
expect(schedule.roles.get(1)).toBe(copy);
|
}
|
||||||
|
(schedule as any)[`edit${Name}`](original, { deleted: true })
|
||||||
|
// Check
|
||||||
|
expect(schedule.modified).toBe(true);
|
||||||
|
expect((schedule as any)[`isModified${Name}`](1)).toBe(true);
|
||||||
|
expect((schedule as any)[`original${Name}s`].get(1)).toBe(original);
|
||||||
|
expect((schedule as any)[`${name}s`].get(1).deleted).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -424,6 +424,7 @@ export class ClientSchedule extends ClientEntity {
|
||||||
originalEvents: Map<Id, ClientScheduleEvent>;
|
originalEvents: Map<Id, ClientScheduleEvent>;
|
||||||
originalRoles: Map<Id, ClientScheduleRole>;
|
originalRoles: Map<Id, ClientScheduleRole>;
|
||||||
originalShifts: Map<Id, ClientScheduleShift>;
|
originalShifts: Map<Id, ClientScheduleShift>;
|
||||||
|
modified: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: 111,
|
id: 111,
|
||||||
|
@ -439,12 +440,33 @@ export class ClientSchedule extends ClientEntity {
|
||||||
this.originalEvents = new Map(events);
|
this.originalEvents = new Map(events);
|
||||||
this.originalRoles = new Map(roles);
|
this.originalRoles = new Map(roles);
|
||||||
this.originalShifts = new Map(shifts);
|
this.originalShifts = new Map(shifts);
|
||||||
|
this.modified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: ClientSchedule): boolean {
|
equals(other: ClientSchedule): boolean {
|
||||||
throw new Error("ClientSchedule.equals not implemented")
|
throw new Error("ClientSchedule.equals not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private recalcModified() {
|
||||||
|
function mapEquals<K, V>(a: Map<K, V>, b: Map<K, V>) {
|
||||||
|
if (a.size !== b.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const [key, value] of a) {
|
||||||
|
if (!b.has(key) || b.get(key) !== value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.modified = (
|
||||||
|
!mapEquals(this.locations, this.originalLocations)
|
||||||
|
|| !mapEquals(this.events, this.originalEvents)
|
||||||
|
|| !mapEquals(this.roles, this.originalRoles)
|
||||||
|
|| !mapEquals(this.shifts, this.originalShifts)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private fixLocationRefs(locations: Map<Id, ClientScheduleLocation>) {
|
private fixLocationRefs(locations: Map<Id, ClientScheduleLocation>) {
|
||||||
for (const events of [this.events, this.originalEvents]) {
|
for (const events of [this.events, this.originalEvents]) {
|
||||||
for (const event of events.values()) {
|
for (const event of events.values()) {
|
||||||
|
@ -472,6 +494,10 @@ export class ClientSchedule extends ClientEntity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isModifiedLocation(id: Id) {
|
||||||
|
return this.originalLocations.get(id) !== this.locations.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
setLocation(location: ClientScheduleLocation) {
|
setLocation(location: ClientScheduleLocation) {
|
||||||
if (location.deleted) {
|
if (location.deleted) {
|
||||||
this.checkLocationRefsForDeletion(location.id);
|
this.checkLocationRefsForDeletion(location.id);
|
||||||
|
@ -480,6 +506,22 @@ export class ClientSchedule extends ClientEntity {
|
||||||
if (!location.deleted) {
|
if (!location.deleted) {
|
||||||
this.fixLocationRefs(new Map([[location.id, location]]));
|
this.fixLocationRefs(new Map([[location.id, location]]));
|
||||||
}
|
}
|
||||||
|
this.modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editLocation(
|
||||||
|
location: ClientScheduleLocation,
|
||||||
|
edits: {
|
||||||
|
deleted?: boolean,
|
||||||
|
name?: string,
|
||||||
|
description?: string
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const copy = location.clone();
|
||||||
|
if (edits.deleted !== undefined) copy.deleted = edits.deleted;
|
||||||
|
if (edits.name !== undefined) copy.name = edits.name;
|
||||||
|
if (edits.description !== undefined) copy.description = edits.description;
|
||||||
|
this.setLocation(copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreLocation(id: Id) {
|
restoreLocation(id: Id) {
|
||||||
|
@ -491,10 +533,31 @@ export class ClientSchedule extends ClientEntity {
|
||||||
this.checkLocationRefsForDeletion(id);
|
this.checkLocationRefsForDeletion(id);
|
||||||
this.locations.delete(id);
|
this.locations.delete(id);
|
||||||
}
|
}
|
||||||
|
this.recalcModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
isModifiedEvent(id: Id) {
|
||||||
|
return this.originalEvents.get(id) !== this.events.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
setEvent(event: ClientScheduleEvent) {
|
setEvent(event: ClientScheduleEvent) {
|
||||||
this.events.set(event.id, event);
|
this.events.set(event.id, event);
|
||||||
|
this.modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editEvent(
|
||||||
|
event: ClientScheduleEvent,
|
||||||
|
edits: {
|
||||||
|
deleted?: boolean,
|
||||||
|
name?: string,
|
||||||
|
description?: string
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const copy = event.clone();
|
||||||
|
if (edits.deleted !== undefined) copy.deleted = edits.deleted;
|
||||||
|
if (edits.name !== undefined) copy.name = edits.name;
|
||||||
|
if (edits.description !== undefined) copy.description = edits.description;
|
||||||
|
this.setEvent(copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreEvent(id: Id) {
|
restoreEvent(id: Id) {
|
||||||
|
@ -526,6 +589,10 @@ export class ClientSchedule extends ClientEntity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isModifiedRole(id: Id) {
|
||||||
|
return this.originalRoles.get(id) !== this.roles.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
setRole(role: ClientScheduleRole) {
|
setRole(role: ClientScheduleRole) {
|
||||||
if (role.deleted) {
|
if (role.deleted) {
|
||||||
this.checkRoleRefsForDeletion(role.id);
|
this.checkRoleRefsForDeletion(role.id);
|
||||||
|
@ -534,6 +601,22 @@ export class ClientSchedule extends ClientEntity {
|
||||||
if (!role.deleted) {
|
if (!role.deleted) {
|
||||||
this.fixRoleRefs(new Map([[role.id, role]]));
|
this.fixRoleRefs(new Map([[role.id, role]]));
|
||||||
}
|
}
|
||||||
|
this.modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editRole(
|
||||||
|
role: ClientScheduleRole,
|
||||||
|
edits: {
|
||||||
|
deleted?: boolean,
|
||||||
|
name?: string,
|
||||||
|
description?: string
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const copy = role.clone();
|
||||||
|
if (edits.deleted !== undefined) copy.deleted = edits.deleted;
|
||||||
|
if (edits.name !== undefined) copy.name = edits.name;
|
||||||
|
if (edits.description !== undefined) copy.description = edits.description;
|
||||||
|
this.setRole(copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreRole(id: Id) {
|
restoreRole(id: Id) {
|
||||||
|
@ -545,10 +628,31 @@ export class ClientSchedule extends ClientEntity {
|
||||||
this.checkRoleRefsForDeletion(id);
|
this.checkRoleRefsForDeletion(id);
|
||||||
this.roles.delete(id);
|
this.roles.delete(id);
|
||||||
}
|
}
|
||||||
|
this.recalcModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
isModifiedShift(id: Id) {
|
||||||
|
return this.originalShifts.get(id) !== this.shifts.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
setShift(shift: ClientScheduleShift) {
|
setShift(shift: ClientScheduleShift) {
|
||||||
this.shifts.set(shift.id, shift);
|
this.shifts.set(shift.id, shift);
|
||||||
|
this.modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editShift(
|
||||||
|
shift: ClientScheduleShift,
|
||||||
|
edits: {
|
||||||
|
deleted?: boolean,
|
||||||
|
name?: string,
|
||||||
|
description?: string
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const copy = shift.clone();
|
||||||
|
if (edits.deleted !== undefined) copy.deleted = edits.deleted;
|
||||||
|
if (edits.name !== undefined) copy.name = edits.name;
|
||||||
|
if (edits.description !== undefined) copy.description = edits.description;
|
||||||
|
this.setShift(copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreShift(id: Id) {
|
restoreShift(id: Id) {
|
||||||
|
@ -558,6 +662,7 @@ export class ClientSchedule extends ClientEntity {
|
||||||
} else {
|
} else {
|
||||||
this.shifts.delete(id);
|
this.shifts.delete(id);
|
||||||
}
|
}
|
||||||
|
this.recalcModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromApi(api: Living<ApiSchedule>, opts: { zone: Zone, locale: string }) {
|
static fromApi(api: Living<ApiSchedule>, opts: { zone: Zone, locale: string }) {
|
||||||
|
@ -680,5 +785,6 @@ export class ClientSchedule extends ClientEntity {
|
||||||
this.originalShifts,
|
this.originalShifts,
|
||||||
this.shifts,
|
this.shifts,
|
||||||
);
|
);
|
||||||
|
this.recalcModified();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue