"use client"; import { useState, useMemo, useCallback, useEffect } from "react"; import { Match, Team } from "@/lib/types"; import { computeGroupTables, computeThirdPlaceTable } from "@/lib/standings"; import { qualifiedThirdGroups, resolveAnnexC } from "@/lib/bracket"; import { resolveBracket, ResolvedTie } from "@/lib/resolve-bracket"; import { loadOverrides, saveOverrides, buildSimMatches, propagateSimWinners, defaultScore, SimOverrides, } from "@/lib/simulation"; import Bracket from "./Bracket"; import Flag from "./Flag"; function teamById(teams: Team[], id: string | null) { return id ? teams.find((t) => t.id === id) : undefined; } function fmtDate(iso: string): string { const d = new Date(iso); return d.toLocaleString("de-DE", { weekday: "short", day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit", }); } // Label für die Spielphase in der Liste function phaseLabel(match: Match): string { if (match.stage === "GROUP" && match.group) return `Gruppe ${match.group}`; if (match.stage === "R32") return "Sechzehntelfinale"; if (match.stage === "R16") return "Achtelfinale"; if (match.stage === "QF") return "Viertelfinale"; if (match.stage === "SF") return "Halbfinale"; if (match.stage === "3RD") return "Spiel um Platz 3"; if (match.stage === "FINAL") return "Finale"; return match.stage; } export default function Simulation({ teams, matches }: { teams: Team[]; matches: Match[] }) { const [overrides, setOverrides] = useState({}); useEffect(() => { setOverrides(loadOverrides()); }, []); const handleScore = useCallback((matchId: string, homeScore: number, awayScore: number) => { setOverrides((prev) => { const next = { ...prev, [matchId]: { homeScore, awayScore } }; saveOverrides(next); return next; }); }, []); const handleReset = useCallback(() => { setOverrides({}); saveOverrides({}); }, []); const handleClear = useCallback((matchId: string) => { setOverrides((prev) => { const next = { ...prev }; delete next[matchId]; saveOverrides(next); return next; }); }, []); // ---- Simulations-Pipeline ---- const simMatches = useMemo(() => buildSimMatches(matches, overrides), [matches, overrides]); const simTables = useMemo(() => computeGroupTables(teams, simMatches), [teams, simMatches]); const simThirds = useMemo(() => computeThirdPlaceTable(simTables), [simTables]); const simQGroups = useMemo(() => qualifiedThirdGroups(simThirds), [simThirds]); const simAnnex = useMemo( () => (simQGroups.length === 8 ? resolveAnnexC(simQGroups) : null), [simQGroups], ); const simAnnexResolved = simAnnex != null; const { r32, later: laterRaw } = useMemo( () => resolveBracket(simMatches, teams, simTables, simThirds, simAnnex, simAnnexResolved), [simMatches, teams, simTables, simThirds, simAnnex, simAnnexResolved], ); const later = useMemo(() => propagateSimWinners(r32, laterRaw), [r32, laterRaw]); const preResolved = useMemo(() => ({ r32, later }), [r32, later]); // ---- Eingabeliste: offene Spiele aus dem echten Feed ---- const openMatches = useMemo( () => matches .filter((m) => m.status !== "FINISHED") .sort((a, b) => +new Date(a.utcDate) - +new Date(b.utcDate)), [matches], ); const groupPhase = openMatches.filter((m) => m.stage === "GROUP"); const koPhase = openMatches.filter((m) => m.stage !== "GROUP"); const hasChanges = Object.keys(overrides).length > 0; return (
Simulation — Ergebnisse frei wählbar, beeinflusst nicht die echten Daten. Alle ungespielten Spiele sind vorbelegt mit dem Polymarket-Favoriten. Eingaben werden im Browser gespeichert.
{/* ---- Eingabeliste ---- */}

Offene Spiele ({openMatches.length})

{hasChanges && ( )}
{groupPhase.length > 0 && (
Gruppenphase
{groupPhase.map((m) => ( ))}
)} {koPhase.length > 0 && (
K.o.-Phase
{koPhase.map((m) => ( ))}
)}
{/* ---- Simulierter K.o.-Baum ---- */}

Simulierter K.o.-Baum

); } // ---- Einzelne Eingabezeile ---- function SimRow({ match, teams, overrides, onScore, onClear, }: { match: Match; teams: Team[]; overrides: SimOverrides; onScore: (id: string, h: number, a: number) => void; onClear: (id: string) => void; }) { const home = teamById(teams, match.homeTeamId); const away = teamById(teams, match.awayTeamId); const def = defaultScore(match); const ov = overrides[match.id]; const homeVal = ov?.homeScore ?? def?.homeScore; const awayVal = ov?.awayScore ?? def?.awayScore; const homeChanged = ov != null; const isKO = match.stage !== "GROUP"; const inputStyle = (changed: boolean): React.CSSProperties => ({ width: 36, height: 28, textAlign: "center", fontFamily: "var(--font-mono)", fontSize: 14, fontWeight: 700, color: changed ? "var(--turf)" : "var(--ink)", background: "var(--bg-raised)", border: `1px solid ${changed ? "var(--turf)" : "var(--line)"}`, borderRadius: "var(--radius-sm)", outline: "none", MozAppearance: "textfield", }); return (
{phaseLabel(match)} {home?.name ?? "—"} { const v = parseInt(e.target.value, 10); if (!isNaN(v) && v >= 0) { onScore(match.id, v, awayVal ?? (isKO ? 0 : 1)); } else if (e.target.value === "") { // Bei Leerung: Standard wiederherstellen const fallback = def; if (fallback) onScore(match.id, fallback.homeScore, fallback.awayScore); } }} /> : { const v = parseInt(e.target.value, 10); if (!isNaN(v) && v >= 0) { onScore(match.id, homeVal ?? (isKO ? 0 : 1), v); } else if (e.target.value === "") { const fallback = def; if (fallback) onScore(match.id, fallback.homeScore, fallback.awayScore); else onClear(match.id); } }} /> {away?.name ?? "—"} {fmtDate(match.utcDate)}
); }