'use client' import { useQuery, gql } from '@/lib/graphql/hooks' import { use, useEffect } from 'react' import Link from 'next/link' import { TeamFlag } from '@/components/team-flag' import { MatchCard } from '@/components/match-card' import { LiveBadge } from '@/components/live-badge' const TOURNAMENT_QUERY = gql` query Tournament($year: Int!) { tournament(year: $year) { year host winner runnerUp thirdPlace fourthPlace totalGoals matchesCount teamsCount avgGoalsPerGame topScorers(limit: 10) { playerName goals penalties ownGoals team { name iso2 slug } } matches { id year round group date time isLive isQualiPlayoff scoreFt scoreHt scoreEt scoreP team1 { id name iso2 slug } team2 { id name iso2 slug } goals { playerName minute minuteOffset isPenalty isOwnGoal team { id } } } } groupStandings(year: $year) { groupName pos played won drawn lost goalsFor goalsAgainst goalDiff pts team { id name iso2 slug } } } ` interface MatchData { id: number; year: number; round: string; group?: string | null date?: string | null; time?: string | null; isLive: boolean; isQualiPlayoff: boolean scoreFt?: number[] | null; scoreHt?: number[] | null; scoreEt?: number[] | null; scoreP?: number[] | null team1: { id: number; name: string; iso2?: string | null; slug: string } team2: { id: number; name: string; iso2?: string | null; slug: string } goals: Array<{ playerName: string; minute?: number | null; minuteOffset?: number | null; isPenalty: boolean; isOwnGoal: boolean; team: { id: number } }> } interface Standing { groupName: string; pos?: number | null played: number; won: number; drawn: number; lost: number goalsFor: number; goalsAgainst: number; goalDiff: number; pts: number team: { id: number; name: string; iso2?: string | null; slug: string } } function GoalList({ match }: { match: MatchData }) { if (!match.goals?.length) return null const t1Goals = match.goals.filter(g => !g.isOwnGoal ? g.team.id === match.team1.id : g.team.id !== match.team1.id) const t2Goals = match.goals.filter(g => !g.isOwnGoal ? g.team.id === match.team2.id : g.team.id !== match.team2.id) const renderGoal = (g: MatchData['goals'][0], i: number) => ( {i > 0 && ,} {g.playerName} {' '}{g.minute ?? ''}{g.minuteOffset ? `+${g.minuteOffset}` : ''}'{g.isPenalty ? ' (P)' : g.isOwnGoal ? ' (OG)' : ''} ) return (
{t1Goals.map(renderGoal)}
{t2Goals.map(renderGoal)}
) } export function TournamentClient({ params }: { params: Promise<{ year: string }> }) { const { year: yearStr } = use(params) const year = parseInt(yearStr) const { data, loading } = useQuery(TOURNAMENT_QUERY, { variables: { year }, pollInterval: year === 2026 ? 60_000 : 0 }) useEffect(() => { if (!data) return const hash = window.location.hash if (!hash) return const el = document.getElementById(hash.slice(1)) if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }) }, [data]) const t = data?.tournament const standings: Standing[] = data?.groupStandings ?? [] const byGroup = standings.reduce>((acc, s) => { acc[s.groupName] = [...(acc[s.groupName] ?? []), s] return acc }, {}) const allMatches: MatchData[] = t?.matches ?? [] const byRound = allMatches.reduce>((acc, m) => { const key = m.group ?? m.round acc[key] = [...(acc[key] ?? []), m] return acc }, {}) // Union of groups from standings + groups from match data (handles groups with no played matches yet) const groupNames = new Set([ ...Object.keys(byGroup), ...allMatches.filter(m => m.group).map(m => m.group!), ]) const groupRounds = [...groupNames].sort().map(g => [g, byGroup[g] ?? []] as [string, Standing[]]) const koRounds = allMatches.filter(m => !m.group && !m.isQualiPlayoff) const koByRound = koRounds.reduce>((acc, m) => { acc[m.round] = [...(acc[m.round] ?? []), m] return acc }, {}) const liveMatches = allMatches.filter(m => m.isLive) const maxScorer = Math.max(...(t?.topScorers?.map((s: { goals: number }) => s.goals) ?? [1]), 1) if (loading && !data) { return (
Loading {year} World Cup…
) } if (!t) return
Tournament {year} not found.
return (
{/* Header */}
{liveMatches.length > 0 &&
}

{year}

{t.host}

{t.winner && (
{t.winner}
{t.runnerUp &&
def. {t.runnerUp}
}
)}
{[ { label: 'Teams', value: t.teamsCount }, { label: 'Matches', value: t.matchesCount }, { label: 'Goals', value: t.totalGoals }, { label: 'Goals/Game', value: t.avgGoalsPerGame ? Number(t.avgGoalsPerGame).toFixed(2) : null }, ].filter(s => s.value != null).map(s => (
{s.label}
{s.value}
))}
{/* Live matches first */} {liveMatches.length > 0 && (

LIVE

{liveMatches.map(m => (
))}
)} {/* Group stage */} {groupRounds.length > 0 && (

Group Stage

{groupRounds.map(([groupName, rows]) => { const sorted = [...rows].sort((a, b) => b.pts - a.pts || b.goalDiff - a.goalDiff) const groupMatches = (byRound[groupName] ?? []).sort((a, b) => { if (!a.date) return 1; if (!b.date) return -1 const cmp = a.date.localeCompare(b.date) if (cmp !== 0) return cmp return (a.time ?? '').localeCompare(b.time ?? '') }) return (

{groupName}

{/* Standings mini */}
{sorted.map((s, i) => (
{s.team.name} {s.played} {s.won} {s.drawn} {s.lost} {s.pts}
))}
{/* Group matches */}
{groupMatches.map(m => (
))}
) })}
)} {/* Knockout rounds */} {Object.keys(koByRound).length > 0 && (

Knockout Stage

{Object.entries(koByRound).map(([round, roundMatches]) => (

{round}

{roundMatches.map(m => (
))}
))}
)}
{/* Sidebar: top scorers */}

TOP SCORERS

{t.topScorers?.map((s: { playerName: string; goals: number; penalties: number; team?: { name: string; iso2?: string | null; slug: string } | null }, i: number) => (
{i + 1} {s.team && }
{s.playerName}
{s.penalties > 0 &&
{s.penalties} pen
}
{s.goals}
))}
{t.thirdPlace && (
3rd Place
{t.thirdPlace}
{t.fourthPlace && (
{t.fourthPlace}
)}
)}
) }