From fa275899456395445b0219583b3e07ff3d109f61 Mon Sep 17 00:00:00 2001 From: mandalore Date: Fri, 26 Jun 2026 20:24:24 -0500 Subject: [PATCH] replace W-L-T with win percentage, add Win % column, and add sortable headers Co-Authored-By: Claude Sonnet 4.6 --- index.html | 11 +++++----- src/main.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index 785d2cb..ff3bbcb 100644 --- a/index.html +++ b/index.html @@ -38,7 +38,7 @@
-
Combined W–L–T
+
Team Win %
@@ -71,10 +71,11 @@ # - Player - ITC Points - W–L–T - ITC Rank + Player + ITC Points + W–L–T + Win % + ITC Rank diff --git a/src/main.js b/src/main.js index 6c8be19..ccefa8c 100644 --- a/src/main.js +++ b/src/main.js @@ -57,8 +57,58 @@ const CACHE_KEY = "gg_aos_board_v1"; const $ = (id) => document.getElementById(id); const fmtPts = (n) => Number(n || 0).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }); +const fmtWpct = (w, l, t) => { const g = (+w||0)+(+l||0)+(+t||0); return g ? ((+w||0)/g*100).toFixed(1)+'%' : '—'; }; const fullName = (u = {}) => [u.firstName, u.lastName].filter(Boolean).join(" ").trim(); +/* ---- Sort state ---- */ +let sortState = { col: 'pts', dir: 'desc' }; +let lastState = null; + +const sortCols = { + pts: { key: r => +(r.ITCPoints || 0), defaultDir: 'desc' }, + name: { key: r => fullName(r.user || {}).toLowerCase() || '', defaultDir: 'asc' }, + wlt: { key: r => (+r.wins||0) * 10000 - (+r.losses||0) * 100 - (+r.ties||0), defaultDir: 'desc' }, + wpct: { key: r => { const g=(+r.wins||0)+(+r.losses||0)+(+r.ties||0); return g?(+r.wins||0)/g:-1; }, defaultDir: 'desc' }, + rank: { key: r => +(r.placing || Infinity), defaultDir: 'asc' }, +}; + +function applySort(rows) { + const { col, dir } = sortState; + const sc = sortCols[col] || sortCols.pts; + return [...rows].sort((a, b) => { + const av = sc.key(a), bv = sc.key(b); + if (typeof av === 'string') return dir === 'asc' ? av.localeCompare(bv) : bv.localeCompare(av); + return dir === 'asc' ? av - bv : bv - av; + }); +} + +function updateSortHeaders() { + document.querySelectorAll('th[data-sort]').forEach(th => { + const arrow = th.querySelector('.sort-arrow'); + if (arrow) arrow.textContent = th.dataset.sort === sortState.col ? (sortState.dir === 'asc' ? ' ▲' : ' ▼') : ''; + }); +} + +function initSortHeaders() { + document.querySelectorAll('th[data-sort]').forEach(th => { + const arrow = document.createElement('span'); + arrow.className = 'sort-arrow text-xs opacity-60'; + th.appendChild(arrow); + th.addEventListener('click', () => { + const col = th.dataset.sort; + if (sortState.col === col) { + sortState.dir = sortState.dir === 'asc' ? 'desc' : 'asc'; + } else { + sortState.col = col; + sortState.dir = (sortCols[col] || sortCols.pts).defaultDir; + } + updateSortHeaders(); + if (lastState) render(lastState); + }); + }); + updateSortHeaders(); +} + /* ---- Roster ---- */ async function getMemberIds(force) { try { @@ -122,6 +172,7 @@ function build(rows, ids) { /* ---- Render ---- */ function render(state) { + lastState = state; const { kept, rosterSize, updated, live, fromCache } = state; $("dot").className = "w-2 h-2 rounded-full bg-success animate-pulse transition-colors"; @@ -147,7 +198,7 @@ function render(state) { const top = kept[0], tu = top.user || {}; champ.classList.remove("hidden"); $("champ-name").textContent = fullName(tu) || tu.nickname || "—"; - $("champ-sub").textContent = `${top.wins || 0}–${top.losses || 0}–${top.ties || 0} · ITC #${top.placing ?? "—"}`; + $("champ-sub").textContent = `${fmtWpct(top.wins, top.losses, top.ties)} Win Rate · ITC #${top.placing ?? "—"}`; $("champ-pts").textContent = fmtPts(top.ITCPoints); // Stat strip @@ -157,11 +208,11 @@ function render(state) { best = Math.min(...kept.map(r => +r.placing || Infinity)); $("s-placed").textContent = kept.length; $("s-top").textContent = fmtPts(top.ITCPoints); - $("s-rec").textContent = `${W}–${L}–${T}`; + $("s-rec").textContent = fmtWpct(W, L, T); $("s-best").textContent = isFinite(best) ? ("#" + best) : "—"; // Rows - kept.forEach((r, i) => { + applySort(kept).forEach((r, i) => { const u = r.user || {}, name = fullName(u) || u.nickname || "Unknown"; const tr = document.createElement("tr"); tr.innerHTML = @@ -171,6 +222,7 @@ function render(state) { `${fmtPts(r.ITCPoints)}` + `` + `${r.wins || 0}${r.losses || 0}–${r.ties || 0}` + + `${fmtWpct(r.wins, r.losses, r.ties)}` + `#${r.placing ?? "—"}`; tb.appendChild(tr); }); @@ -236,6 +288,7 @@ function showSkeleton() { `
` + `
` + `
` + + `
` + `
` + `
`; tb.appendChild(tr); @@ -270,5 +323,6 @@ const SAMPLE = (() => { /* ---- Go ---- */ $("refresh").addEventListener("click", () => load(true)); +initSortHeaders(); paintCache(); load(false);