2026-07-02 00:44:23 +00:00
|
|
|
import { Link } from "react-router";
|
|
|
|
|
import type { Route } from "./+types/admin.draft-schedule";
|
|
|
|
|
|
|
|
|
|
import { findAllSports } from "~/models/sport";
|
|
|
|
|
import { findDraftScheduleForHorizon } from "~/models/sports-season";
|
|
|
|
|
import {
|
|
|
|
|
DraftScheduleGantt,
|
|
|
|
|
type GanttSport,
|
|
|
|
|
} from "~/components/admin/DraftScheduleGantt";
|
|
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
|
|
|
|
} from "~/components/ui/card";
|
|
|
|
|
import { Button } from "~/components/ui/button";
|
|
|
|
|
import { AlertTriangle, ArrowRight } from "lucide-react";
|
|
|
|
|
|
|
|
|
|
const ALLOWED_MONTHS = [6, 12] as const;
|
|
|
|
|
|
|
|
|
|
export function meta(): Route.MetaDescriptors {
|
|
|
|
|
return [{ title: "Draft Schedule - Brackt" }];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function loader({ request }: Route.LoaderArgs) {
|
|
|
|
|
const url = new URL(request.url);
|
|
|
|
|
const monthsParam = Number(url.searchParams.get("months"));
|
|
|
|
|
const months = ALLOWED_MONTHS.includes(monthsParam as (typeof ALLOWED_MONTHS)[number])
|
|
|
|
|
? monthsParam
|
|
|
|
|
: 6;
|
|
|
|
|
|
|
|
|
|
const [allSports, windows] = await Promise.all([
|
|
|
|
|
findAllSports(),
|
|
|
|
|
findDraftScheduleForHorizon(months),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const windowsBySport = new Map<string, typeof windows>();
|
|
|
|
|
for (const w of windows) {
|
|
|
|
|
const list = windowsBySport.get(w.sport.id) ?? [];
|
|
|
|
|
list.push(w);
|
|
|
|
|
windowsBySport.set(w.sport.id, list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sports: GanttSport[] = allSports.map((s) => ({
|
|
|
|
|
id: s.id,
|
|
|
|
|
name: s.name,
|
|
|
|
|
slug: s.slug,
|
|
|
|
|
iconUrl: s.iconUrl,
|
|
|
|
|
windows: windowsBySport.get(s.id) ?? [],
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const sportsWithoutCoverage = sports.filter((s) => s.windows.length === 0);
|
|
|
|
|
const today = new Date().toISOString().split("T")[0];
|
|
|
|
|
|
|
|
|
|
return { sports, sportsWithoutCoverage, today, months };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function AdminDraftSchedule({ loaderData }: Route.ComponentProps) {
|
|
|
|
|
const { sports, sportsWithoutCoverage, today, months } = loaderData;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="p-4 md:p-8">
|
|
|
|
|
<div className="mb-8 flex flex-wrap items-start justify-between gap-4">
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-3xl font-bold">Draft Schedule</h1>
|
|
|
|
|
<p className="text-muted-foreground mt-2">
|
|
|
|
|
See when each sport is scheduled to be drafted and spot gaps in coverage.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-1 rounded-md border p-1">
|
|
|
|
|
{ALLOWED_MONTHS.map((m) => (
|
|
|
|
|
<Button
|
|
|
|
|
key={m}
|
|
|
|
|
variant={m === months ? "default" : "ghost"}
|
|
|
|
|
size="sm"
|
|
|
|
|
asChild
|
|
|
|
|
>
|
|
|
|
|
<Link to={`?months=${m}`}>{m} months</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{sportsWithoutCoverage.length > 0 && (
|
|
|
|
|
<Card className="mb-6 border-amber-300 bg-amber-50 dark:border-amber-800 dark:bg-amber-950/30">
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="flex items-center gap-2 text-amber-800 dark:text-amber-300">
|
|
|
|
|
<AlertTriangle className="h-5 w-5" />
|
|
|
|
|
Sports without a draft window
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription className="text-amber-700 dark:text-amber-400/80">
|
|
|
|
|
{sportsWithoutCoverage.length} sport
|
2026-07-02 02:33:15 +00:00
|
|
|
{sportsWithoutCoverage.length !== 1 ? "s" : ""}{" "}
|
|
|
|
|
{sportsWithoutCoverage.length !== 1 ? "have" : "has"} no draft window
|
2026-07-02 00:44:23 +00:00
|
|
|
open or upcoming in the next {months} months.
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<ul className="space-y-2">
|
|
|
|
|
{sportsWithoutCoverage.map((s) => (
|
|
|
|
|
<li
|
|
|
|
|
key={s.id}
|
|
|
|
|
className="flex items-center justify-between gap-3 rounded-md border border-amber-200 bg-background/60 px-3 py-2 dark:border-amber-900"
|
|
|
|
|
>
|
|
|
|
|
<span className="text-sm font-medium">{s.name}</span>
|
|
|
|
|
<Button variant="outline" size="sm" asChild className="h-7 text-xs">
|
|
|
|
|
<Link to="/admin/sports-seasons/new">
|
|
|
|
|
Schedule a season
|
|
|
|
|
<ArrowRight className="ml-1 h-3 w-3" />
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<DraftScheduleGantt sports={sports} today={today} months={months} />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|