third place marks

This commit is contained in:
2026-06-27 12:15:34 -05:00
parent bf931e70c0
commit a721de5b23
4 changed files with 115 additions and 187 deletions

View File

@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
import { fetchMatchesAndTeams, fetchOdds, attachOdds, fetchLiveScores, applyLiveScores, assignKONumbersBySlots } from "@/lib/feeds";
import { computeGroupTables, computeThirdPlaceTable } from "@/lib/standings";
import { qualifiedThirdGroups, resolveAnnexC } from "@/lib/bracket";
import { securelyQualifiedThirdTeams } from "@/lib/third-place-security";
// Diese Route wird vom Frontend gepollt. Sie ist der einzige Ort, der die
// Upstream-Feeds berührt — gecacht, damit Rate Limits eingehalten werden.
@@ -41,6 +42,9 @@ export async function GET() {
const qGroups = qualifiedThirdGroups(thirdTable);
const annex = qGroups.length === 8 ? resolveAnnexC(qGroups) : null;
// Sichere Drittplatzierte (Team-IDs, deren Top-8-Platz mathematisch feststeht)
const secureThirdTeams = securelyQualifiedThirdTeams(matches, teams);
return NextResponse.json({
updatedAt: new Date().toISOString(),
teams,
@@ -48,6 +52,7 @@ export async function GET() {
groupTables,
groupTablesLive,
thirdTable,
secureThirdTeams: [...secureThirdTeams],
annexAssignment: annex,
annexResolved: annex != null,
});

View File

@@ -3,12 +3,13 @@
import { Team, ThirdPlaceRow } from "@/lib/types";
export default function ThirdPlace({
rows, teams,
}: { rows: ThirdPlaceRow[]; teams: Team[] }) {
rows, teams, secureTeams,
}: { rows: ThirdPlaceRow[]; teams: Team[]; secureTeams?: string[] }) {
const name = (id: string) => {
const t = teams.find((t) => t.id === id);
return t?.localisedName ?? t?.name ?? id;
};
const secureSet = secureTeams ? new Set(secureTeams) : null;
return (
<div className="third-wrap">
@@ -35,7 +36,12 @@ export default function ThirdPlace({
>
<td>{r.overallRank}</td>
<td>{r.group}</td>
<td className={r.played === 3 ? "team-complete" : ""} style={{ fontWeight: 600 }}>{name(r.teamId)}</td>
<td className={r.played === 3 ? "team-complete" : ""} style={{ fontWeight: 600 }}>
{name(r.teamId)}
{secureSet?.has(r.teamId) && (
<span style={{ color: "var(--turf)", marginLeft: 6, fontSize: 13 }} title="sicher qualifiziert"></span>
)}
</td>
<td>{r.played}</td>
<td className="pts">{r.points}</td>
<td>{r.goalDiff > 0 ? `+${r.goalDiff}` : r.goalDiff}</td>

View File

@@ -16,6 +16,7 @@ interface ApiData {
groupTables: GroupTable[];
groupTablesLive: GroupTable[];
thirdTable: ThirdPlaceRow[];
secureThirdTeams: string[];
annexAssignment: ThirdAssignment | null;
annexResolved: boolean;
}
@@ -128,7 +129,7 @@ export default function Home() {
/>
)}
{data && tab === "thirds" && (
<ThirdPlace rows={data.thirdTable} teams={data.teams} />
<ThirdPlace rows={data.thirdTable} teams={data.teams} secureTeams={data.secureThirdTeams} />
)}
{data && tab === "bracket" && (
<Bracket