replace W-L-T with win percentage, add Win % column, and add sortable headers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+6
-5
@@ -38,7 +38,7 @@
|
|||||||
<div class="stat-value text-2xl text-warning" id="s-top">—</div>
|
<div class="stat-value text-2xl text-warning" id="s-top">—</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Combined W–L–T</div>
|
<div class="stat-title">Team Win %</div>
|
||||||
<div class="stat-value text-2xl" id="s-rec">—</div>
|
<div class="stat-value text-2xl" id="s-rec">—</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
@@ -71,10 +71,11 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-12">#</th>
|
<th class="w-12">#</th>
|
||||||
<th>Player</th>
|
<th class="cursor-pointer select-none" data-sort="name">Player</th>
|
||||||
<th class="text-right">ITC Points</th>
|
<th class="text-right cursor-pointer select-none" data-sort="pts">ITC Points</th>
|
||||||
<th class="text-right hidden sm:table-cell">W–L–T</th>
|
<th class="text-right hidden sm:table-cell cursor-pointer select-none" data-sort="wlt">W–L–T</th>
|
||||||
<th class="text-right hidden sm:table-cell">ITC Rank</th>
|
<th class="text-right hidden sm:table-cell cursor-pointer select-none" data-sort="wpct">Win %</th>
|
||||||
|
<th class="text-right hidden sm:table-cell cursor-pointer select-none" data-sort="rank">ITC Rank</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="rows"></tbody>
|
<tbody id="rows"></tbody>
|
||||||
|
|||||||
+57
-3
@@ -57,8 +57,58 @@ const CACHE_KEY = "gg_aos_board_v1";
|
|||||||
|
|
||||||
const $ = (id) => document.getElementById(id);
|
const $ = (id) => document.getElementById(id);
|
||||||
const fmtPts = (n) => Number(n || 0).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 });
|
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();
|
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 ---- */
|
/* ---- Roster ---- */
|
||||||
async function getMemberIds(force) {
|
async function getMemberIds(force) {
|
||||||
try {
|
try {
|
||||||
@@ -122,6 +172,7 @@ function build(rows, ids) {
|
|||||||
|
|
||||||
/* ---- Render ---- */
|
/* ---- Render ---- */
|
||||||
function render(state) {
|
function render(state) {
|
||||||
|
lastState = state;
|
||||||
const { kept, rosterSize, updated, live, fromCache } = state;
|
const { kept, rosterSize, updated, live, fromCache } = state;
|
||||||
|
|
||||||
$("dot").className = "w-2 h-2 rounded-full bg-success animate-pulse transition-colors";
|
$("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 || {};
|
const top = kept[0], tu = top.user || {};
|
||||||
champ.classList.remove("hidden");
|
champ.classList.remove("hidden");
|
||||||
$("champ-name").textContent = fullName(tu) || tu.nickname || "—";
|
$("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);
|
$("champ-pts").textContent = fmtPts(top.ITCPoints);
|
||||||
|
|
||||||
// Stat strip
|
// Stat strip
|
||||||
@@ -157,11 +208,11 @@ function render(state) {
|
|||||||
best = Math.min(...kept.map(r => +r.placing || Infinity));
|
best = Math.min(...kept.map(r => +r.placing || Infinity));
|
||||||
$("s-placed").textContent = kept.length;
|
$("s-placed").textContent = kept.length;
|
||||||
$("s-top").textContent = fmtPts(top.ITCPoints);
|
$("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) : "—";
|
$("s-best").textContent = isFinite(best) ? ("#" + best) : "—";
|
||||||
|
|
||||||
// Rows
|
// Rows
|
||||||
kept.forEach((r, i) => {
|
applySort(kept).forEach((r, i) => {
|
||||||
const u = r.user || {}, name = fullName(u) || u.nickname || "Unknown";
|
const u = r.user || {}, name = fullName(u) || u.nickname || "Unknown";
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
tr.innerHTML =
|
tr.innerHTML =
|
||||||
@@ -171,6 +222,7 @@ function render(state) {
|
|||||||
`<td class="text-right font-mono font-bold">${fmtPts(r.ITCPoints)}</td>` +
|
`<td class="text-right font-mono font-bold">${fmtPts(r.ITCPoints)}</td>` +
|
||||||
`<td class="text-right font-mono hidden sm:table-cell">` +
|
`<td class="text-right font-mono hidden sm:table-cell">` +
|
||||||
`<span class="text-success">${r.wins || 0}</span>–<span class="text-error">${r.losses || 0}</span>–${r.ties || 0}</td>` +
|
`<span class="text-success">${r.wins || 0}</span>–<span class="text-error">${r.losses || 0}</span>–${r.ties || 0}</td>` +
|
||||||
|
`<td class="text-right font-mono hidden sm:table-cell">${fmtWpct(r.wins, r.losses, r.ties)}</td>` +
|
||||||
`<td class="text-right font-mono text-base-content/40 hidden sm:table-cell">#${r.placing ?? "—"}</td>`;
|
`<td class="text-right font-mono text-base-content/40 hidden sm:table-cell">#${r.placing ?? "—"}</td>`;
|
||||||
tb.appendChild(tr);
|
tb.appendChild(tr);
|
||||||
});
|
});
|
||||||
@@ -236,6 +288,7 @@ function showSkeleton() {
|
|||||||
`<td><div class="skeleton h-4 w-5"></div></td>` +
|
`<td><div class="skeleton h-4 w-5"></div></td>` +
|
||||||
`<td><div class="skeleton h-4 w-3/5"></div></td>` +
|
`<td><div class="skeleton h-4 w-3/5"></div></td>` +
|
||||||
`<td><div class="skeleton h-4 w-12 ml-auto"></div></td>` +
|
`<td><div class="skeleton h-4 w-12 ml-auto"></div></td>` +
|
||||||
|
`<td class="hidden sm:table-cell"><div class="skeleton h-4 w-14 ml-auto"></div></td>` +
|
||||||
`<td class="hidden sm:table-cell"><div class="skeleton h-4 w-12 ml-auto"></div></td>` +
|
`<td class="hidden sm:table-cell"><div class="skeleton h-4 w-12 ml-auto"></div></td>` +
|
||||||
`<td class="hidden sm:table-cell"><div class="skeleton h-4 w-10 ml-auto"></div></td>`;
|
`<td class="hidden sm:table-cell"><div class="skeleton h-4 w-10 ml-auto"></div></td>`;
|
||||||
tb.appendChild(tr);
|
tb.appendChild(tr);
|
||||||
@@ -270,5 +323,6 @@ const SAMPLE = (() => {
|
|||||||
|
|
||||||
/* ---- Go ---- */
|
/* ---- Go ---- */
|
||||||
$("refresh").addEventListener("click", () => load(true));
|
$("refresh").addEventListener("click", () => load(true));
|
||||||
|
initSortHeaders();
|
||||||
paintCache();
|
paintCache();
|
||||||
load(false);
|
load(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user