// groups.js — standings computed from groups.json + results.json and the 12 // group tables. Only matches with status "finished" count toward standings // (live scores are ignored until full-time). computeStandings() and // isGroupFinished() are reused by bracket.js to resolve the Round of 32. import { getData, flagSrc } from './app.js'; import { t } from './i18n.js'; import { getFavorites } from './storage.js'; // Tiebreak order per complement spec §2: points, goal difference, goals for. // Team id alphabetical as a final stable fallback. export function computeStandings() { const { groups, matches, resultByMatchId } = getData(); const tables = {}; for (const [letter, teamIds] of Object.entries(groups)) { tables[letter] = new Map(teamIds.map((id) => [id, { teamId: id, played: 0, won: 0, drawn: 0, lost: 0, gf: 0, ga: 0, gd: 0, points: 0, }])); } for (const match of matches) { if (!match.phase.startsWith('Group ')) continue; const result = resultByMatchId.get(match.id); if (result?.status !== 'finished') continue; const rows = tables[match.phase.slice(6)]; applyResult(rows.get(match.homeTeam), result.homeScore, result.awayScore); applyResult(rows.get(match.awayTeam), result.awayScore, result.homeScore); } const standings = {}; for (const [letter, rows] of Object.entries(tables)) { standings[letter] = [...rows.values()].sort((a, b) => b.points - a.points || b.gd - a.gd || b.gf - a.gf || a.teamId.localeCompare(b.teamId)); } return standings; } function applyResult(row, scored, conceded) { row.played += 1; row.gf += scored; row.ga += conceded; row.gd = row.gf - row.ga; if (scored > conceded) { row.won += 1; row.points += 3; } else if (scored === conceded) { row.drawn += 1; row.points += 1; } else { row.lost += 1; } } export function isGroupFinished(letter) { const { matches, resultByMatchId } = getData(); return matches .filter((m) => m.phase === `Group ${letter}`) .every((m) => resultByMatchId.get(m.id)?.status === 'finished'); } // -------------------------------------------------------------- render export function initGroups() { render(); document.addEventListener('langchange', render); document.addEventListener('favchange', render); document.addEventListener('datachange', render); // new results → recompute standings } function render() { const standings = computeStandings(); document.getElementById('groups-root').innerHTML = `
${t('standings.legendTop2')} ${t('standings.legendThird')}
${pairs}
`; } function groupCardHTML(letter, rows) { const finished = isGroupFinished(letter); const headers = ['played', 'won', 'drawn', 'lost', 'gf', 'ga', 'gd', 'pts'] .map((key) => { const tip = t(`tip.${key}`); const goals = key === 'gf' || key === 'ga' ? 'col-goals ' : ''; return `| # | ${t('standings.team')} | ${headers}
|---|