diff --git a/app/components/admin/DraftScheduleGantt.tsx b/app/components/admin/DraftScheduleGantt.tsx index cf062d3..e491fe3 100644 --- a/app/components/admin/DraftScheduleGantt.tsx +++ b/app/components/admin/DraftScheduleGantt.tsx @@ -46,6 +46,40 @@ const STATUS_LABELS: Record = { const clampPct = (n: number) => Math.max(0, Math.min(100, n)); +// Row layout (px). A single-lane row is LANE_HEIGHT + ROW_V_PAD tall; extra +// concurrent windows add one lane each so overlapping bars never stack on top +// of one another. +const LANE_HEIGHT = 30; +const LANE_GAP = 8; +const ROW_V_PAD = 14; + +const rowHeight = (laneCount: number) => Math.max(laneCount, 1) * LANE_HEIGHT + ROW_V_PAD; + +/** + * Greedy interval-scheduling: assign each window to the first lane whose last + * bar ends before this one starts, otherwise open a new lane. Windows arrive + * sorted by draftOn (see findDraftScheduleForHorizon). + */ +function assignLanes( + windows: DraftScheduleWindow[], + start: Date +): { placed: Array<{ window: DraftScheduleWindow; lane: number }>; laneCount: number } { + const laneEnds: number[] = []; + const placed = windows.map((window) => { + const startDay = differenceInCalendarDays(parseISO(window.draftOn), start); + const endDay = differenceInCalendarDays(parseISO(window.draftOff), start); + let lane = laneEnds.findIndex((laneEnd) => laneEnd < startDay); + if (lane === -1) { + lane = laneEnds.length; + laneEnds.push(endDay); + } else { + laneEnds[lane] = endDay; + } + return { window, lane }; + }); + return { placed, laneCount: Math.max(laneEnds.length, 1) }; +} + export function DraftScheduleGantt({ sports, today, months }: DraftScheduleGanttProps) { const start = parseISO(today); const end = addMonths(start, months); @@ -58,6 +92,12 @@ export function DraftScheduleGantt({ sports, today, months }: DraftScheduleGantt .map((date) => ({ date, left: pct(date) })) .filter((m) => m.left >= 0 && m.left <= 100); + // Pre-compute lane assignment + height for each sport row. + const rows = sports.map((sport) => { + const { placed, laneCount } = assignLanes(sport.windows, start); + return { sport, placed, height: rowHeight(laneCount) }; + }); + if (sports.length === 0) { return ( @@ -106,10 +146,11 @@ export function DraftScheduleGantt({ sports, today, months }: DraftScheduleGantt {/* Sport labels column */}