import { Link } from "react-router"; import { useState } from "react"; import type { Route } from "./+types/admin._index"; import { findAllSports } from "~/models/sport"; import { findAllSportsSeasons } from "~/models/sports-season"; import { findAllSeasonTemplates } from "~/models/season-template"; import { getEventsForDates } from "~/models/scoring-event"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "~/components/ui/card"; import { Button } from "~/components/ui/button"; import { Badge } from "~/components/ui/badge"; import { Trophy, Calendar, FolderKanban, ArrowRight, CheckCircle2, Clock, ExternalLink } from "lucide-react"; export function meta(): Route.MetaDescriptors { return [{ title: "Admin - Brackt" }]; } function toDateStr(d: Date) { return d.toISOString().split("T")[0]; } function getTodayAndTomorrowDates() { const today = new Date(); const tomorrow = new Date(today); tomorrow.setDate(today.getDate() + 1); return { today: toDateStr(today), tomorrow: toDateStr(tomorrow) }; } export async function loader() { const { today, tomorrow } = getTodayAndTomorrowDates(); const [sports, sportsSeasons, templates, upcomingEvents] = await Promise.all([ findAllSports(), findAllSportsSeasons(), findAllSeasonTemplates(), getEventsForDates([today, tomorrow]), ]); return { stats: { sportsCount: sports.length, sportsSeasonsCount: sportsSeasons.length, templatesCount: templates.length, }, upcomingEvents, today, tomorrow, }; } function formatEventTime(eventStartsAt: string | null): string | null { if (!eventStartsAt) return null; const d = new Date(eventStartsAt); return d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }); } function getEventTypeLabel(eventType: string): string { switch (eventType) { case "playoff_game": return "Bracket"; case "major_tournament": return "Tournament"; case "final_standings": return "Final Standings"; default: return eventType; } } type DashboardEvent = { id: string; name: string; eventDate: string | null; eventStartsAt: string | null; eventType: string; isComplete: boolean; completedAt: string | null; sportsSeasonId: string; sportsSeasonName: string; sportName: string; sportSlug: string | null; }; function GamesToScore({ events, today, tomorrow, }: { events: DashboardEvent[]; today: string; tomorrow: string; }) { const [activeTab, setActiveTab] = useState<"today" | "tomorrow">("today"); const todayEvents = events.filter((e) => e.eventDate === today); const tomorrowEvents = events.filter((e) => e.eventDate === tomorrow); const displayEvents = activeTab === "today" ? todayEvents : tomorrowEvents; const scored = displayEvents.filter((e) => e.isComplete).length; const total = displayEvents.length; const pending = total - scored; return (
Games to Score Scoring events that need your attention
{total > 0 && (
{scored}/{total}

scored

)}
{/* Tab selector */}
{displayEvents.length === 0 ? (
No scoring events scheduled for {activeTab === "today" ? "today" : "tomorrow"}.
) : ( <> {pending > 0 && (
{pending} event{pending !== 1 ? "s" : ""} still need{pending === 1 ? "s" : ""} scoring
)} {pending === 0 && total > 0 && (
All events scored for {activeTab === "today" ? "today" : "tomorrow"}!
)}
{displayEvents.map((event) => (
{event.name} {getEventTypeLabel(event.eventType)}
{event.sportName} · {event.sportsSeasonName} {event.eventStartsAt && ( <> · {formatEventTime(event.eventStartsAt)} )}
{event.isComplete ? ( Scored ) : ( Pending )}
))}
)}
); } export default function AdminDashboard({ loaderData }: Route.ComponentProps) { const { stats, upcomingEvents, today, tomorrow } = loaderData; // Serialize dates to strings for the client component const serializedEvents: DashboardEvent[] = upcomingEvents.map((e) => ({ ...e, eventStartsAt: e.eventStartsAt ? new Date(e.eventStartsAt).toISOString() : null, completedAt: e.completedAt ? new Date(e.completedAt).toISOString() : null, })); return (

Admin Dashboard

Manage sports, seasons, participants, and templates

Total Sports
{stats.sportsCount}

Active sports in the system

Sports Seasons
{stats.sportsSeasonsCount}

Across all sports

Templates
{stats.templatesCount}

Season templates available

{/* Games to Score widget — full width */}
Quick Actions Common administrative tasks Getting Started Set up your sports system
1

Create Sports

Add sports like NFL, NBA, Golf, etc.

2

Create Sports Seasons

Add specific seasons for each sport

3

Add Participants

Add teams or players to each sports season

4

Create Templates

Bundle sports seasons into templates for leagues

); }