import { useMemo } from 'react' import DayColumn from './DayColumn' import { getDeviceAccentClass } from './TestCard' function addDays(date, n) { const d = new Date(date) d.setDate(d.getDate() + n) return d } function formatDateRange(monday) { const sunday = addDays(monday, 6) const opts = { month: 'short', day: 'numeric' } const startStr = monday.toLocaleDateString('en-US', opts) const endStr = sunday.toLocaleDateString('en-US', { month: sunday.getMonth() !== monday.getMonth() ? 'short' : undefined, day: 'numeric', }) return `${startStr} – ${endStr}` } function toDateKey(date) { return date.toISOString().slice(0, 10) } function getMondayOfWeek(date) { const d = new Date(date) const day = d.getDay() d.setDate(d.getDate() + (day === 0 ? -6 : 1 - day)) d.setHours(0, 0, 0, 0) return d } // Returns the two shifts that form the next/active test window. // Window = Shift 3 of day D → Shift 1 of day D+1 function getNextTestWindow() { const now = new Date() const hour = now.getHours() const tod = new Date(now); tod.setHours(0, 0, 0, 0) // midnight–1AM: still inside shift 3 that started yesterday if (hour < 1) { return { shift3Date: toDateKey(addDays(tod, -1)), shift1Date: toDateKey(tod) } } // 1AM–5PM: shift 1 finished or in daytime gap — next window starts at 5PM today // 5PM–midnight: currently inside shift 3 of today // Both cases: upcoming/active window is shift 3 today + shift 1 tomorrow return { shift3Date: toDateKey(tod), shift1Date: toDateKey(addDays(tod, 1)) } } // scheduleData: { "YYYY-MM-DD": { shift1: [...], shift2: [...], shift3: [...] }, ... } export default function Calendar({ scheduleData = {}, daytimeDateKey = null, weekStart, onWeekChange, dualDeviceWeekendWeekEnabled = false, onDualDeviceWeekendWeekEnabledChange, windowLookup = new Map(), onWindowSelect, }) { const SLOT_CHROME_PX = 10 const TEST_CARD_ROW_HEIGHT_PX = 22 const TEST_CARD_ROW_GAP_PX = 2 const DAY_HEADER_HEIGHT_PX = 48 const today = new Date() today.setHours(0, 0, 0, 0) const activeWindow = getNextTestWindow() const days = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i)) // Reorder columns: Sun Mon Tue Wed Thu Fri Sat // weekStart is Monday, so days[0]=Mon ... days[6]=Sun → put Sunday first const orderedDays = [ ...days.slice(0, 7)] const shiftMinRows = useMemo(() => { const minima = { shift2: 1, shift3: 1, nextDayShift1: 1 } for (const date of orderedDays) { const key = toDateKey(date) const shifts = scheduleData[key] ?? { shift1: [], shift2: [], shift3: [] } const nextKey = toDateKey(addDays(date, 1)) const nextDayShift1 = scheduleData[nextKey]?.shift1 ?? [] minima.shift2 = Math.max(minima.shift2, shifts.shift2.length || 1) minima.shift3 = Math.max(minima.shift3, shifts.shift3.length || 1) minima.nextDayShift1 = Math.max(minima.nextDayShift1, nextDayShift1.length || 1) } return minima }, [orderedDays, scheduleData]) const slotHeights = useMemo(() => { const calcSlotHeight = (rows) => { const safeRows = Math.max(1, rows || 1) const contentHeight = (safeRows * TEST_CARD_ROW_HEIGHT_PX) + ((safeRows - 1) * TEST_CARD_ROW_GAP_PX) return contentHeight + SLOT_CHROME_PX } return { shift2: calcSlotHeight(shiftMinRows.shift2), shift3: calcSlotHeight(shiftMinRows.shift3), nextDayShift1: calcSlotHeight(shiftMinRows.nextDayShift1), } }, [shiftMinRows]) const totalShiftHeightPx = slotHeights.shift2 + slotHeights.shift3 + slotHeights.nextDayShift1 const deviceLegendItems = Array.from( new Set( Object.values(scheduleData) .flatMap((day) => [ ...(day?.shift1 ?? []), ...(day?.shift2 ?? []), ...(day?.shift3 ?? []), ]) .map((test) => test?.device) .filter(Boolean), ), ).sort((a, b) => String(a).localeCompare(String(b))) return (