'use client'
import { useQuery, gql } from '@/lib/graphql/hooks'
import Link from 'next/link'
import { TeamFlag } from '@/components/team-flag'
import {
ChartBarIcon, StarIcon, TrophyIcon, ClockIcon, BoltIcon,
FireIcon, SparklesIcon, ArrowPathIcon, GlobeEuropeAfricaIcon, TableCellsIcon,
} from '@heroicons/react/24/outline'
const STATS_QUERY = gql`
query Stats {
tournaments { year host totalGoals matchesCount avgGoalsPerGame winner }
topScorers(limit: 20) {
playerName goals penalties ownGoals tournaments
team { name iso2 slug }
}
teams {
id name iso2 slug
stats { appearances titles wins draws losses goalsFor goalsAgainst goalDiff winPct }
}
goalsByMinute { bucket count }
confederationStats { confederation appearances titles totalGoals }
hatTricks {
playerName year round goals
team { name iso2 }
opponent { name iso2 }
}
biggestWins(limit: 10) {
id year round date margin totalGoals scoreFt
team1 { name iso2 } team2 { name iso2 }
}
highestScoringMatches(limit: 10) {
id year round date totalGoals scoreFt
team1 { name iso2 } team2 { name iso2 }
}
extraTimeStats {
totalKnockoutMatches wentToExtraTime wentToPenalties extraTimePct penaltiesPct
}
}
`
function SectionTitle({ children, icon: Icon }: { children: React.ReactNode; icon: React.ComponentType<{ className?: string }> }) {
return (
{children}
)
}
function Card({ children, className = '' }: { children: React.ReactNode; className?: string }) {
return (
{children}
)
}
interface Tournament { year: number; host: string; totalGoals?: number | null; matchesCount?: number | null; avgGoalsPerGame?: string | number | null; winner?: string | null }
interface Scorer { playerName: string; goals: number; penalties: number; ownGoals: number; tournaments: number; team?: { name: string; iso2?: string | null; slug: string } | null }
interface TeamRow { id: number; name: string; iso2?: string | null; slug: string; stats?: { appearances: number; titles: number; wins: number; draws: number; losses: number; goalsFor: number; goalsAgainst: number; winPct: number } | null }
interface MinuteBucket { bucket: string; count: number }
interface ConfStat { confederation: string; appearances: number; titles: number; totalGoals: number }
interface HatTrick { playerName: string; year: number; round: string; goals: number; team?: { name: string; iso2?: string | null } | null; opponent?: { name: string; iso2?: string | null } | null }
interface MatchRow { id: number; year: number; round: string; date?: string | null; margin?: number | null; totalGoals?: number | null; scoreFt?: number[] | null; team1: { name: string; iso2?: string | null }; team2: { name: string; iso2?: string | null } }
interface ETStats { totalKnockoutMatches: number; wentToExtraTime: number; wentToPenalties: number; extraTimePct: number; penaltiesPct: number }
export function StatsClient() {
const { data, loading } = useQuery(STATS_QUERY)
const tournaments: Tournament[] = (data?.tournaments ?? []).filter((t: Tournament) => t.totalGoals != null).sort((a: Tournament, b: Tournament) => a.year - b.year)
const scorers: Scorer[] = data?.topScorers ?? []
const teams: TeamRow[] = (data?.teams ?? []).filter((t: TeamRow) => t.stats && t.stats.appearances > 0).sort((a: TeamRow, b: TeamRow) => (b.stats?.appearances ?? 0) - (a.stats?.appearances ?? 0))
const minuteBuckets: MinuteBucket[] = data?.goalsByMinute ?? []
const confStats: ConfStat[] = data?.confederationStats ?? []
const hatTricks: HatTrick[] = data?.hatTricks ?? []
const biggestWins: MatchRow[] = data?.biggestWins ?? []
const highScoring: MatchRow[] = data?.highestScoringMatches ?? []
const etStats: ETStats | null = data?.extraTimeStats ?? null
const titlesByNation = teams
.filter(t => (t.stats?.titles ?? 0) > 0)
.sort((a, b) => (b.stats?.titles ?? 0) - (a.stats?.titles ?? 0))
.slice(0, 10)
const maxGoals = Math.max(...tournaments.map(t => t.totalGoals ?? 0), 1)
const maxScorer = Math.max(...scorers.map(s => s.goals), 1)
const maxMinute = Math.max(...minuteBuckets.map(b => b.count), 1)
return (
Historical Statistics
{loading && !data && (
Loading statistics…
)}
{/* ── Goals per tournament bar chart ── */}
{tournaments.length > 0 && (
Goals Scored per Tournament
{tournaments.map(t => {
const h = Math.max(4, Math.round(((t.totalGoals ?? 0) / maxGoals) * 140))
const avg = t.avgGoalsPerGame ? Number(t.avgGoalsPerGame).toFixed(1) : null
return (
{t.totalGoals}
)
})}
{tournaments.map(t => (
{t.year}
))}
)}
{/* ── All-time top scorers ── */}
All-Time Top Scorers
{scorers.map((s, i) => (
{i + 1}
{s.team &&
}
{s.playerName}
{s.team?.name} · {s.tournaments} WC{s.tournaments !== 1 ? 's' : ''}{s.penalties > 0 ? ` · ${s.penalties}P` : ''}
{s.goals}
))}
{/* ── World Cup titles ── */}
World Cup Titles by Nation
{titlesByNation.map((t, i) => (
{i + 1}
{t.name}
{Array.from({ length: t.stats?.titles ?? 0 }).map((_, j) => (
))}
{t.stats?.titles}
))}
{/* ── Goals by minute heatmap ── */}
{minuteBuckets.length > 0 && (
Goals by Minute (All-Time)
{minuteBuckets.map(b => {
const h = Math.max(8, Math.round((b.count / maxMinute) * 80))
return (
)
})}
)}
{/* ── Biggest wins ── */}
Biggest Victories
{biggestWins.map(m => (
{m.team1.name} vs {m.team2.name}
{m.year} · {m.round}
{m.scoreFt?.[0]}–{m.scoreFt?.[1]}
+{m.margin}
))}
{/* ── Highest scoring matches ── */}
Highest Scoring Matches
{highScoring.map(m => (
{m.team1.name} vs {m.team2.name}
{m.year} · {m.round}
{m.scoreFt?.[0]}–{m.scoreFt?.[1]}
{m.totalGoals} goals
))}
{/* ── Hat-tricks ── */}
{hatTricks.length > 0 && (
Hat-Tricks
{hatTricks.map((h, i) => (
{h.team &&
}
{h.playerName}
{h.team?.name}
{h.goals}
{h.year} · {h.round}
{h.opponent && vs {h.opponent.name}}
))}
)}
{/* ── ET & Penalty stats ── */}
{etStats && (
Extra Time & Penalty Shootouts
{[
{ label: 'Knockout Matches', value: etStats.totalKnockoutMatches },
{ label: 'Went to AET', value: `${etStats.wentToExtraTime} (${etStats.extraTimePct}%)` },
{ label: 'Decided by PSO', value: `${etStats.wentToPenalties} (${etStats.penaltiesPct}%)` },
{ label: 'Decided in 90min', value: etStats.totalKnockoutMatches - etStats.wentToExtraTime },
].map(s => (
))}
)}
{/* ── Confederation stats ── */}
{confStats.length > 0 && (
Performance by Confederation
| Confederation |
Appearances |
Titles |
Goals |
{confStats.map(c => (
| {c.confederation} |
{c.appearances} |
{c.titles} |
{c.totalGoals} |
))}
)}
{/* ── All-time team table ── */}
{teams.length > 0 && (
All-Time Team Table
{['#', 'Team', 'WC', 'W', 'D', 'L', 'GF', 'GA', 'GD', 'Win%'].map((h, i) => (
| {h} |
))}
{teams.slice(0, 40).map((t, i) => (
| {i + 1} |
{t.name}
|
{t.stats?.appearances} |
{t.stats?.wins} |
{t.stats?.draws} |
{t.stats?.losses} |
{t.stats?.goalsFor} |
{t.stats?.goalsAgainst} |
{(t.stats?.goalsFor ?? 0) - (t.stats?.goalsAgainst ?? 0) >= 0
? `+${(t.stats?.goalsFor ?? 0) - (t.stats?.goalsAgainst ?? 0)}`
: (t.stats?.goalsFor ?? 0) - (t.stats?.goalsAgainst ?? 0)}
|
{t.stats?.winPct}% |
))}
)}
)
}