139 lines
4.6 KiB
TypeScript
139 lines
4.6 KiB
TypeScript
|
|
import { Link } from "react-router";
|
||
|
|
import type { Route } from "./+types/admin.tournaments._index";
|
||
|
|
|
||
|
|
import { findAllTournamentsGroupedBySport } from "~/models/tournament";
|
||
|
|
import {
|
||
|
|
Card,
|
||
|
|
CardContent,
|
||
|
|
CardDescription,
|
||
|
|
CardHeader,
|
||
|
|
CardTitle,
|
||
|
|
} from "~/components/ui/card";
|
||
|
|
import {
|
||
|
|
Table,
|
||
|
|
TableBody,
|
||
|
|
TableCell,
|
||
|
|
TableHead,
|
||
|
|
TableHeader,
|
||
|
|
TableRow,
|
||
|
|
} from "~/components/ui/table";
|
||
|
|
import { Badge } from "~/components/ui/badge";
|
||
|
|
import { Button } from "~/components/ui/button";
|
||
|
|
import { Trophy } from "lucide-react";
|
||
|
|
|
||
|
|
export function meta(): Route.MetaDescriptors {
|
||
|
|
return [{ title: "Tournaments - Brackt Admin" }];
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function loader() {
|
||
|
|
const grouped = await findAllTournamentsGroupedBySport();
|
||
|
|
return { grouped };
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function AdminTournamentsIndex({
|
||
|
|
loaderData,
|
||
|
|
}: Route.ComponentProps) {
|
||
|
|
const { grouped } = loaderData;
|
||
|
|
|
||
|
|
const totalCount = grouped.reduce(
|
||
|
|
(acc, g) => acc + g.tournaments.length,
|
||
|
|
0
|
||
|
|
);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="p-8">
|
||
|
|
<div className="flex items-center justify-between mb-6">
|
||
|
|
<div>
|
||
|
|
<h1 className="text-3xl font-bold">Tournaments</h1>
|
||
|
|
<p className="text-muted-foreground mt-1">
|
||
|
|
Canonical tournaments across all sports. Enter results once here
|
||
|
|
and they fan out to every linked scoring window.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{grouped.length === 0 ? (
|
||
|
|
<Card>
|
||
|
|
<CardContent className="py-12 text-center">
|
||
|
|
<Trophy className="mx-auto h-12 w-12 text-muted-foreground" />
|
||
|
|
<h3 className="mt-4 text-lg font-semibold">
|
||
|
|
No tournaments yet
|
||
|
|
</h3>
|
||
|
|
<p className="text-muted-foreground mt-2">
|
||
|
|
Canonical tournaments are created automatically by the backfill
|
||
|
|
or data-sync flows.
|
||
|
|
</p>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
) : (
|
||
|
|
<div className="space-y-6">
|
||
|
|
<p className="text-sm text-muted-foreground">
|
||
|
|
{totalCount} {totalCount === 1 ? "tournament" : "tournaments"}{" "}
|
||
|
|
across {grouped.length}{" "}
|
||
|
|
{grouped.length === 1 ? "sport" : "sports"}
|
||
|
|
</p>
|
||
|
|
{grouped.map((group) => (
|
||
|
|
<Card key={group.sport.id}>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>{group.sport.name}</CardTitle>
|
||
|
|
<CardDescription>
|
||
|
|
{group.tournaments.length}{" "}
|
||
|
|
{group.tournaments.length === 1
|
||
|
|
? "tournament"
|
||
|
|
: "tournaments"}
|
||
|
|
</CardDescription>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<Table>
|
||
|
|
<TableHeader>
|
||
|
|
<TableRow>
|
||
|
|
<TableHead>Name</TableHead>
|
||
|
|
<TableHead>Year</TableHead>
|
||
|
|
<TableHead>Starts</TableHead>
|
||
|
|
<TableHead>Status</TableHead>
|
||
|
|
<TableHead className="text-right">Actions</TableHead>
|
||
|
|
</TableRow>
|
||
|
|
</TableHeader>
|
||
|
|
<TableBody>
|
||
|
|
{group.tournaments.map((t) => (
|
||
|
|
<TableRow key={t.id}>
|
||
|
|
<TableCell className="font-medium">{t.name}</TableCell>
|
||
|
|
<TableCell>{t.year}</TableCell>
|
||
|
|
<TableCell className="text-muted-foreground">
|
||
|
|
{t.startsAt
|
||
|
|
? new Date(t.startsAt).toLocaleDateString()
|
||
|
|
: "—"}
|
||
|
|
</TableCell>
|
||
|
|
<TableCell>
|
||
|
|
<Badge
|
||
|
|
variant={
|
||
|
|
t.status === "completed"
|
||
|
|
? "default"
|
||
|
|
: t.status === "in_progress"
|
||
|
|
? "secondary"
|
||
|
|
: "outline"
|
||
|
|
}
|
||
|
|
>
|
||
|
|
{t.status}
|
||
|
|
</Badge>
|
||
|
|
</TableCell>
|
||
|
|
<TableCell className="text-right">
|
||
|
|
<Button variant="ghost" size="sm" asChild>
|
||
|
|
<Link to={`/admin/tournaments/${t.id}`}>
|
||
|
|
Open
|
||
|
|
</Link>
|
||
|
|
</Button>
|
||
|
|
</TableCell>
|
||
|
|
</TableRow>
|
||
|
|
))}
|
||
|
|
</TableBody>
|
||
|
|
</Table>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|