Files
wm-projekt/app/components/Bracket.tsx
2026-06-23 16:26:01 -05:00

133 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
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";
function Side({
s, prob, teams,
}: { s: ResolvedSide; prob?: number | null; teams: Team[] }) {
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;
return (
<div className={`side ${s.isWinner ? "win" : ""} ${s.provisional ? "prov" : ""} ${fix ? "fix" : ""}`}>
<span className="nm">
{s.teamId ? (
<>
<Flag team={team} size={18} />
<span className="tn">{s.label}</span>
{s.provisional && <span className="prov-mark" title="vorläufig Gruppe/Zuordnung noch nicht fix"></span>}
</>
) : (
<span className="lbl">{s.label}</span>
)}
{prob != null && prob > 0 && (
<span className="prob">{Math.round(prob * 100)}%</span>
)}
</span>
<span className="sc">{s.score != null ? s.score : ""}</span>
</div>
);
}
function Tie({ tie, teams, isFinal }: { tie: ResolvedTie; teams: Team[]; isFinal?: boolean }) {
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} />
</div>
);
}
export default function Bracket({
matches, teams, tables, thirds, assignment, annexResolved,
}: {
matches: Match[]; teams: Team[]; tables: GroupTable[];
thirds: ThirdPlaceRow[]; assignment: ThirdAssignment | null; annexResolved: boolean;
}) {
const { r32: r32Array, later } = resolveBracket(
matches, teams, tables, thirds, assignment, annexResolved,
);
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);
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];
return (
<div>
<div className="bracket-banner">
<span className="k">Annex-C-Zuordnung der Drittplatzierten:</span>
<span className="v">
{annexResolved
? "aufgelöst — die acht Dritten sind den Gruppensiegern fest zugeteilt"
: "noch offen — sobald die 8 besten Dritten feststehen, verbindet sich der Baum automatisch"}
</span>
</div>
<div className="bracket-scroll">
<div className="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} />)}
</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} />)}
</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} />)}
</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} />)}
</div>
</div>
<div className="round">
<div className="round-label">Finale</div>
<div className="round-matches">
{fin && <Tie tie={fin} teams={teams} isFinal />}
{third && (
<div style={{ marginTop: 20 }}>
<div className="round-label" style={{ marginBottom: 8 }}>Spiel um Platz 3</div>
<Tie tie={third} teams={teams} />
</div>
)}
</div>
</div>
</div>
</div>
<div className="legend">
<span><i style={{ background: "var(--turf)" }} />Sieger / weiter</span>
<span><i style={{ background: "var(--gold)" }} />Finale</span>
<span><i className="leg-fix" />fix qualifiziert</span>
<span><i className="leg-prov" />vorläufig (, nach aktueller Tabelle)</span>
<span><i style={{ background: "var(--ink-faint)" }} />Platzhalter offen</span>
<span>%-Werte: Polymarket-Wahrscheinlichkeit (falls verfügbar)</span>
</div>
</div>
);
}