import type { ReactNode } from "react" import type { ScheduleDay } from "./ScheduleBoard.tsx" import { type ScheduleEntry, ScheduleItem, } from "./ScheduleItem.tsx" import { placeScheduleIntervals } from "./scheduleGeometry.ts" export const ScheduleHourGrid = ({ blockSize, days, formatHour, items, onSelect, template, }: { blockSize: number days: readonly ScheduleDay[] formatHour: (minute: number) => string items: readonly ScheduleEntry[] onSelect?: (key: string) => void template: string }): ReactNode => { const timed = items.filter( (item) => item.startMinute !== null, ) const start = Math.floor( Math.min( 480, ...timed.map((item) => item.startMinute ?? 480), ) / 60, ) * 60 const end = Math.min( 1440, Math.ceil( Math.max( 1080, ...timed.map( (item) => item.endMinute ?? (item.startMinute ?? 1080) + 30, ), ) / 60, ) * 60, ) const height = Math.max(360, blockSize) const scale = height / Math.max(60, end - start) const hours = Array.from( { length: (end - start) / 60 + 1 }, (_unused, index) => start + index * 60, ) return (
{hours.map((minute) => ( {formatHour(minute)} ))}
{days.map((day) => { const entries = timed.filter( (item) => item.dayKey === day.key, ) // Minimum painted height participates in packing, so short adjacent items cannot hide each other. const placed = placeScheduleIntervals( entries.map((item) => ({ key: item.key, startMinute: item.startMinute ?? 0, endMinute: Math.max( item.endMinute ?? (item.startMinute ?? 0) + 30, (item.startMinute ?? 0) + 30 / scale, ), })), ) return ( // biome-ignore lint/a11y/useSemanticElements: this names schedule entries, not form controls.
{hours.map((minute) => ( ) })}
) }