"use client" import { useMemo } from "react" import type { CalendarEvent } from "@/lib/types" import { getRoomColor } from "@/lib/room-theme" import { addDays, isSameDay, parseDateTime, WEEKDAY_LABELS, } from "@/lib/date-utils" const HOUR_HEIGHT = 64 // px per hour interface WeekViewProps { weekStart: Date events: CalendarEvent[] showRoomColors: boolean onEventClick: (event: CalendarEvent) => void } interface PositionedEvent { event: CalendarEvent start: Date end: Date top: number height: number lane: number lanes: number } // Assign overlapping events to side-by-side lanes within a single day. function layoutDayEvents( events: { event: CalendarEvent; start: Date; end: Date }[], hoursStart: number, ): PositionedEvent[] { const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime()) const result: PositionedEvent[] = [] let cluster: typeof sorted = [] let clusterEnd = 0 const flush = () => { if (cluster.length === 0) return // Greedy lane assignment within the cluster. const laneEnds: number[] = [] const withLanes = cluster.map((item) => { let lane = laneEnds.findIndex((end) => end <= item.start.getTime()) if (lane === -1) { lane = laneEnds.length laneEnds.push(item.end.getTime()) } else { laneEnds[lane] = item.end.getTime() } return { ...item, lane } }) const lanes = laneEnds.length for (const item of withLanes) { const startHrs = item.start.getHours() + item.start.getMinutes() / 60 const endHrs = item.end.getHours() + item.end.getMinutes() / 60 const safeEnd = Math.max(endHrs, startHrs + 0.5) result.push({ event: item.event, start: item.start, end: item.end, top: (startHrs - hoursStart) * HOUR_HEIGHT, height: (safeEnd - startHrs) * HOUR_HEIGHT, lane: item.lane, lanes, }) } cluster = [] clusterEnd = 0 } for (const item of sorted) { if (cluster.length === 0 || item.start.getTime() < clusterEnd) { cluster.push(item) clusterEnd = Math.max(clusterEnd, item.end.getTime()) } else { flush() cluster.push(item) clusterEnd = item.end.getTime() } } flush() return result } export function WeekView({ weekStart, events, showRoomColors, onEventClick }: WeekViewProps) { const days = useMemo( () => Array.from({ length: 7 }, (_, i) => addDays(weekStart, i)), [weekStart], ) // Determine visible hour range from events (clamped to a sensible default). const { hoursStart, hoursEnd } = useMemo(() => { let min = 8 let max = 18 for (const e of events) { const s = parseDateTime(e.start_time) const en = parseDateTime(e.end_time) if (!isNaN(s.getTime())) min = Math.min(min, s.getHours()) if (!isNaN(en.getTime())) { const endHr = en.getMinutes() > 0 ? en.getHours() + 1 : en.getHours() max = Math.max(max, endHr) } } return { hoursStart: Math.max(0, min), hoursEnd: Math.min(24, Math.max(max, min + 6)) } }, [events]) const timeSlots = useMemo( () => Array.from({ length: hoursEnd - hoursStart }, (_, i) => hoursStart + i), [hoursStart, hoursEnd], ) const positionedByDay = useMemo(() => { return days.map((day) => { const dayEvents = events .map((event) => ({ event, start: parseDateTime(event.start_time), end: parseDateTime(event.end_time), })) .filter((x) => !isNaN(x.start.getTime()) && isSameDay(x.start, day)) return layoutDayEvents(dayEvents, hoursStart) }) }, [days, events, hoursStart]) const today = new Date() const gridHeight = (hoursEnd - hoursStart) * HOUR_HEIGHT const formatHour = (h: number) => { if (h === 0) return "12 AM" if (h === 12) return "12 PM" return h > 12 ? `${h - 12} PM` : `${h} AM` } return (