This commit is contained in:
2026-06-23 17:44:38 -05:00
parent e2bfb0a17d
commit 8dcf9d5e08
2 changed files with 65 additions and 19 deletions

View File

@@ -1,20 +1,32 @@
"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,
}: { s: ResolvedSide; prob?: number | null; teams: Team[] }) {
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;
// fix = ein echtes Team steht fest (nicht vorläufig). Eigene Klasse für klares Styling.
const fix = s.teamId != null && !s.provisional;
const hasTip = !!s.tooltip;
return (
<div
className={`side ${s.isWinner ? "win" : ""} ${s.provisional ? "prov" : ""} ${fix ? "fix" : ""}`}
title={s.tooltip ?? undefined}
{...(hasTip ? {
onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => onShowTip(s.tooltip!, e.clientX, e.clientY),
onMouseMove: (e: React.MouseEvent<HTMLDivElement>) => onShowTip(s.tooltip!, e.clientX, e.clientY),
onMouseLeave: onHideTip,
} : {})}
>
<span className="nm">
{s.teamId ? (
@@ -35,12 +47,18 @@ function Side({
);
}
function Tie({ tie, teams, isFinal }: { tie: ResolvedTie; teams: Team[]; isFinal?: boolean }) {
function Tie({
tie, teams, isFinal, onShowTip, onHideTip,
}: {
tie: ResolvedTie; teams: Team[]; isFinal?: boolean;
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>
<Side s={tie.home} prob={tie.prob?.home} teams={teams} />
<Side s={tie.away} prob={tie.prob?.away} teams={teams} />
<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>
);
}
@@ -57,8 +75,6 @@ export default function Bracket({
const r32ByNum = new Map(r32Array.map((t) => [t.matchNumber, t]));
// Korrekte Baum-Reihenfolge: von der Wurzel (Finale) rekursiv die Quellen
// jeder Runde ermitteln, damit die Spalten vertikal ausgerichtet sind.
const sfOrder = orderedRound([104]);
const qfOrder = orderedRound(sfOrder);
const r16Order = orderedRound(qfOrder);
@@ -71,6 +87,10 @@ export default function Bracket({
const fin = later[104];
const third = later[103];
const [tip, setTip] = useState<TipState | null>(null);
const showTip = useCallback((text: string, x: number, y: number) => setTip({ x, y, text }), []);
const hideTip = useCallback(() => setTip(null), []);
return (
<div>
<div className="bracket-banner">
@@ -87,35 +107,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} />)}
{r32.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} 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} />)}
{r16.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} 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} />)}
{qf.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} 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} />)}
{sf.map((t) => <Tie key={t.matchNumber} tie={t} teams={teams} 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 />}
{fin && <Tie tie={fin} teams={teams} isFinal 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} />
<Tie tie={third} teams={teams} onShowTip={showTip} onHideTip={hideTip} />
</div>
)}
</div>
@@ -131,6 +151,12 @@ export default function Bracket({
<span><i style={{ background: "var(--ink-faint)" }} />Platzhalter offen</span>
<span>%-Werte: Polymarket-Wahrscheinlichkeit (falls verfügbar)</span>
</div>
{tip && (
<div className="kobaum-tip" style={{ left: tip.x + 12, top: tip.y + 12 }}>
{tip.text}
</div>
)}
</div>
);
}
}

View File

@@ -190,9 +190,13 @@ table.standings { width: 100%; border-collapse: collapse; }
}
.bracket-banner .v { font-family: var(--font-mono); font-size: 12px; color: var(--turf); }
.bracket-scroll {
max-height: calc(100vh - 260px);
overflow: auto;
padding-bottom: 8px;
margin-left: -20px;
margin-right: -20px;
padding-left: 20px;
padding-right: 20px;
overflow-x: hidden;
overflow-y: visible;
padding-bottom: 10px;
scrollbar-gutter: stable;
}
.bracket {
@@ -236,6 +240,22 @@ table.standings { width: 100%; border-collapse: collapse; }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend i { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
/* Tooltip im K.o.-Baum (position: fixed, damit overflow-x den Tooltip nicht abschneidet) */
.kobaum-tip {
position: fixed;
z-index: 200;
pointer-events: none;
background: var(--bg-card);
border: 1px solid var(--line);
color: var(--ink);
font-family: var(--font-mono);
font-size: 12px;
padding: 6px 9px;
border-radius: var(--radius-sm);
white-space: nowrap;
box-shadow: 0 6px 20px rgba(0,0,0,0.4);
}
/* ---------- Zustände ---------- */
.notice {
background: var(--bg-card); border: 1px solid var(--line);