Add line indicating now to Timetable
All checks were successful
/ build (push) Successful in 1m33s
/ deploy (push) Successful in 16s

Add a red vertical line indicated the current time and date in the
timetable with the label "now" on top of it.
This commit is contained in:
Hornwitser 2025-06-18 18:17:03 +02:00
parent bea8e77742
commit ebf7bdcc9c

View file

@ -68,6 +68,20 @@
</tr>
</thead>
<tbody>
<tr class="overlay">
<th></th>
<td :colSpan="totalColumns">
<div
v-if="nowOffset !== undefined"
class="now"
:style="` --now-offset: ${nowOffset}`"
>
<div class="label">
now
</div>
</div>
</td>
</tr>
<template v-for="location in schedule.locations.values()" :key="location.id">
<tr v-if="locationRows.has(location.id)">
<th>{{ location.name }}</th>
@ -364,7 +378,8 @@ function tableElementsFromStretches(
type HourHead = { span: number, isBreak: boolean, isDayShift: boolean, content?: string }
type LocationCell = { span: number, slots: Set<ClientScheduleEventSlot>, title: string, crew?: boolean }
type RoleCell = { span: number, slots: Set<ClientScheduleShiftSlot>, title: string };
const columnGroups: { className?: string, cols: Col[] }[] = [];
type ColumnGroup = { start: number, end: number, width: number, className?: string, cols: Col[] };
const columnGroups: ColumnGroup[] = [];
const dayHeaders: DayHead[] = [];
const hourHeaders: HourHead[]= [];
const locationRows = new Map<number, LocationCell[]>([...locations.keys()].map(id => [id, []]));
@ -373,8 +388,8 @@ function tableElementsFromStretches(
const shiftBySlotId = new Map([...shifts.values()].flatMap?.(shift => [...shift.slots.values()].map(slot =>[slot.id, shift])));
let totalColumns = 0;
function startColumnGroup(className?: string) {
columnGroups.push({ className, cols: []})
function startColumnGroup(start: number, end: number, width: number, className?: string) {
columnGroups.push({ start, end, width, className, cols: []})
}
function startDay(isBreak: boolean, content?: string) {
dayHeaders.push({ span: 0, isBreak, content })
@ -414,13 +429,12 @@ function tableElementsFromStretches(
}
}
let first = true;
let lastStretch: Stretch | undefined;
for (let stretch of stretches) {
stretch = padStretch(stretch, timezone);
const startDate = DateTime.fromMillis(stretch.start, { zone: timezone, locale: accountStore.activeLocale });
if (first) {
first = false;
startColumnGroup();
if (!lastStretch) {
startColumnGroup(stretch.start, stretch.end, (stretch.end - stretch.start) / oneHourMs);
startDay(false, startDate.toFormat("yyyy-LL-dd"));
startHour(false, startDate.toFormat("HH:mm"));
for(const location of locations.values()) {
@ -430,7 +444,7 @@ function tableElementsFromStretches(
startRole(role.id);
}
} else {
startColumnGroup("break");
startColumnGroup(lastStretch.end, stretch.start, 1, "break");
const dayName = startDate.toFormat("yyyy-LL-dd");
const lastDayHeader = dayHeaders[dayHeaders.length - 1]
const sameDay = dayName === lastDayHeader.content && lastDayHeader.span;
@ -445,7 +459,7 @@ function tableElementsFromStretches(
}
pushColumn();
startColumnGroup();
startColumnGroup(stretch.start, stretch.end, (stretch.end - stretch.start) / oneHourMs);
if (!sameDay)
startDay(false, dayName);
startHour(false, startDate.toFormat("HH:mm"));
@ -499,6 +513,8 @@ function tableElementsFromStretches(
}
}
}
lastStretch = stretch;
}
return {
@ -565,6 +581,28 @@ 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 now = useState(() => Math.round(Date.now() / oneMinMs) * oneMinMs);
const interval = ref<any>();
onMounted(() => {
interval.value = setInterval(() => {
const newNow = Math.round(Date.now() / oneMinMs) * oneMinMs;
if (now.value !== newNow)
now.value = newNow;
}, 1000);
});
onUnmounted(() => {
clearInterval(interval.value);
});
const nowOffset = computed(() => {
let offset = 0;
for (let group of columnGroups.value) {
if (group.start <= now.value && now.value < group.end) {
return offset + (now.value - group.start) / (group.end - group.start) * group.width;
}
offset += group.width;
}
});
</script>
<style scoped>
@ -595,7 +633,7 @@ const roleRows = computed(() => elements.value.roleRows);
left: 0;
}
.timetable :is(td, th) {
.timetable tr:not(.overlay) :is(td, th) {
overflow: hidden;
white-space: pre;
text-overflow: ellipsis;
@ -610,6 +648,30 @@ const roleRows = computed(() => elements.value.roleRows);
border-bottom: 1px solid var(--foreground);
}
.timetable tbody {
position: relative;
}
.timetable tr.overlay .now {
background-color: #f008;
position: absolute;
top: 0;
left: calc(var(--row-header-width) + var(--cell-size) * var(--now-offset) - 1px);
bottom: 0;
width: 2px;
scroll-margin-inline-start: calc(var(--row-header-width) + 2rem);
}
.now .label {
position: absolute;
top: 0;
color: white;
background: red;
border-radius: 0.2rem;
font-size: 0.5rem;
line-height: 1.1;
padding-inline: 0.1rem;
translate: calc(-50% + 0.5px) -50%;
}
colgroup.break {
background-color: color-mix(in oklab, var(--background), rgb(50, 50, 255) 60%);
}