import { Form, Link } from "react-router"; import { useState } from "react"; import type { Route } from "./+types/admin._index"; import { countSports } from "~/models/sport"; import { countSportsSeasons } from "~/models/sports-season"; import { countSeasonTemplates } from "~/models/season-template"; import { getEventsForDates } from "~/models/scoring-event"; import { countSeasonsByStatus } from "~/models/season"; 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, Users } from "lucide-react"; import { seedOrRepairBracktTemplate } from "~/services/brackt.server"; export async function action({ request }: Route.ActionArgs) { const formData = await request.formData(); if (formData.get("intent") === "seed-brackt") { await seedOrRepairBracktTemplate(); return { success: true, message: "Brackt sport and template repaired." }; } return { error: "Invalid action" }; } 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 [sportsCount, sportsSeasonsCount, templatesCount, upcomingEvents, seasonStatusCounts] = await Promise.all([ countSports(), countSportsSeasons(), countSeasonTemplates(), getEventsForDates([today, tomorrow]), countSeasonsByStatus(), ]); return { stats: { sportsCount, sportsSeasonsCount, templatesCount }, seasonStatusCounts, upcomingEvents, today, tomorrow, }; } function formatEventTime(eventStartsAt: string | null): string | null { if (!eventStartsAt) return null; const d = new Date(eventStartsAt); return d.toLocaleTimeString("en-US", { 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; displayDate: 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.displayDate === today); const tomorrowEvents = events.filter((e) => e.displayDate === 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, actionData }: Route.ComponentProps) { const { stats, seasonStatusCounts, 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, })); const systemIsPopulated = stats.sportsCount > 0; return (

Admin Dashboard

Manage sports, seasons, participants, and templates

Seasons by Status
{( [ { key: "pre_draft", label: "Pre-Draft" }, { key: "draft", label: "Drafting" }, { key: "active", label: "Active" }, { key: "completed", label: "Completed" }, ] as const ).map(({ key, label }) => (
{label} {seasonStatusCounts[key]}
))}
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
{actionData && "message" in actionData && (

{actionData.message}

)}
{!systemIsPopulated && ( 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

)}
); }