"use client"; import { useMemo } from "react"; import { cn } from "@/lib/utils"; interface Player { id: string; firstName: string; lastName: string; level?: string | null; } interface Match { id: string; round: number; position: number; status: string; team1Players: string[]; team2Players: string[]; team1Score: number[] | null; team2Score: number[] | null; winnerId: string | null; scheduledAt: string | null; court: { id: string; name: string; } | null; } interface BracketViewProps { matches: Match[]; players: Map; onMatchClick?: (match: Match) => void; } const statusColors: Record = { SCHEDULED: "bg-gray-50 border-gray-300", IN_PROGRESS: "bg-blue-50 border-blue-400", COMPLETED: "bg-green-50 border-green-300", CANCELLED: "bg-red-50 border-red-300", WALKOVER: "bg-yellow-50 border-yellow-300", }; function getRoundName(round: number, totalRounds: number): string { const roundsFromEnd = totalRounds - round + 1; switch (roundsFromEnd) { case 1: return "Final"; case 2: return "Semifinal"; case 3: return "Cuartos"; case 4: return "Octavos"; case 5: return "Dieciseisavos"; default: return `Ronda ${round}`; } } function formatScore(scores: number[] | null): string { if (!scores || scores.length === 0) return "-"; return scores.join("-"); } function TeamSlot({ playerIds, players, score, isWinner, isEmpty, }: { playerIds: string[]; players: Map; score: number[] | null; isWinner: boolean; isEmpty: boolean; }) { if (isEmpty || playerIds.length === 0) { return (
TBD -
); } const playerNames = playerIds .map((id) => { const player = players.get(id); if (!player) return "Desconocido"; return `${player.firstName} ${player.lastName.charAt(0)}.`; }) .join(" / "); return (
{playerNames} {formatScore(score)}
); } function MatchBox({ match, players, onClick, }: { match: Match; players: Map; onClick?: () => void; }) { const team1IsWinner = match.winnerId && match.team1Players.includes(match.winnerId); const team2IsWinner = match.winnerId && match.team2Players.includes(match.winnerId); const isClickable = match.status !== "COMPLETED" && match.status !== "WALKOVER" && match.team1Players.length > 0 && match.team2Players.length > 0; return (
{/* Match Info Header */}
Partido {match.position} {match.court && {match.court.name}}
{/* Teams */}
vs
{/* Status */}
{match.status === "SCHEDULED" && "Programado"} {match.status === "IN_PROGRESS" && "En Juego"} {match.status === "COMPLETED" && "Finalizado"} {match.status === "WALKOVER" && "Walkover"} {match.status === "CANCELLED" && "Cancelado"}
); } export function BracketView({ matches, players, onMatchClick }: BracketViewProps) { // Group matches by round const matchesByRound = useMemo(() => { const grouped = new Map(); matches.forEach((match) => { const roundMatches = grouped.get(match.round) || []; roundMatches.push(match); grouped.set(match.round, roundMatches); }); // Sort matches in each round by position grouped.forEach((roundMatches) => { roundMatches.sort((a, b) => a.position - b.position); }); return grouped; }, [matches]); const totalRounds = Math.max(...Array.from(matchesByRound.keys()), 0); const rounds = Array.from({ length: totalRounds }, (_, i) => i + 1); if (matches.length === 0) { return (

No hay bracket generado

Genera el bracket para comenzar el torneo

); } return (
{rounds.map((round) => { const roundMatches = matchesByRound.get(round) || []; const roundName = getRoundName(round, totalRounds); // Calculate vertical spacing for bracket alignment const matchesInPreviousRound = round > 1 ? matchesByRound.get(round - 1)?.length || 0 : roundMatches.length * 2; const spacingMultiplier = Math.pow(2, round - 1); return (
{/* Round Header */}

{roundName}

{roundMatches.length} {roundMatches.length === 1 ? "partido" : "partidos"}

{/* Matches */}
{roundMatches.map((match) => ( onMatchClick?.(match)} /> ))}
); })}
{/* Legend */}
Programado
En Juego
Finalizado
Walkover
); }