Refactor slot editing to use searchable selections
All checks were successful
/ build (push) Successful in 1m36s
/ deploy (push) Successful in 16s

Instead of having to type in exactly the name of events or shifts and
then hope you remembered it right, replace these interactions with the
custom select component that gives a complete list of the available
choices and allows quickly searching for the right one.
This commit is contained in:
Hornwitser 2025-06-27 18:59:23 +02:00
parent da65103e05
commit b0d5cdf791
4 changed files with 125 additions and 215 deletions

View file

@ -7,7 +7,6 @@
<th>end</th>
<th>duration</th>
<th>shift</th>
<th>s</th>
<th>role</th>
<th>assigned</th>
<th v-if="edit"></th>
@ -35,24 +34,16 @@
>
</td>
<td>
<input
type="text"
v-model="newShiftName"
>
<SelectSingleEntity
:entities="schedule.shifts"
v-model="newShiftId"
/>
</td>
<td></td>
<td>
<select
<SelectSingleEntity
:entities="schedule.roles"
v-model="newShiftRoleId"
>
<option
v-for="role in schedule.roles.values()"
:key="role.id"
:value="role.id"
:disabled="role.deleted"
:selected="role.id === newShiftRoleId"
>{{ role.name }}</option>
</select>
/>
</td>
<td></td>
<td>
@ -90,34 +81,28 @@
>
</td>
<td>
<input
type="text"
v-model="ss.name"
>
<SelectSingleEntity
:entities="schedule.shifts"
:modelValue="ss.slot.shiftId"
@update:modelValue="ss.slot.setShiftId($event)"
/>
</td>
<td>{{ status(ss) }}</td>
<td>
<select
<SelectSingleEntity
v-if="ss.shift"
:entities="schedule.roles"
v-model="ss.shift.roleId"
>
<option
v-for="role in schedule.roles.values()"
:key="role.id"
:value="role.id"
:disabled="role.deleted"
:selected="role.id === ss.shift.roleId"
>{{ role.name }}</option>
</select>
/>
</td>
<td>
<AssignedCrew
:edit="true"
<SelectMultiEntity
:entities="usersStore.users"
v-model="ss.slot.assigned"
/>
</td>
<td>
<button
:disabled="ss.deleted"
:disabled="ss.slot.deleted"
type="button"
@click="ss.slot.deleted = true"
>Remove</button>
@ -149,12 +134,18 @@
>
</td>
<td>
<input
type="text"
v-model="newShiftName"
>
<SelectSingleEntity
:entities="schedule.shifts"
v-model="newShiftId"
/>
</td>
<td colspan="3">
<td>
<SelectSingleEntity
:entities="schedule.roles"
v-model="newShiftRoleId"
/>
</td>
<td colspan="2">
<button
type="button"
@click="newShiftSlot()"
@ -180,10 +171,11 @@
<td>{{ ss.start.toFormat("yyyy-LL-dd HH:mm") }}</td>
<td>{{ ss.end.toFormat("HH:mm") }}</td>
<td>{{ ss.end.diff(ss.start).toFormat('hh:mm') }}</td>
<td>{{ ss.name }}</td>
<td>{{ status(ss) }}</td>
<td>{{ ss.roleId }}</td>
<td><AssignedCrew :modelValue="ss.assigned" :edit="false" /></td>
<td>{{ ss.shift?.name }}</td>
<td>{{ ss.shift?.roleId }}</td>
<td>
<AssignedCrew :modelValue="ss.slot.assigned" :edit="false" />
</td>
</template>
</tr>
</template>
@ -206,12 +198,8 @@ const props = defineProps<{
interface ShiftSlot {
type: "slot",
id: Id,
deleted: boolean,
shift: ClientScheduleShift,
shift?: ClientScheduleShift,
slot: ClientScheduleShiftSlot,
name: string,
roleId: Id,
assigned: Set<Id>,
start: DateTime,
end: DateTime,
}
@ -221,24 +209,13 @@ interface Gap {
id?: undefined,
shift?: undefined,
slot?: undefined,
name?: undefined,
role?: undefined,
start: DateTime,
end: DateTime,
}
function status(shiftSlot: ShiftSlot) {
if (
!shiftSlot.shift
|| shiftSlot.shift.name !== shiftSlot.name
) {
const shift = [...schedule.value.shifts.values()].find(shift => !shift.deleted && shift.name === shiftSlot.name);
return shift ? "L" : "N";
}
return shiftSlot.shift.slots.size === 1 ? "" : shiftSlot.shift.slots.size;
}
const accountStore = useAccountStore();
const usersStore = useUsersStore();
await usersStore.fetch();
const schedule = await useSchedule();
const oneDayMs = 24 * 60 * 60 * 1000;
@ -286,7 +263,7 @@ function durationFromTime(time: string) {
}
return duration;
}
const newShiftName = ref("");
const newShiftId = ref<Id>();
function editShiftSlot(
shiftSlot: ShiftSlot,
@ -298,20 +275,18 @@ function editShiftSlot(
) {
if (edits.start) {
const start = DateTime.fromISO(edits.start, { zone: accountStore.activeTimezone, locale: accountStore.activeLocale });
shiftSlot.start = start;
shiftSlot.end = start.plus(shiftSlot.slot.end.diff(shiftSlot.slot.start));
shiftSlot.slot.start = start;
shiftSlot.slot.end = start.plus(shiftSlot.slot.end.diff(shiftSlot.slot.start));
}
if (edits.end !== undefined) {
shiftSlot.end = endFromTime(shiftSlot.start, edits.end);
shiftSlot.slot.end = endFromTime(shiftSlot.slot.start, edits.end);
}
if (edits.duration !== undefined) {
shiftSlot.end = shiftSlot.start.plus(durationFromTime(edits.duration));
shiftSlot.slot.end = shiftSlot.slot.start.plus(durationFromTime(edits.duration));
}
}
function newShiftSlot(options: { start?: DateTime, end?: DateTime } = {}) {
const name = newShiftName.value;
const nameId = toId(name);
const shift = [...schedule.value.shifts.values()].find(shift => toId(shift.name) === nameId);
const shift = schedule.value.shifts.get(newShiftId.value!);
if (!shift) {
alert("Invalid shift");
return;
@ -352,7 +327,7 @@ function newShiftSlot(options: { start?: DateTime, end?: DateTime } = {}) {
);
schedule.value.shiftSlots.set(slot.id, slot);
shift.slotIds.add(slot.id);
newShiftName.value = "";
newShiftId.value = undefined;
}
const oneHourMs = 60 * 60 * 1000;
@ -367,25 +342,20 @@ function gapFormat(gap: Gap) {
const shiftSlots = computed(() => {
const data: (ShiftSlot | Gap)[] = [];
for (const shift of schedule.value.shifts.values()) {
if (props.roleId !== undefined && shift.roleId !== props.roleId)
for (const slot of schedule.value.shiftSlots.values()) {
const shift = schedule.value.shifts.get(slot.shiftId!);
if (shift && props.roleId !== undefined && shift.roleId !== props.roleId)
continue;
for (const slot of shift.slots.values()) {
if (props.shiftSlotFilter && !props.shiftSlotFilter(slot))
continue;
data.push({
type: "slot",
id: slot.id,
deleted: slot.deleted || shift.deleted,
shift,
slot,
name: shift.name,
roleId: shift.roleId,
assigned: slot.assigned,
start: slot.start,
end: slot.end,
});
}
if (props.shiftSlotFilter && !props.shiftSlotFilter(slot))
continue;
data.push({
type: "slot",
id: slot.id,
shift,
slot,
start: slot.start,
end: slot.end,
});
}
const byTime = (a: DateTime, b: DateTime) => a.toMillis() - b.toMillis();
data.sort((a, b) => byTime(a.start, b.start) || byTime(a.end, b.end));