refactor: event instances
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Multi-Day Lane Layout
|
||||
* Assigns multi-day/all-day instances to horizontal lanes so they render
|
||||
* as spanning bars across day columns without overlapping.
|
||||
*/
|
||||
|
||||
import type { CalendarInstance } from '@/types/instance'
|
||||
import { startOfLocalDay } from './date'
|
||||
|
||||
export interface MultiDaySegment {
|
||||
instance: CalendarInstance;
|
||||
startCol: number;
|
||||
span: number;
|
||||
lane: number;
|
||||
isStart: boolean;
|
||||
isEnd: boolean;
|
||||
}
|
||||
|
||||
export interface LaneAssignment {
|
||||
segments: MultiDaySegment[];
|
||||
laneCount: number;
|
||||
}
|
||||
|
||||
export function layoutMultiDayLanes(
|
||||
instances: CalendarInstance[],
|
||||
rangeStart: Date,
|
||||
rangeEnd: Date,
|
||||
columnCount: number,
|
||||
getColumnIndex: (date: Date) => number
|
||||
): LaneAssignment {
|
||||
const segments: MultiDaySegment[] = [];
|
||||
|
||||
// Filter multi-day/all-day events that overlap this range
|
||||
const rangeStartMs = startOfLocalDay(rangeStart).getTime();
|
||||
const rangeEndExclusive = startOfLocalDay(new Date(rangeEnd));
|
||||
rangeEndExclusive.setDate(rangeEndExclusive.getDate() + 1);
|
||||
|
||||
const multiDayEvents = instances.filter(instance => {
|
||||
return instance.timeless
|
||||
&& instance.startMs < rangeEndExclusive.getTime()
|
||||
&& instance.endMs > rangeStartMs;
|
||||
});
|
||||
|
||||
// Sort: longer events first, then by start date
|
||||
multiDayEvents.sort((a, b) => {
|
||||
if (b.durationMs !== a.durationMs) return b.durationMs - a.durationMs;
|
||||
return a.startMs - b.startMs;
|
||||
});
|
||||
|
||||
// Lane assignment - track which days are occupied in each lane
|
||||
const lanes: boolean[][] = [];
|
||||
|
||||
for (const instance of multiDayEvents) {
|
||||
const eventStart = startOfLocalDay(new Date(instance.startMs));
|
||||
const eventEnd = startOfLocalDay(new Date(instance.endMs - 1));
|
||||
|
||||
// Clamp to visible range
|
||||
const segStart = eventStart < rangeStart ? rangeStart : eventStart;
|
||||
const segEnd = eventEnd > rangeEnd ? rangeEnd : eventEnd;
|
||||
|
||||
const startCol = getColumnIndex(segStart);
|
||||
const endCol = getColumnIndex(segEnd);
|
||||
|
||||
// Skip if outside visible columns
|
||||
if (startCol < 0 || startCol >= columnCount) continue;
|
||||
|
||||
const span = Math.min(endCol, columnCount - 1) - startCol + 1;
|
||||
|
||||
// Find available lane
|
||||
let lane = 0;
|
||||
while (true) {
|
||||
if (!lanes[lane]) {
|
||||
lanes[lane] = new Array(columnCount).fill(false);
|
||||
}
|
||||
|
||||
let isFree = true;
|
||||
for (let d = startCol; d < startCol + span; d++) {
|
||||
if (lanes[lane][d]) {
|
||||
isFree = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isFree) {
|
||||
for (let d = startCol; d < startCol + span; d++) {
|
||||
lanes[lane][d] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
lane++;
|
||||
}
|
||||
|
||||
segments.push({
|
||||
instance,
|
||||
startCol,
|
||||
span,
|
||||
lane,
|
||||
isStart: eventStart >= rangeStart,
|
||||
isEnd: eventEnd <= rangeEnd,
|
||||
});
|
||||
}
|
||||
|
||||
return { segments, laneCount: lanes.length };
|
||||
}
|
||||
Reference in New Issue
Block a user