"use client"; import { useState, useCallback } from "react"; import { GroupTable, Match, Team, ThirdPlaceRow } from "@/lib/types"; import { ThirdAssignment, orderedRound } from "@/lib/bracket"; import { resolveBracket, ResolvedSide, ResolvedTie } from "@/lib/resolve-bracket"; import Flag from "./Flag"; interface TipState { x: number; y: number; text: string } function Side({ s, prob, teams, onShowTip, onHideTip, }: { s: ResolvedSide; prob?: number | null; teams: Team[]; onShowTip: (text: string, x: number, y: number) => void; onHideTip: () => void; }) { const team = s.teamId ? teams.find((t) => t.id === s.teamId) : undefined; const fix = s.teamId != null && !s.provisional; const hasTip = !!s.tooltip; return (
) => onShowTip(s.tooltip!, e.clientX, e.clientY), onMouseMove: (e: React.MouseEvent) => onShowTip(s.tooltip!, e.clientX, e.clientY), onMouseLeave: onHideTip, } : {})} > {s.teamId ? ( <> {s.label} {s.provisional && } ) : ( {s.label} )} {prob != null && prob > 0 && ( {Math.round(prob * 100)}% )} {s.score != null ? s.score : "–"}
); } function Tie({ tie, teams, isFinal, onShowTip, onHideTip, }: { tie: ResolvedTie; teams: Team[]; isFinal?: boolean; onShowTip: (text: string, x: number, y: number) => void; onHideTip: () => void; }) { return (
Spiel {tie.matchNumber}
); } export default function Bracket({ matches, teams, tables, thirds, assignment, annexResolved, preResolved, }: { matches: Match[]; teams: Team[]; tables: GroupTable[]; thirds: ThirdPlaceRow[]; assignment: ThirdAssignment | null; annexResolved: boolean; preResolved?: { r32: ResolvedTie[]; later: Record }; }) { const { r32: r32Array, later } = preResolved ?? resolveBracket( matches, teams, tables, thirds, assignment, annexResolved, ); const r32ByNum = new Map(r32Array.map((t) => [t.matchNumber, t])); const sfOrder = orderedRound([104]); const qfOrder = orderedRound(sfOrder); const r16Order = orderedRound(qfOrder); const r32Order = orderedRound(r16Order); const r32 = r32Order.map((n) => r32ByNum.get(n)).filter(Boolean) as ResolvedTie[]; const r16 = r16Order.map((n) => later[n]).filter(Boolean); const qf = qfOrder.map((n) => later[n]).filter(Boolean); const sf = sfOrder.map((n) => later[n]).filter(Boolean); const fin = later[104]; const third = later[103]; const [tip, setTip] = useState(null); const showTip = useCallback((text: string, x: number, y: number) => setTip({ x, y, text }), []); const hideTip = useCallback(() => setTip(null), []); return (
Annex-C-Zuordnung der Drittplatzierten: {annexResolved ? "aufgelöst — die acht Dritten sind den Gruppensiegern fest zugeteilt" : "noch offen — sobald die 8 besten Dritten feststehen, verbindet sich der Baum automatisch"}
Letzte 32
{r32.map((t) => )}
Achtelfinale
{r16.map((t) => )}
Viertelfinale
{qf.map((t) => )}
Halbfinale
{sf.map((t) => )}
Finale
{fin && } {third && (
Spiel um Platz 3
)}
Sieger / weiter Finale fix qualifiziert vorläufig (≈, nach aktueller Tabelle) Platzhalter offen %-Werte: Polymarket-Wahrscheinlichkeit (falls verfügbar)
{tip && (
{tip.text}
)}
); }