import { Form, useLoaderData, useActionData, useNavigation, Link } from 'react-router'; import type { Route } from './+types/admin.sports-seasons.$id.events.$eventId.cs2-setup'; import { findSportsSeasonById } from '~/models/sports-season'; import { getScoringEventById } from '~/models/scoring-event'; import { findParticipantsBySportsSeasonId } from '~/models/participant'; import { getCs2StageResultsForEvent, upsertCs2StageAssignments, markCs2StageEliminations, clearCs2StageAssignments, } from '~/models/cs2-major-stage'; import { Button } from '~/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '~/components/ui/card'; import { Badge } from '~/components/ui/badge'; import { ArrowLeft, Save, Trash2, CheckCircle2 } from 'lucide-react'; import { useState } from 'react'; export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { return [{ title: `CS2 Stage Setup — ${data?.event?.name ?? "Event"} - Brackt Admin` }]; } export async function loader({ params }: Route.LoaderArgs) { const sportsSeasonId = params.id; const eventId = params.eventId; const [sportsSeason, event] = await Promise.all([ findSportsSeasonById(sportsSeasonId), getScoringEventById(eventId), ]); if (!sportsSeason) throw new Response('Sports season not found', { status: 404 }); if (!event) throw new Response('Event not found', { status: 404 }); const [participants, stageResults] = await Promise.all([ findParticipantsBySportsSeasonId(sportsSeasonId), getCs2StageResultsForEvent(eventId), ]); return { sportsSeason, event, participants, stageResults }; } interface ActionData { success?: boolean; message?: string; } export async function action({ request, params }: Route.ActionArgs) { const eventId = params.eventId; const formData = await request.formData(); const intent = formData.get('intent') as string; if (intent === 'reset') { await clearCs2StageAssignments(eventId); return { success: true, message: 'Stage assignments cleared.' }; } if (intent === 'assign') { const assignments: Array<{ scoringEventId: string; participantId: string; stageEntry: number }> = []; for (const [key, value] of formData.entries()) { if (key.startsWith('stage_')) { const participantId = key.slice('stage_'.length); const stageEntry = parseInt(value as string, 10); if (!isNaN(stageEntry) && stageEntry >= 1 && stageEntry <= 3) { assignments.push({ scoringEventId: eventId, participantId, stageEntry }); } } } if (assignments.length === 0) { return { success: false, message: 'No stage assignments submitted.' }; } await upsertCs2StageAssignments(assignments); return { success: true, message: `Saved ${assignments.length} stage assignments.` }; } if (intent === 'mark-eliminations') { // Parse elimination data: elim_{participantId} = winsAtElimination (0, 1, or 2) const eliminations: Array<{ participantId: string; winsAtElimination: number }> = []; for (const [key, value] of formData.entries()) { if (key.startsWith('elim_')) { const participantId = key.slice('elim_'.length); const wins = parseInt(value as string, 10); if (!isNaN(wins) && wins >= 0 && wins <= 2) { eliminations.push({ participantId, winsAtElimination: wins }); } } } await markCs2StageEliminations(eventId, eliminations); return { success: true, message: `Marked ${eliminations.length} eliminations.` }; } return { success: false, message: 'Unknown action.' }; } const STAGE_LABELS: Record = { 1: { label: 'Stage 1', description: 'Opening Stage (16 teams, Bo1 Swiss)', maxTeams: 16, badgeVariant: 'outline' }, 2: { label: 'Stage 2', description: 'Elimination Stage (8 Challengers, Bo1/Bo3 Swiss)', maxTeams: 8, badgeVariant: 'secondary' }, 3: { label: 'Stage 3', description: 'Decider Stage (8 Legends, all Bo3 Swiss)', maxTeams: 8, badgeVariant: 'default' }, }; const RECORD_OPTIONS = [ { value: '2', label: '2-3 (advanced furthest)' }, { value: '1', label: '1-3' }, { value: '0', label: '0-3 (earliest exit)' }, ]; export default function AdminCs2Setup() { const { sportsSeason, event, participants, stageResults } = useLoaderData(); const actionData = useActionData(); const navigation = useNavigation(); const isSubmitting = navigation.state === 'submitting'; // Stage assignment state (local, submitted via form) const [stageSelections, setStageSelections] = useState>(() => { const initial: Record = {}; stageResults.forEach(r => { initial[r.participantId] = r.stageEntry.toString(); }); return initial; }); // Elimination checkbox state — tracks which teams are checked as eliminated const [eliminatedChecked, setEliminatedChecked] = useState>(() => { const initial: Record = {}; stageResults.forEach(r => { initial[r.participantId] = r.stageEliminated !== null; }); return initial; }); // Group participants by stage for display const byStage: Record = { 1: [], 2: [], 3: [] }; for (const r of stageResults) { byStage[r.stageEntry]?.push(r); } const stageCounts = Object.fromEntries( Object.entries(byStage).map(([k, v]) => [k, v.length]) ); return (
Back to event

CS2 Stage Setup

{event.name} — {sportsSeason.name}

{actionData?.message && (
{actionData.message}
)} {/* Stage Assignment */} Stage Assignments {stageResults.length > 0 && (
)}
Assign each team to their starting stage. Stage 1 = 16 Opening Stage teams, Stage 2 = 8 Challengers (skip Stage 1), Stage 3 = 8 Legends (skip Stages 1 and 2). Total: 32 teams across all three stages.
{/* Stage summary badges */}
{[1, 2, 3].map(stage => (
{STAGE_LABELS[stage].label} {stageCounts[stage] ?? 0}/{STAGE_LABELS[stage].maxTeams} teams
))}
{/* Teams listed with stage selectors */}
Team Starting Stage
{participants.map(participant => (
{participant.name}
))}
{/* Advancement Tracking (only shown when stage assignments exist) */} {stageResults.length > 0 && ( Stage Advancement Tracking Record which teams were eliminated and their W-L record at elimination. Teams not listed as eliminated are assumed to have advanced (or are still playing). Stage 3 W-L records determine QP sub-placements (9–16).
{[1, 2, 3].map(stage => { const teamsInStage = byStage[stage]; if (teamsInStage.length === 0) return null; return (

{STAGE_LABELS[stage].label} {STAGE_LABELS[stage].description}

{teamsInStage.map(r => { const isEliminated = eliminatedChecked[r.participantId] ?? false; return (
{r.participantName} {r.stageEliminated !== null && ( eliminated {r.stageEliminatedWins}-3 )} {isEliminated && ( )} {!isEliminated &&
}
); })}
); })} )} {/* Champions Stage link */} {stageResults.length > 0 && ( Champions Stage (Playoffs) The 8-team single-elimination bracket. Use the bracket admin to track Quarterfinals, Semifinals, and Grand Final results. )}
); }