- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats. - Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets. - Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly. - Enhanced database schema to accommodate new scoring rules and bracket templates. - Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly. - Documented implementation phases for gradual rollout of new features. - Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
208 lines
7.8 KiB
TypeScript
208 lines
7.8 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="grid grid-cols-2 gap-4">
|
|
<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="playoffRound">Playoff Round (Optional)</Label>
|
|
<Input
|
|
id="playoffRound"
|
|
name="playoffRound"
|
|
type="text"
|
|
placeholder="e.g., Quarterfinals, Round 1"
|
|
/>
|
|
</div>
|
|
</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; playoffRound?: string | null; 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.playoffRound && <span>{event.playoffRound}</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>
|
|
);
|
|
}
|