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 { 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 } 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); // Type assertion since we know the sport relation is included return { sportsSeason: sportsSeason as typeof sportsSeason & { sport: { id: string; name: string; type: string; slug: string } }, participants }; } 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 } = 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 Manage probability distributions and projected points

Generate probabilities from betting odds (Futures Odds), manually enter them (Manual Entry), or recalculate based on results (Recalculate).

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
); }