brackt/app/routes/admin.sports-seasons.tsx
2026-05-12 16:52:26 -07:00

184 lines
7.1 KiB
TypeScript

import { Link, useSearchParams } from "react-router";
import type { Route } from "./+types/admin.sports-seasons";
import { findAllAdminSportsSeasons } from "~/models/sports-season";
import { filterVisibleSportsSeasons } from "./admin.sports-seasons.helpers";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Button } from "~/components/ui/button";
import { Label } from "~/components/ui/label";
import { Switch } from "~/components/ui/switch";
import { Plus, Calendar } from "lucide-react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import { Badge } from "~/components/ui/badge";
export function meta(): Route.MetaDescriptors {
return [{ title: "Sports Seasons - Brackt Admin" }];
}
export async function loader() {
const sportsSeasons = await findAllAdminSportsSeasons();
return { sportsSeasons };
}
export default function AdminSportsSeasons({ loaderData }: Route.ComponentProps) {
const { sportsSeasons } = loaderData;
const [searchParams, setSearchParams] = useSearchParams();
const today = new Date().toISOString().slice(0, 10);
const hideCompleted = searchParams.get("showCompleted") !== "true";
const visibleSportsSeasons = filterVisibleSportsSeasons(sportsSeasons, hideCompleted);
function setHideCompleted(nextHideCompleted: boolean) {
const next = new URLSearchParams(searchParams);
if (nextHideCompleted) next.delete("showCompleted");
else next.set("showCompleted", "true");
setSearchParams(next);
}
return (
<div className="p-8">
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-3xl font-bold">Sports Seasons</h1>
<p className="text-muted-foreground mt-1">
Manage sports seasons across all sports
</p>
</div>
<Button asChild>
<Link to="/admin/sports-seasons/new">
<Plus className="mr-2 h-4 w-4" />
Add Sports Season
</Link>
</Button>
</div>
<Card>
<CardHeader>
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<CardTitle>All Sports Seasons</CardTitle>
<CardDescription>
{visibleSportsSeasons.length} of {sportsSeasons.length} {sportsSeasons.length === 1 ? "season" : "seasons"} shown
</CardDescription>
</div>
<div className="flex items-center gap-3">
<Label htmlFor="hide-completed-seasons">Hide completed seasons</Label>
<Switch
id="hide-completed-seasons"
checked={hideCompleted}
onCheckedChange={setHideCompleted}
/>
</div>
</div>
</CardHeader>
<CardContent>
{sportsSeasons.length === 0 ? (
<div className="text-center py-12">
<Calendar className="mx-auto h-12 w-12 text-muted-foreground" />
<h3 className="mt-4 text-lg font-semibold">No sports seasons yet</h3>
<p className="text-muted-foreground mt-2">
Get started by creating your first sports season
</p>
<Button className="mt-4" asChild>
<Link to="/admin/sports-seasons/new">
<Plus className="mr-2 h-4 w-4" />
Add Sports Season
</Link>
</Button>
</div>
) : visibleSportsSeasons.length === 0 ? (
<div className="text-center py-12">
<Calendar className="mx-auto h-12 w-12 text-muted-foreground" />
<h3 className="mt-4 text-lg font-semibold">No visible sports seasons</h3>
<p className="text-muted-foreground mt-2">
All current sports seasons are completed. Turn off the filter to review them.
</p>
</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Sport</TableHead>
<TableHead>Year</TableHead>
<TableHead>Status</TableHead>
<TableHead>Draft Window</TableHead>
<TableHead>Scoring Type</TableHead>
<TableHead>Participants</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{visibleSportsSeasons.map((season) => (
<TableRow key={season.id}>
<TableCell className="font-medium">{season.name}</TableCell>
<TableCell>{season.sport.name}</TableCell>
<TableCell>{season.year}</TableCell>
<TableCell>
<Badge
variant={
season.status === "active"
? "default"
: season.status === "completed"
? "secondary"
: "outline"
}
>
{season.status}
</Badge>
</TableCell>
<TableCell>
{(() => {
const active = season.draftOn <= today && today <= season.draftOff;
return (
<div className="space-y-0.5">
<Badge variant={active ? "default" : "secondary"}>
{active ? "Active" : "Inactive"}
</Badge>
<p className="text-xs text-muted-foreground whitespace-nowrap">
{season.draftOn} {season.draftOff}
</p>
</div>
);
})()}
</TableCell>
<TableCell className="text-muted-foreground capitalize">
{season.scoringType.replace("_", " ")}
</TableCell>
<TableCell className="text-muted-foreground">
{season.participants.length}
</TableCell>
<TableCell className="text-right">
<div className="flex items-center justify-end gap-2">
<Button variant="ghost" size="sm" asChild>
<Link to={`/admin/sports-seasons/${season.id}/participants`}>
Participants
</Link>
</Button>
<Button variant="ghost" size="sm" asChild>
<Link to={`/admin/sports-seasons/${season.id}`}>Edit</Link>
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</CardContent>
</Card>
</div>
);
}