ultisuite-client/lib/agenda/agenda-event-layout.ts
R3D347HR4Y 3bbf3691b0
Some checks failed
E2E / Playwright e2e (push) Has been cancelled
bordel c'est beau
2026-06-11 10:10:39 +02:00

90 lines
2.6 KiB
TypeScript

import type { AgendaEvent } from "./agenda-types.ts"
export interface PositionedEvent {
event: AgendaEvent
/** Minutes depuis minuit. */
top: number
/** Durée en minutes (minimum appliqué côté rendu). */
duration: number
leftPct: number
widthPct: number
}
interface WorkItem {
event: AgendaEvent
startMin: number
endMin: number
col: number
cluster: number
}
/**
* Positionne les événements horaires d'une colonne jour façon Google Calendar :
* les événements qui se chevauchent se partagent la largeur par colonnes.
*/
export function layoutDayEvents(events: AgendaEvent[], day: Date): PositionedEvent[] {
const dayStart = new Date(day)
dayStart.setHours(0, 0, 0, 0)
const dayStartMs = dayStart.getTime()
const minutesInDay = 24 * 60
const items: WorkItem[] = events
.map((event) => {
const startMin = Math.max(0, (event.start.getTime() - dayStartMs) / 60000)
const endMin = Math.min(minutesInDay, (event.end.getTime() - dayStartMs) / 60000)
return { event, startMin, endMin: Math.max(endMin, startMin + 15), col: 0, cluster: 0 }
})
.filter((it) => it.startMin < minutesInDay && it.endMin > 0)
.sort((a, b) => a.startMin - b.startMin || b.endMin - a.endMin)
// Regroupe en clusters d'événements transitivement chevauchants.
let clusterId = 0
let clusterEnd = -1
for (const it of items) {
if (it.startMin >= clusterEnd) {
clusterId++
clusterEnd = it.endMin
} else {
clusterEnd = Math.max(clusterEnd, it.endMin)
}
it.cluster = clusterId
}
const positioned: PositionedEvent[] = []
const clusters = new Map<number, WorkItem[]>()
for (const it of items) {
const list = clusters.get(it.cluster) ?? []
list.push(it)
clusters.set(it.cluster, list)
}
for (const list of clusters.values()) {
// Attribution gloutonne de colonnes.
const colEnds: number[] = []
for (const it of list) {
let col = colEnds.findIndex((end) => end <= it.startMin)
if (col === -1) {
col = colEnds.length
colEnds.push(it.endMin)
} else {
colEnds[col] = it.endMin
}
it.col = col
}
const colCount = colEnds.length
const width = 100 / colCount
for (const it of list) {
positioned.push({
event: it.event,
top: it.startMin,
duration: it.endMin - it.startMin,
leftPct: it.col * width,
// Léger débord à la Google quand il reste de la place à droite.
widthPct: it.col === colCount - 1 ? width : Math.min(width * 1.7, 100 - it.col * width),
})
}
}
return positioned.sort((a, b) => a.top - b.top)
}