195 lines
7.2 KiB
TypeScript
195 lines
7.2 KiB
TypeScript
import { Form, Link } from "react-router";
|
|
import type { Route } from "./+types/admin.sports-seasons.$id.events";
|
|
import { loader, action } from "./admin.sports-seasons.$id.events.server";
|
|
import { Button } from "~/components/ui/button";
|
|
import { Input } from "~/components/ui/input";
|
|
import { Label } from "~/components/ui/label";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "~/components/ui/card";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "~/components/ui/select";
|
|
import { Badge } from "~/components/ui/badge";
|
|
import { Calendar, Trophy, ArrowLeft, Trash2 } from "lucide-react";
|
|
import { format } from "date-fns";
|
|
|
|
export { loader, action };
|
|
|
|
export default function SportsSeasonEvents({
|
|
loaderData,
|
|
actionData,
|
|
}: Route.ComponentProps) {
|
|
const { sportsSeason, events } = loaderData;
|
|
|
|
const getEventTypeLabel = (type: string) => {
|
|
switch (type) {
|
|
case "playoff_game":
|
|
return "Bracket";
|
|
case "major_tournament":
|
|
return "Major Tournament";
|
|
case "final_standings":
|
|
return "Final Standings";
|
|
default:
|
|
return type;
|
|
}
|
|
};
|
|
|
|
const getStatusBadge = (isComplete: boolean) => {
|
|
return isComplete ? (
|
|
<Badge variant="default" className="bg-green-500">
|
|
Completed
|
|
</Badge>
|
|
) : (
|
|
<Badge variant="secondary">In Progress</Badge>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="p-8">
|
|
<div className="max-w-4xl">
|
|
<div className="mb-6">
|
|
<Button variant="ghost" size="sm" asChild className="mb-2">
|
|
<Link to={`/admin/sports-seasons/${sportsSeason.id}`}>
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
Back to Sports Season
|
|
</Link>
|
|
</Button>
|
|
<h1 className="text-3xl font-bold">Scoring Events</h1>
|
|
<p className="text-muted-foreground mt-1">
|
|
{sportsSeason.sport.name} - {sportsSeason.name}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
{/* Create New Event Card */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Create New Event</CardTitle>
|
|
<CardDescription>
|
|
Add a new scoring event (bracket, tournament, standings, etc.)
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form method="post" className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="name">Event Name</Label>
|
|
<Input
|
|
id="name"
|
|
name="name"
|
|
type="text"
|
|
placeholder="e.g., Super Bowl, Round 1, Game 3"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="eventType">Event Type</Label>
|
|
<Select name="eventType" defaultValue="playoff_game" required>
|
|
<SelectTrigger id="eventType">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="playoff_game">Bracket</SelectItem>
|
|
<SelectItem value="major_tournament">Major Tournament</SelectItem>
|
|
<SelectItem value="final_standings">Final Standings</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="eventDate">Event Date (Optional)</Label>
|
|
<Input id="eventDate" name="eventDate" type="date" />
|
|
</div>
|
|
|
|
{actionData?.error && (
|
|
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
|
{actionData.error}
|
|
</div>
|
|
)}
|
|
|
|
<Button type="submit" className="w-full">
|
|
<Trophy className="mr-2 h-4 w-4" />
|
|
Create Event
|
|
</Button>
|
|
</Form>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Events List */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Events</CardTitle>
|
|
<CardDescription>
|
|
{events.length} {events.length === 1 ? "event" : "events"}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{events.length === 0 ? (
|
|
<p className="text-sm text-muted-foreground">
|
|
No events created yet. Create your first event above.
|
|
</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{events.map((event: { id: string; name: string; eventType: string; eventDate?: string | null; isComplete: boolean }) => (
|
|
<Card key={event.id} className="hover:border-primary/50 transition-colors">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between gap-4">
|
|
<Link
|
|
to={`/admin/sports-seasons/${sportsSeason.id}/events/${event.id}`}
|
|
className="flex-1 cursor-pointer"
|
|
>
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<h3 className="font-semibold">{event.name}</h3>
|
|
{getStatusBadge(event.isComplete)}
|
|
</div>
|
|
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
|
<span>
|
|
{getEventTypeLabel(event.eventType)}
|
|
</span>
|
|
{event.eventDate && (
|
|
<span className="flex items-center gap-1">
|
|
<Calendar className="h-3 w-3" />
|
|
{format(new Date(event.eventDate), "MMM d, yyyy")}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</Link>
|
|
<Form method="post" className="flex-shrink-0">
|
|
<input type="hidden" name="intent" value="delete-event" />
|
|
<input type="hidden" name="eventId" value={event.id} />
|
|
<Button
|
|
type="submit"
|
|
size="sm"
|
|
variant="ghost"
|
|
className="text-destructive hover:text-destructive"
|
|
onClick={(e) => {
|
|
if (!confirm(`Delete event "${event.name}"? This will also delete all results.`)) {
|
|
e.preventDefault();
|
|
}
|
|
}}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</Form>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|