import { Form, Link, redirect, useNavigate } from "react-router"; import type { Route } from "./+types/admin.sports-seasons.$id"; import { findSportsSeasonById, updateSportsSeason, deleteSportsSeason } from "~/models/sports-season"; import { findParticipantsBySportsSeasonId } from "~/models/participant"; import { processSeasonStandings } from "~/models/scoring-calculator"; import { database } from "~/database/context"; import { participantEvSnapshots } from "~/database/schema"; import { eq, desc } from "drizzle-orm"; import { getSimulatorInfo, type SimulatorType } from "~/services/simulations/registry"; 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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "~/components/ui/alert-dialog"; import { Badge } from "~/components/ui/badge"; import { Trash2, Users, Trophy, Calculator, CheckCircle2, Zap, AlertTriangle, Loader2 } from "lucide-react"; import { useState } from "react"; export async function loader({ params }: Route.LoaderArgs) { const sportsSeason = await findSportsSeasonById(params.id); if (!sportsSeason) { throw new Response("Sports season not found", { status: 404 }); } const participants = await findParticipantsBySportsSeasonId(params.id); // Get the most recent snapshot date (if any) const db = database(); const lastSnapshot = await db .select({ snapshotDate: participantEvSnapshots.snapshotDate }) .from(participantEvSnapshots) .where(eq(participantEvSnapshots.sportsSeasonId, params.id)) .orderBy(desc(participantEvSnapshots.snapshotDate)) .limit(1); const lastSimulatedDate = lastSnapshot[0]?.snapshotDate ?? null; const simulatorInfo = sportsSeason.sport?.simulatorType ? getSimulatorInfo(sportsSeason.sport.simulatorType as SimulatorType) : null; return { sportsSeason, participants, lastSimulatedDate, simulatorInfo, }; } export async function action({ request, params }: Route.ActionArgs) { const formData = await request.formData(); const intent = formData.get("intent"); if (intent === "delete") { await deleteSportsSeason(params.id); return redirect("/admin/sports-seasons"); } if (intent === "finalize-standings") { try { await processSeasonStandings(params.id); await updateSportsSeason(params.id, { status: "completed" }); return { success: true, message: "Standings finalized and fantasy placements assigned!" }; } catch (error) { console.error("Error finalizing standings:", error); return { error: "Failed to finalize standings. Please try again." }; } } // Update const name = formData.get("name"); const year = formData.get("year"); const startDate = formData.get("startDate"); const endDate = formData.get("endDate"); const status = formData.get("status"); const scoringType = formData.get("scoringType"); const scoringPattern = formData.get("scoringPattern"); const totalMajors = formData.get("totalMajors"); // Validation if (typeof name !== "string" || !name.trim()) { return { error: "Season name is required" }; } if (typeof year !== "string") { return { error: "Year is required" }; } const yearNum = parseInt(year, 10); if (isNaN(yearNum) || yearNum < 2000 || yearNum > 2100) { return { error: "Year must be between 2000 and 2100" }; } if (status !== "upcoming" && status !== "active" && status !== "completed") { return { error: "Invalid status" }; } if (scoringType !== "playoffs" && scoringType !== "regular_season" && scoringType !== "majors") { return { error: "Invalid scoring type" }; } const validScoringPatterns = ["playoff_bracket", "season_standings", "qualifying_points"]; if (scoringPattern && typeof scoringPattern === "string" && !validScoringPatterns.includes(scoringPattern)) { return { error: "Invalid scoring pattern" }; } try { const updateData: any = { name: name.trim(), year: yearNum, startDate: typeof startDate === "string" && startDate ? startDate : null, endDate: typeof endDate === "string" && endDate ? endDate : null, status, scoringType, }; if (scoringPattern && typeof scoringPattern === "string") { updateData.scoringPattern = scoringPattern; } if (totalMajors && typeof totalMajors === "string") { const totalMajorsNum = parseInt(totalMajors, 10); if (!isNaN(totalMajorsNum) && totalMajorsNum > 0) { updateData.totalMajors = totalMajorsNum; } } await updateSportsSeason(params.id, updateData); return { success: true, message: "Sports season updated successfully!" }; } catch (error) { console.error("Error updating sports season:", error); return { error: "Failed to update sports season. Please try again." }; } } export default function EditSportsSeason({ loaderData, actionData }: Route.ComponentProps) { const { sportsSeason, participants, lastSimulatedDate, simulatorInfo } = loaderData; const navigate = useNavigate(); const [scoringPattern, setScoringPattern] = useState(sportsSeason.scoringPattern || ""); return (

Edit Sports Season

{sportsSeason.sport.name} - {sportsSeason.name}

Sports Season Details Update the information for this sports season

Playoffs: Team sports playoffs. Regular Season: Full season standings. Majors: Individual sport majors.

Qualifying Points: For sports like Golf/Tennis where participants earn points across majors, then top 8 get fantasy points.

{scoringPattern === "qualifying_points" && (

How many major tournaments will be tracked? (e.g., Golf has 4 majors, Tennis has 4 Grand Slams)

)} {actionData?.error && (
{actionData.error}
)} {actionData?.success && (
{actionData.message}
)}
Participants {participants.length} {participants.length === 1 ? "participant" : "participants"}
{participants.length === 0 ? (

No participants added yet. Add teams or players to this season.

) : (
{participants.slice(0, 5).map((participant) => (
{participant.name}
))} {participants.length > 5 && (

And {participants.length - 5} more...

)}
)}
Expected Values {simulatorInfo ? simulatorInfo.name : "Manage probability distributions and projected points"}
{sportsSeason.simulationStatus === "failed" && ( Last run failed )} {simulatorInfo && (
)}

{simulatorInfo ? <> {simulatorInfo.description}.{" "} {lastSimulatedDate ? `Last simulated: ${lastSimulatedDate}.` : "No simulation has been run yet."} {" "}Import futures odds first, then run the simulation to update EVs and save a snapshot. : "Import futures odds to set probability distributions."}

Scoring Events Manage games, tournaments, and schedule entries

Create playoff brackets, major tournaments, or import a race/game schedule.

{sportsSeason.scoringPattern === "season_standings" && (
Championship Standings Update participant positions and points as the season progresses
)} {sportsSeason.scoringPattern === "season_standings" && (
Finalize Standings {sportsSeason.status === "completed" && ( Finalized )} {sportsSeason.status === "completed" ? "Season standings have been finalized and fantasy placements assigned." : "When the championship is over, finalize standings to assign fantasy placements (1st–8th) to participants based on their final positions."}
{sportsSeason.status !== "completed" && ( {actionData?.error && (
{actionData.error}
)} {actionData?.success && actionData.message?.includes("finalized") && (
{actionData.message}
)} Finalize championship standings? This will read the current participant standings and assign fantasy placements (1st through 8th place). The season will be marked as completed. This action can be re-run to correct results if needed. Cancel
Finalize Standings
)}
)} Danger Zone Permanently delete this sports season Are you absolutely sure? This will permanently delete the sports season "{sportsSeason.name}" and all associated participants and results. This action cannot be undone. Cancel
Delete
); }