diff --git a/index.html b/index.html
index 8c3a934..893f52d 100644
--- a/index.html
+++ b/index.html
@@ -75,6 +75,7 @@
ITC Points |
W–L–T |
Win % |
+ Career W% |
ITC Rank |
diff --git a/src/main.js b/src/main.js
index a8da12b..430e46e 100644
--- a/src/main.js
+++ b/src/main.js
@@ -60,6 +60,44 @@ const fmtPts = (n) => Number(n || 0).toLocaleString(undefined, { minimumFraction
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();
+/* ---- Career stats ---- */
+const careerData = {}; // userId → { w, l, t } | null (in-flight)
+
+async function fetchCareer(userId) {
+ const r = await fetch(
+ `${API}/placings?placingsType=player&userId=${userId}&limit=1000`,
+ { headers: HEADERS, credentials: 'omit' }
+ );
+ if (!r.ok) return { w: 0, l: 0, t: 0 };
+ const p = await r.json();
+ const items = Array.isArray(p) ? p : (p.data || []);
+ let w = 0, l = 0, t = 0;
+ for (const item of items) {
+ const v = item.value || item;
+ if (v.wins != null || v.losses != null) {
+ w += +(v.wins || 0);
+ l += +(v.losses || 0);
+ t += +(v.ties || 0);
+ }
+ }
+ return { w, l, t };
+}
+
+async function loadCareerStats(kept) {
+ const toFetch = kept.filter(r => {
+ const uid = r.userId || r.user?.id;
+ return uid && !(uid in careerData);
+ });
+ if (!toFetch.length) return;
+ toFetch.forEach(r => { careerData[r.userId || r.user?.id] = null; });
+ await Promise.allSettled(toFetch.map(async r => {
+ const uid = r.userId || r.user?.id;
+ try { careerData[uid] = await fetchCareer(uid); }
+ catch { careerData[uid] = { w: 0, l: 0, t: 0 }; }
+ }));
+ if (lastState) render(lastState);
+}
+
/* ---- Sort state ---- */
let sortState = { col: 'pts', dir: 'desc' };
let lastState = null;
@@ -69,7 +107,8 @@ const sortCols = {
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' },
+ rank: { key: r => +(r.placing || Infinity), defaultDir: 'asc' },
+ cwpct: { key: r => { const d = careerData[r.userId || r.user?.id]; if (!d) return -1; const g = d.w+d.l+d.t; return g ? d.w/g : -1; }, defaultDir: 'desc' },
};
function applySort(rows) {
@@ -211,6 +250,9 @@ function render(state) {
$("s-rec").textContent = fmtWpct(W, L, T);
$("s-best").textContent = isFinite(best) ? ("#" + best) : "—";
+ // Kick off career stat fetches for any players not yet loaded (non-blocking)
+ loadCareerStats(kept);
+
// Rows
applySort(kept).forEach((r, i) => {
const u = r.user || {}, name = fullName(u) || u.nickname || "Unknown";
@@ -223,6 +265,12 @@ function render(state) {
`` +
`${r.wins || 0}–${r.losses || 0}–${r.ties || 0} | ` +
`${fmtWpct(r.wins, r.losses, r.ties)} | ` +
+ (() => { const uid = r.userId || r.user?.id; const d = uid && careerData[uid];
+ return `${
+ d == null ? '…'
+ : (d.w+d.l+d.t) === 0 ? '—'
+ : fmtWpct(d.w, d.l, d.t)
+ } | `; })() +
`#${r.placing ?? "—"} | `;
tb.appendChild(tr);
});
@@ -290,6 +338,7 @@ function showSkeleton() {
` | ` +
` | ` +
` | ` +
+ ` | ` +
` | `;
tb.appendChild(tr);
}