114 lines
4.0 KiB
TypeScript
114 lines
4.0 KiB
TypeScript
"use client";
|
||
|
||
import { GroupTable, Match, Team, ThirdPlaceRow } from "@/lib/types";
|
||
import { ThirdAssignment } from "@/lib/bracket";
|
||
import { resolveBracket, ResolvedSide, ResolvedTie } from "@/lib/resolve-bracket";
|
||
|
||
function Side({ s, prob }: { s: ResolvedSide; prob?: number | null }) {
|
||
return (
|
||
<div className={`side ${s.isWinner ? "win" : ""}`}>
|
||
<span className="nm">
|
||
{s.teamId ? (
|
||
<>
|
||
<span>{s.label}</span>
|
||
{s.code && <span className="c">{s.code}</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, isFinal }: { tie: ResolvedTie; isFinal?: boolean }) {
|
||
return (
|
||
<div className={`tie ${isFinal ? "final-tie" : ""}`}>
|
||
<span className="tie-num">#{tie.matchNumber}</span>
|
||
<Side s={tie.home} prob={tie.prob?.home} />
|
||
<Side s={tie.away} prob={tie.prob?.away} />
|
||
</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, later } = resolveBracket(matches, teams, tables, thirds, assignment);
|
||
|
||
const pick = (nums: number[]) => nums.map((n) => later[n]).filter(Boolean);
|
||
const r16 = pick([89, 90, 91, 92, 93, 94, 95, 96]);
|
||
const qf = pick([97, 98, 99, 100]);
|
||
const sf = pick([101, 102]);
|
||
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} />)}
|
||
</div>
|
||
</div>
|
||
<div className="round">
|
||
<div className="round-label">Achtelfinale</div>
|
||
<div className="round-matches">
|
||
{r16.map((t) => <Tie key={t.matchNumber} tie={t} />)}
|
||
</div>
|
||
</div>
|
||
<div className="round">
|
||
<div className="round-label">Viertelfinale</div>
|
||
<div className="round-matches">
|
||
{qf.map((t) => <Tie key={t.matchNumber} tie={t} />)}
|
||
</div>
|
||
</div>
|
||
<div className="round">
|
||
<div className="round-label">Halbfinale</div>
|
||
<div className="round-matches">
|
||
{sf.map((t) => <Tie key={t.matchNumber} tie={t} />)}
|
||
</div>
|
||
</div>
|
||
<div className="round">
|
||
<div className="round-label">Finale</div>
|
||
<div className="round-matches">
|
||
{fin && <Tie tie={fin} isFinal />}
|
||
{third && (
|
||
<div style={{ marginTop: 20 }}>
|
||
<div className="round-label" style={{ marginBottom: 8 }}>Spiel um Platz 3</div>
|
||
<Tie tie={third} />
|
||
</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 style={{ background: "var(--ink-faint)" }} />Platzhalter offen</span>
|
||
<span>%-Werte: Polymarket-Wahrscheinlichkeit (falls verfügbar)</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|