import { Link } from "react-router"; import { addMonths, differenceInCalendarDays, eachMonthOfInterval, format, parseISO, } from "date-fns"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "~/components/ui/card"; import type { DraftScheduleWindow } from "~/models/sports-season"; export interface GanttSport { id: string; name: string; slug: string; iconUrl: string | null; windows: DraftScheduleWindow[]; } interface DraftScheduleGanttProps { sports: GanttSport[]; /** Horizon start (today) as a YYYY-MM-DD string. */ today: string; /** Number of months the timeline spans. */ months: number; } // Bar color by sport-season status, using the theme chart tokens from app.css. const STATUS_COLORS: Record = { active: "var(--chart-1)", upcoming: "var(--chart-2)", completed: "var(--muted-foreground)", }; const STATUS_LABELS: Record = { active: "Active", upcoming: "Upcoming", completed: "Completed", }; const clampPct = (n: number) => Math.max(0, Math.min(100, n)); export function DraftScheduleGantt({ sports, today, months }: DraftScheduleGanttProps) { const start = parseISO(today); const end = addMonths(start, months); const totalDays = Math.max(differenceInCalendarDays(end, start), 1); const pct = (date: Date) => (differenceInCalendarDays(date, start) / totalDays) * 100; // Month-boundary gridlines that fall inside the horizon. const monthLines = eachMonthOfInterval({ start, end }) .map((date) => ({ date, left: pct(date) })) .filter((m) => m.left >= 0 && m.left <= 100); if (sports.length === 0) { return ( Draft Schedule Draft windows across the next {months} months

No sports found.

Create a sport to start scheduling draft windows.

); } return (
Draft Schedule Draft windows across the next {months} months (each bar spans a sport-season’s draft-on → draft-off window)
{(Object.keys(STATUS_COLORS) as DraftScheduleWindow["status"][]).map((status) => (
))}
{/* Sport labels column */}
{/* Timeline column */}
{/* Month gridlines (full height) */} {monthLines.map((m) => (
); }