import { Form, Link } from "react-router"; import type { Route } from "./+types/admin.sports-seasons.$id.participants"; import { logger } from "~/lib/logger"; import { findSportsSeasonById } from "~/models/sports-season"; import { findParticipantsBySportsSeasonId, findParticipantByName, createParticipant, deleteParticipant, } from "~/models/participant"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; import { Textarea } from "~/components/ui/textarea"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "~/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "~/components/ui/table"; import { Plus, Trash2, ArrowLeft } from "lucide-react"; export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { return [{ title: `Participants — ${data?.sportsSeason?.name ?? "Sports Season"} - Brackt Admin` }]; } 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") { const participantId = formData.get("participantId"); if (typeof participantId === "string") { await deleteParticipant(participantId); } return { success: true }; } // Bulk add participants if (intent === "bulk") { const bulkNames = formData.get("bulkNames"); if (typeof bulkNames !== "string" || !bulkNames.trim()) { return { error: "Please enter at least one participant name" }; } // Dedup case-insensitively, preserving first occurrence's casing const seenKeys = new Map(); for (const raw of bulkNames.split("\n").map(n => n.trim()).filter(n => n.length > 0)) { const key = raw.toLowerCase(); if (!seenKeys.has(key)) seenKeys.set(key, raw); } const names = [...seenKeys.values()]; if (names.length === 0) { return { error: "Please enter at least one participant name" }; } try { const existingParticipants = await findParticipantsBySportsSeasonId(params.id); const existingNames = new Set(existingParticipants.map(p => p.name.toLowerCase())); const skipped: string[] = []; let added = 0; for (const name of names) { if (existingNames.has(name.toLowerCase())) { skipped.push(name); continue; } await createParticipant({ sportsSeasonId: params.id, name, shortName: null, externalId: null, expectedValue: "0", }); added++; } return { success: true, count: added, skipped }; } catch (error) { logger.error("Error creating participants:", error); return { error: "Failed to add participants. Please try again." }; } } // Add single participant const name = formData.get("name"); if (typeof name !== "string" || !name.trim()) { return { error: "Participant name is required" }; } const trimmedName = name.trim(); const existing = await findParticipantByName(params.id, trimmedName); if (existing) { return { error: `"${trimmedName}" already exists in this season.` }; } try { await createParticipant({ sportsSeasonId: params.id, name: trimmedName, shortName: null, externalId: null, expectedValue: "0", }); return { success: true }; } catch (error) { logger.error("Error creating participant:", error); return { error: "Failed to add participant. Please try again." }; } } export default function ManageParticipants({ loaderData, actionData }: Route.ComponentProps) { const { sportsSeason, participants } = loaderData; // Use success state to reset forms by changing the key const formKey = actionData?.success ? Date.now() : 'static'; return (

Manage Participants

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

Add Participant Add a {sportsSeason.sport.type === "team" ? "team" : "player"} to this season
{actionData?.error && !actionData?.count && (
{actionData.error}
)} {actionData?.success && !actionData?.count && (
Participant added successfully!
)}
Bulk Add Participants Add multiple participants at once (one per line)