Files
wm-projekt/app/page.tsx
2026-06-22 15:32:32 -05:00

123 lines
4.1 KiB
TypeScript

"use client";
import { useEffect, useState, useCallback } from "react";
import { GroupTable, Match, Team, ThirdPlaceRow } from "@/lib/types";
import { ThirdAssignment } from "@/lib/bracket";
import Groups from "./components/Groups";
import ThirdPlace from "./components/ThirdPlace";
import Bracket from "./components/Bracket";
interface ApiData {
updatedAt: string;
teams: Team[];
matches: Match[];
groupTables: GroupTable[];
thirdTable: ThirdPlaceRow[];
annexAssignment: ThirdAssignment | null;
annexResolved: boolean;
}
type Tab = "groups" | "thirds" | "bracket";
export default function Home() {
const [data, setData] = useState<ApiData | null>(null);
const [error, setError] = useState<string | null>(null);
const [tab, setTab] = useState<Tab>("groups");
const load = useCallback(async () => {
try {
const res = await fetch("/api/matches", { cache: "no-store" });
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.detail || `Fehler ${res.status}`);
}
setData(await res.json());
setError(null);
} catch (e) {
setError(e instanceof Error ? e.message : "Verbindung fehlgeschlagen");
}
}, []);
useEffect(() => {
load();
const id = setInterval(load, 30_000); // alle 30 s aktualisieren
return () => clearInterval(id);
}, [load]);
const anyLive = data?.matches.some(
(m) => m.status === "LIVE" || m.status === "IN_PLAY" || m.status === "PAUSED",
);
return (
<>
<header className="masthead">
<div className="wrap masthead-inner">
<div className="brand">
<span className="brand-mark">WM <span className="accent">26</span></span>
<span className="brand-sub">USA · Kanada · Mexiko</span>
</div>
<span className="status-pill">
<span className={`dot ${anyLive ? "live" : data ? "on" : ""}`} />
{error
? "Feed offline"
: data
? anyLive ? "Live" : `Aktualisiert ${new Date(data.updatedAt).toLocaleTimeString("de-DE")}`
: "Lade Daten…"}
</span>
</div>
</header>
<main className="wrap">
<nav className="tabs">
<button className={`tab ${tab === "groups" ? "active" : ""}`} onClick={() => setTab("groups")}>
Gruppen
</button>
<button className={`tab ${tab === "thirds" ? "active" : ""}`} onClick={() => setTab("thirds")}>
Drittplatzierte
</button>
<button className={`tab ${tab === "bracket" ? "active" : ""}`} onClick={() => setTab("bracket")}>
K.o.-Baum
</button>
</nav>
<section className="section">
{error && (
<div className="notice err">
Die Live-Feeds sind gerade nicht erreichbar: {error}.
Prüfe den <code>FOOTBALL_DATA_TOKEN</code> und die Netzwerkfreigabe des Servers.
Die Seite versucht es automatisch erneut.
</div>
)}
{!data && !error && (
<div className="group-grid">
{Array.from({ length: 6 }).map((_, i) => <div className="skel" key={i} />)}
</div>
)}
{data && tab === "groups" && (
<Groups tables={data.groupTables} teams={data.teams} matches={data.matches} />
)}
{data && tab === "thirds" && (
<ThirdPlace rows={data.thirdTable} teams={data.teams} />
)}
{data && tab === "bracket" && (
<Bracket
matches={data.matches} teams={data.teams} tables={data.groupTables}
thirds={data.thirdTable} assignment={data.annexAssignment}
annexResolved={data.annexResolved}
/>
)}
</section>
</main>
<footer className="foot">
<div className="wrap">
Daten: football-data.org (Spiele &amp; Tabellen) · Polymarket Gamma API (Wahrscheinlichkeiten) ·
Annex-C-Zuordnung nach den FIFA-Wettbewerbsregeln WM 2026. Kein offizielles FIFA-Produkt.
</div>
</footer>
</>
);
}