184 lines
7.1 KiB
TypeScript
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>
|
|
);
|
|
}
|