/* ============================================================ Season — scorecard stats + handicaps for every member. Visible to ALL members. Sortable columns (click a header). Aggregates every POSTED weekend's stats (stored on the season doc) plus the live current weekend. Managers also get the "post this weekend" control. ============================================================ */ function SeasonScreen({ ctx }) { const Y = window.YCC; const { seasonDoc, accountMembers, tour, scores, sideResults, gameConfig, postWeekendResults, clearWeekendResults, isManagerUser, member } = ctx; const ENG = window.YCC_SCORING; const fullName = (m) => `${m.first_name || ""} ${m.last_name || ""}`.trim() || m.email; const initials = (n) => (n || "?").trim().split(/\s+/).map((s) => s[0]).join("").slice(0, 2).toUpperCase(); const [sort, setSort] = useState({ key: "wins", dir: "desc" }); // ---- aggregate per-member stats across the season ---- const postedStats = (seasonDoc && seasonDoc.stats) || {}; const live = ENG.weekendStats ? ENG.weekendStats({ tour, scores, sideResults, gameConfig }) : {}; const NUM_KEYS = ["eagles", "birdies", "pars", "bogeys", "doubles", "bogeyPlus", "holes", "strokes", "toPar", "rounds", "ctp", "longDrive", "wins"]; const agg = {}; const blank = () => { const o = { weekendsWon: 0 }; NUM_KEYS.forEach((k) => (o[k] = 0)); return o; }; const addWeekend = (byName) => { Object.entries(byName || {}).forEach(([name, s]) => { const a = agg[name] || (agg[name] = blank()); NUM_KEYS.forEach((k) => { a[k] += (s[k] || 0); }); if ((s.wins || 0) > 0) a.weekendsWon += 1; }); }; Object.entries(postedStats).forEach(([wk, byName]) => addWeekend(byName)); // include the live current weekend only if it isn't already posted (avoids double count) const liveIncluded = !postedStats[tour.satISO]; if (liveIncluded) addWeekend(live); // handicap lookup (current index) by member name const hiByName = {}; (accountMembers || []).forEach((m) => { const n = fullName(m); if (m.handicap_index != null) hiByName[n] = m.handicap_index; }); if (member && member.name && member.handicapIndex != null && hiByName[member.name] == null) hiByName[member.name] = member.handicapIndex; // universe of names: every account member + anyone with stats const names = new Set(); (accountMembers || []).forEach((m) => names.add(fullName(m))); Object.keys(agg).forEach((n) => names.add(n)); if (member && member.name) names.add(member.name); const rows = Array.from(names).map((name) => { const a = agg[name] || blank(); const avg = a.holes ? Math.round((a.strokes / a.holes) * 18) : null; return { name, hi: hiByName[name] != null ? hiByName[name] : null, rounds: a.rounds, eagles: a.eagles, birdies: a.birdies, pars: a.pars, bogeys: a.bogeys, doubles: a.doubles, bogeyPlus: a.bogeyPlus, avg, ctp: a.ctp, longDrive: a.longDrive, wins: a.wins, weekendsWon: a.weekendsWon, played: a.holes > 0, }; }); // ---- sorting ---- const COLS = [ { key: "name", label: "Member", text: true }, { key: "hi", label: "HI", hint: "Handicap Index" }, { key: "rounds", label: "Rds", hint: "Rounds played" }, { key: "eagles", label: "Eagles" }, { key: "birdies", label: "Birdies" }, { key: "pars", label: "Pars" }, { key: "bogeys", label: "Bogeys" }, { key: "doubles", label: "Dbl", hint: "Double bogeys" }, { key: "bogeyPlus", label: "Bgy+", hint: "Triple bogey or worse" }, { key: "avg", label: "Avg", hint: "Scoring average per 18 holes", lowGood: true }, { key: "ctp", label: "CTP", hint: "Closest to the pin" }, { key: "longDrive", label: "Long Dr", hint: "Longest drive" }, { key: "wins", label: "Wins", hint: "Game wins (most wins by day)" }, { key: "weekendsWon", label: "Wknds", hint: "Weekends won" }, ]; function clickSort(key) { const col = COLS.find((c) => c.key === key); setSort((s) => s.key === key ? { key, dir: s.dir === "asc" ? "desc" : "asc" } : { key, dir: col && col.text ? "asc" : "desc" }); } const sorted = [...rows].sort((a, b) => { const { key, dir } = sort; if (key === "name") return dir === "asc" ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name); let av = a[key], bv = b[key]; av = (typeof av === "number" && isFinite(av)) ? av : null; bv = (typeof bv === "number" && isFinite(bv)) ? bv : null; if (av == null && bv == null) return a.name.localeCompare(b.name); if (av == null) return 1; if (bv == null) return -1; if (av === bv) return a.name.localeCompare(b.name); return dir === "asc" ? av - bv : bv - av; }); // ---- summary tiles ---- const seasonWeeks = Object.keys(postedStats).length; const totalBirdiesEagles = rows.reduce((n, r) => n + r.eagles + r.birdies, 0); const totalRounds = rows.reduce((n, r) => n + r.rounds, 0); const winLeader = [...rows].filter((r) => r.wins > 0).sort((a, b) => b.wins - a.wins)[0]; const numCellStyle = (highlight) => ({ padding: "10px 12px", textAlign: "center", fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap", color: highlight ? "var(--brand)" : "var(--ink)" }); return (
Every member's scorecard stats and handicap across the season. {liveIncluded ? "Includes this weekend's live scores. " : ""}Tap any column heading to sort.
| clickSort(c.key)} title={c.hint || c.label} style={{ padding: "11px 12px", fontSize: 11, fontWeight: 700, letterSpacing: ".04em", textTransform: "uppercase", color: active ? "var(--brand)" : "var(--ink-soft)", whiteSpace: "nowrap", cursor: "pointer", userSelect: "none", textAlign: stub ? "left" : "center", position: stub ? "sticky" : "static", left: stub ? 0 : "auto", background: "var(--surface-2)", zIndex: stub ? 2 : 1, borderBottom: active ? "2px solid var(--accent)" : "2px solid transparent", }}> {c.label} {active ? (sort.dir === "asc" ? "▲" : "▼") : "↕"} | ); })}|||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
{r.hi == null ? — : r.hi} | {r.rounds || —} | 0)}>{r.eagles || —} | 0)}>{r.birdies || —} | {r.pars || —} | {r.bogeys || —} | {r.doubles || —} | {r.bogeyPlus || —} | {r.avg == null ? — : r.avg} | 0)}>{r.ctp ? {r.ctp} : —} | 0)}>{r.longDrive ? {r.longDrive} : —} | 0 ? "var(--accent-deep)" : "var(--brand)" }}>{r.wins || —} | {r.weekendsWon || —} |
Once a group finishes a skins / Stableford / Nassau / Split-Sixes game and scores are in, the winners show here. Posting writes this weekend's stats (eagles, birdies, CTP, wins…) into the Season tab for every member.
) : (