"use client"; import { useMemo, useState, useEffect, useRef } from "react"; import { Match, Team, teamName } from "@/lib/types"; import { STADIUMS, MATCH_STADIUMS } from "@/lib/stadiums"; import { Dictionary } from "@/lib/i18n"; import Flag from "./Flag"; function fmtShortDate(iso: string, locale: string): string { const d = new Date(iso); return d.toLocaleDateString(locale === "en" ? "en-US" : "de-DE", { weekday: "short", day: "2-digit", month: "2-digit" }); } function fmtTime(iso: string, locale: string): string { const d = new Date(iso); return d.toLocaleTimeString(locale === "en" ? "en-US" : "de-DE", { hour: "2-digit", minute: "2-digit" }); } function scoreDisplay(m: Match, dict: Dictionary): string { if (m.status === "SCHEDULED" || m.status === "POSTPONED" || (m.homeScore == null && m.awayScore == null)) return "– : –"; const base = `${m.homeScore ?? 0} : ${m.awayScore ?? 0}`; if (m.homePenalty != null && m.awayPenalty != null) { return `${base} (${m.homePenalty}:${m.awayPenalty} ${dict.bracket.penaltyShootout})`; } return base; } function isLive(m: Match): boolean { return m.status === "LIVE" || m.status === "IN_PLAY" || m.status === "PAUSED"; } export default function KoFixtures({ teams, matches, dict, locale }: { teams: Team[]; matches: Match[]; dict: Dictionary; locale: string }) { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); function statusLabel(m: Match): string { switch (m.status) { case "LIVE": case "IN_PLAY": return m.minute != null ? `${m.minute}'` : dict.status.live; case "PAUSED": return dict.status.halftime; case "POSTPONED": return dict.status.postponed; case "FINISHED": return dict.status.finished; default: return fmtTime(m.utcDate, locale); } } const koMatches = useMemo(() => { return matches .filter((m) => m.group == null && m.homeTeamId != null && m.awayTeamId != null && // Nur Spiele mit bekannten Teams (keine TBD) teams.some(t => t.id === m.homeTeamId) && teams.some(t => t.id === m.awayTeamId), ) .sort((a, b) => +new Date(a.utcDate) - +new Date(b.utcDate)); }, [matches, teams]); // Lokale Datums-Extraktion (vermeidet UTC-Tagesverschiebung bei US-Spielen) function localDateKey(iso: string): string { const d = new Date(iso); const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } // Nach Datum gruppieren (lokale Zeitzone nach Hydration, sonst UTC) const groups = useMemo(() => { const map = new Map(); for (const m of koMatches) { const key = mounted ? localDateKey(m.utcDate) : m.utcDate.slice(0, 10); if (!map.has(key)) map.set(key, []); map.get(key)!.push(m); } return [...map.entries()]; }, [koMatches, mounted]); const targetMatchId = useMemo(() => { const live = koMatches.find(m => isLive(m)); if (live) return live.id; const upcoming = koMatches.find(m => m.status !== "FINISHED"); return upcoming?.id ?? null; }, [koMatches]); const targetRef = useRef(null); const hasScrolledRef = useRef(null); useEffect(() => { if (!mounted || !targetRef.current) return; if (targetMatchId === hasScrolledRef.current) return; const el = targetRef.current; const raf = requestAnimationFrame(() => { const rect = el.getBoundingClientRect(); const absoluteTop = rect.top + window.scrollY; const offset = 180; window.scrollTo({ top: Math.max(0, absoluteTop - offset), behavior: "smooth" }); }); hasScrolledRef.current = targetMatchId; return () => cancelAnimationFrame(raf); }, [mounted, targetMatchId, koMatches.length]); const [showScrollTop, setShowScrollTop] = useState(false); useEffect(() => { const onScroll = () => setShowScrollTop(window.scrollY > 100); window.addEventListener("scroll", onScroll, { passive: true }); onScroll(); return () => window.removeEventListener("scroll", onScroll); }, []); if (koMatches.length === 0) { return
{dict.koFixtures.noMatches}
; } return ( <>
{groups.map(([date, groupMatches]) => (

{fmtShortDate(groupMatches[0].utcDate, locale)}

{groupMatches.map((m) => { const home = m.homeTeamId ? teams.find(t => t.id === m.homeTeamId) : undefined; const away = m.awayTeamId ? teams.find(t => t.id === m.awayTeamId) : undefined; const live = isLive(m); const stageMap: Record = { R32: "R32", R16: dict.stage.r16, QF: dict.stage.qf, SF: dict.stage.sf, "3RD": dict.stage.thirdPlace, FINAL: dict.stage.final, }; const city = STADIUMS[MATCH_STADIUMS[m.matchNumber]]?.city; const isTarget = String(m.id) === String(targetMatchId); const finished = m.status === "FINISHED"; return (
{/* Kopfzeile */}
{isTarget && ( {live ? dict.koFixtures.liveBadge : dict.koFixtures.nextMatch} )} {m.status === "POSTPONED" && ( {dict.koFixtures.postponedBadge} )} {stageMap[m.stage] ?? m.stage} · {m.matchNumber ? dict.koFixtures.match(m.matchNumber) : dict.koFixtures.match(0).replace("0", "—")} {city ? ` · ${city}` : ""} {statusLabel(m)}
{/* Teams + Score */}
{scoreDisplay(m, dict)}
{/* Torabfolge */} {m.goals && m.goals.length > 0 && (
{dict.label.goals}
{m.goals.map((g, i) => ( {g.scorer} {g.minute}' ))}
)} {/* Prob (falls vorhanden) */} {m.prob && m.prob.home > 0 && (
Polymarket: {Math.round(m.prob.home * 100)}% / {Math.round(m.prob.draw * 100)}% / {Math.round(m.prob.away * 100)}%
)}
); })}
))}
{showScrollTop && ( )} ); } function TeamLabel({ team, reverse, locale }: { team?: Team; reverse?: boolean; locale: string }) { return (
{teamName(team, locale)}
); }