This commit is contained in:
2026-06-27 15:48:48 -05:00
parent 89a9075391
commit 7bad12df68
3 changed files with 80 additions and 8 deletions

View File

@@ -1,9 +1,10 @@
"use client";
import { useState, useCallback } from "react";
import { useMemo, 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 { STADIUMS, MATCH_STADIUMS } from "@/lib/stadiums";
import Flag from "./Flag";
interface TipState { x: number; y: number; text: string }
@@ -47,16 +48,29 @@ function Side({
);
}
function fmtMatchInfo(utcDate?: string, stadiumId?: string): string {
const city = stadiumId ? STADIUMS[stadiumId]?.city : "";
if (!utcDate) return city; // Stadt auch ohne Datum anzeigen
const d = new Date(utcDate);
const dateStr = d.toLocaleDateString("de-DE", { day: "numeric", month: "numeric" });
const timeStr = d.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
const parts = [`${dateStr}`, `${timeStr}`];
if (city) parts.push(city);
return parts.join(" · ");
}
function Tie({
tie, teams, isFinal, onShowTip, onHideTip,
tie, teams, isFinal, matchInfo, onShowTip, onHideTip,
}: {
tie: ResolvedTie; teams: Team[]; isFinal?: boolean;
matchInfo?: string;
onShowTip: (text: string, x: number, y: number) => void;
onHideTip: () => void;
}) {
return (
<div className={`tie ${isFinal ? "final-tie" : ""}`}>
<div className="tie-head">Spiel {tie.matchNumber}</div>
{matchInfo && <div className="tie-meta">{matchInfo}</div>}
<Side s={tie.home} prob={tie.prob?.home} teams={teams} onShowTip={onShowTip} onHideTip={onHideTip} />
<Side s={tie.away} prob={tie.prob?.away} teams={teams} onShowTip={onShowTip} onHideTip={onHideTip} />
</div>
@@ -77,6 +91,33 @@ export default function Bracket({
const r32ByNum = new Map(r32Array.map((t) => [t.matchNumber, t]));
// Baue slotNumber → formatted info (Stadt aus MATCH_STADIUMS, Datum/Uhrzeit aus Feed-Match)
const feedByNum = useMemo(() => {
const m = new Map<number, Match>();
for (const fm of matches) {
if (fm.group == null) m.set(fm.matchNumber, fm);
}
return m;
}, [matches]);
const matchMeta = useMemo(() => {
const meta = new Map<number, string>();
for (let n = 73; n <= 104; n++) {
const stadiumId = MATCH_STADIUMS[n];
const feedMatch = feedByNum.get(n);
const info = fmtMatchInfo(feedMatch?.utcDate, stadiumId);
if (info) meta.set(n, info);
if (feedMatch?.matchNumber === 0 || !feedMatch) {
console.log("[KO-SLOT-DATE]",
"slot:", n,
"| match.matchNumber:", feedMatch?.matchNumber ?? "—",
"| match.utcDate:", feedMatch?.utcDate ?? "—",
"| stadt:", STADIUMS[MATCH_STADIUMS[n]]?.city ?? "—");
}
}
return meta;
}, [feedByNum]);
const sfOrder = orderedRound([104]);
const qfOrder = orderedRound(sfOrder);
const r16Order = orderedRound(qfOrder);
@@ -109,35 +150,35 @@ export default function Bracket({
<div className="round">
<div className="round-label">Letzte 32</div>
<div className="round-matches">
{r32.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} onShowTip={showTip} onHideTip={hideTip} />)}
{r32.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} matchInfo={matchMeta.get(t.matchNumber)} onShowTip={showTip} onHideTip={hideTip} />)}
</div>
</div>
<div className="round">
<div className="round-label">Achtelfinale</div>
<div className="round-matches">
{r16.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} onShowTip={showTip} onHideTip={hideTip} />)}
{r16.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} matchInfo={matchMeta.get(t.matchNumber)} onShowTip={showTip} onHideTip={hideTip} />)}
</div>
</div>
<div className="round">
<div className="round-label">Viertelfinale</div>
<div className="round-matches">
{qf.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} onShowTip={showTip} onHideTip={hideTip} />)}
{qf.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} matchInfo={matchMeta.get(t.matchNumber)} onShowTip={showTip} onHideTip={hideTip} />)}
</div>
</div>
<div className="round">
<div className="round-label">Halbfinale</div>
<div className="round-matches">
{sf.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} onShowTip={showTip} onHideTip={hideTip} />)}
{sf.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} matchInfo={matchMeta.get(t.matchNumber)} onShowTip={showTip} onHideTip={hideTip} />)}
</div>
</div>
<div className="round">
<div className="round-label">Finale</div>
<div className="round-matches">
{fin && <Tie tie={fin} teams={teams} isFinal onShowTip={showTip} onHideTip={hideTip} />}
{fin && <Tie tie={fin} teams={teams} isFinal matchInfo={matchMeta.get(fin.matchNumber)} onShowTip={showTip} onHideTip={hideTip} />}
{third && (
<div style={{ marginTop: 20 }}>
<div className="round-label" style={{ marginBottom: 8 }}>Spiel um Platz 3</div>
<Tie tie={third} teams={teams} onShowTip={showTip} onHideTip={hideTip} />
<Tie tie={third} teams={teams} matchInfo={matchMeta.get(third.matchNumber)} onShowTip={showTip} onHideTip={hideTip} />
</div>
)}
</div>

View File

@@ -224,6 +224,10 @@ table.standings { width: 100%; border-collapse: collapse; }
font-family: var(--font-mono); font-size: 9px; color: var(--ink-faint);
letter-spacing: 0.04em; padding: 5px 10px 0;
}
.tie-meta {
font-family: var(--font-mono); font-size: 8px; color: var(--ink-dim);
padding: 2px 10px 0;
}
.side {
display: flex; align-items: center; justify-content: space-between;
padding: 8px 10px; font-size: 13px;