Release 0.6.1. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01MB7nCCAscYsb3zzkT6LHZi
35 lines
2.6 KiB
JavaScript
35 lines
2.6 KiB
JavaScript
// The one JSON document every public page renders from. No phone numbers, no team codes.
|
|
export const DEFAULT_TZ = process.env.DEFAULT_TIMEZONE || 'America/Chicago';
|
|
export function validTz(tz) { try { new Intl.DateTimeFormat('en-US', { timeZone: tz }); return tz; } catch { return null; } }
|
|
export const tzOf = t => validTz(t.timezone) ?? DEFAULT_TZ;
|
|
export const fmtTime = (ms, tz) => new Date(ms).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', timeZone: tz });
|
|
export function publicState({ t, engine }) {
|
|
const name = id => t.teamName(id);
|
|
const view = m => ({
|
|
id: m.id, stage: m.stage, round: m.round, slot: m.slot, label: m.label ?? null, status: m.status, court: m.court,
|
|
a: m.teamA ? { id: m.teamA, name: name(m.teamA) } : null,
|
|
b: m.teamB ? { id: m.teamB, name: name(m.teamB) } : null,
|
|
winner: m.winner, sets: m.sets, forfeitBy: m.status === 'forfeit' ? m.loser : (m.autoForfeit ?? null), live: m.live ?? null, liveSets: m.liveSets ?? null, startedAt: m.startedAt ?? null, feeds: m.feeds, feedsSide: m.feedsSide, loserFeeds: m.loserFeeds ?? null, loserFeedsSide: m.loserFeedsSide ?? null, conditional: !!m.conditional,
|
|
});
|
|
const board = t.phase === 'live' ? engine.board() : null;
|
|
const pools = t.pools.map(p => ({
|
|
id: p.id,
|
|
standings: t.standings(p.id).map(r => ({ teamId: r.teamId, name: r.name, w: r.w, l: r.l, setsW: r.setsW, setsL: r.setsL, pf: r.pf, pa: r.pa, pointDiff: r.pointDiff, decidedBy: r.decidedBy, status: r.status })),
|
|
matches: t.matches.filter(m => m.poolId === p.id).map(view),
|
|
}));
|
|
const bracket = t.matches.filter(m => m.stage !== 'pool').map(view);
|
|
const teams = t.activeTeams().map(x => t.publicTeam(x.id));
|
|
const withdrawn = [...t.teams.values()].filter(x => x.status === 'withdrawn').map(x => t.publicTeam(x.id));
|
|
return {
|
|
slug: t.slug, name: t.name, date: t.date, notes: t.notes ?? '', timezone: tzOf(t), phase: t.phase, banner: t.banner ?? null,
|
|
rules: t.rules, stages: t.stages, courtCount: t.courtCount,
|
|
teams, withdrawn, pools, bracket,
|
|
courts: board ? board.courts.map(c => ({ ...c, match: c.match ? view(t.match(c.match.id)) : null })) : [],
|
|
upNext: board ? board.upNext.map(u => ({ ...view(t.match(u.id)), court: u.court, etaMin: u.etaMin, tentative: u.tentative, aFrom: u.aFrom, bFrom: u.bFrom })) : [],
|
|
avgMatchMin: board?.avgMatchMin ?? null,
|
|
champion: t.phase === 'final' ? t.champion() : null,
|
|
recentAlerts: engine.alerts.slice(-20).map(a => ({ kind: a.kind, text: a.text, at: a.at, teams: a.teams ?? [], match: a.match ?? null })),
|
|
generatedAt: Date.now(),
|
|
};
|
|
}
|