Dashboard: rank column with gold/silver/bronze podium for top 3 (leaderboard sorts)
Browse files
app.py
CHANGED
|
@@ -275,6 +275,16 @@ DASHBOARD_HTML = """<!DOCTYPE html>
|
|
| 275 |
.lag-track { height: 8px; border-radius: 4px; overflow: hidden; background: var(--panel-2); }
|
| 276 |
.lag-fill { height: 100%; background: var(--accent); border-radius: 4px; }
|
| 277 |
.lag-empty { font-size: 12px; color: var(--muted); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
/* in-row stacked status bar (table Progress column) */
|
| 279 |
.bar-track { flex: 1; display: flex; height: 14px; border-radius: 4px; overflow: hidden;
|
| 280 |
background: var(--panel-2); }
|
|
@@ -481,10 +491,22 @@ async function refresh() {
|
|
| 481 |
return;
|
| 482 |
}
|
| 483 |
|
| 484 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
const c = r.counts || {};
|
| 486 |
const p = pct(c, r.total || 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
return `<tr>
|
|
|
|
| 488 |
<td class="name">${escapeHtml(r.annotator || "—")}<div class="muted" style="font-weight:400;font-size:12px">${escapeHtml(r.folder || "")}</div></td>
|
| 489 |
<td class="num">${r.total || 0}</td>
|
| 490 |
<td><span class="pill delete">${c.delete || 0}</span></td>
|
|
@@ -500,6 +522,7 @@ async function refresh() {
|
|
| 500 |
|
| 501 |
const ac = agg.counts || {};
|
| 502 |
const foot = `<tr>
|
|
|
|
| 503 |
<td>Team total</td>
|
| 504 |
<td class="num">${agg.total}</td>
|
| 505 |
<td><span class="pill delete">${ac.delete || 0}</span></td>
|
|
@@ -514,6 +537,7 @@ async function refresh() {
|
|
| 514 |
|
| 515 |
document.getElementById("table-wrap").innerHTML = `<table>
|
| 516 |
<thead><tr>
|
|
|
|
| 517 |
<th data-sort="name" class="sortable">Annotator${arrow("name")}</th>
|
| 518 |
<th data-sort="total" class="sortable num">Total${arrow("total")}</th>
|
| 519 |
<th data-sort="delete" class="sortable">Delete${arrow("delete")}</th>
|
|
|
|
| 275 |
.lag-track { height: 8px; border-radius: 4px; overflow: hidden; background: var(--panel-2); }
|
| 276 |
.lag-fill { height: 100%; background: var(--accent); border-radius: 4px; }
|
| 277 |
.lag-empty { font-size: 12px; color: var(--muted); }
|
| 278 |
+
/* rank / podium column */
|
| 279 |
+
th.rank-h { width: 44px; text-align: center; }
|
| 280 |
+
td.rank { width: 44px; text-align: center; color: var(--muted);
|
| 281 |
+
font-variant-numeric: tabular-nums; }
|
| 282 |
+
.medal { display: inline-flex; align-items: center; justify-content: center;
|
| 283 |
+
width: 25px; height: 25px; border-radius: 50%; font-weight: 700; font-size: 12px;
|
| 284 |
+
color: #1a1205; box-shadow: inset 0 0 0 1px rgba(0,0,0,.18), 0 1px 3px rgba(0,0,0,.25); }
|
| 285 |
+
.medal.gold { background: linear-gradient(145deg, #ffe487, #f5b301); }
|
| 286 |
+
.medal.silver { background: linear-gradient(145deg, #eef2f5, #b4bdc7); }
|
| 287 |
+
.medal.bronze { background: linear-gradient(145deg, #f0b27a, #c2772f); }
|
| 288 |
/* in-row stacked status bar (table Progress column) */
|
| 289 |
.bar-track { flex: 1; display: flex; height: 14px; border-radius: 4px; overflow: hidden;
|
| 290 |
background: var(--panel-2); }
|
|
|
|
| 491 |
return;
|
| 492 |
}
|
| 493 |
|
| 494 |
+
// Podium medals only when the table is a genuine leaderboard: sorted
|
| 495 |
+
// descending on an "achievement" column (Done is the default). Otherwise the
|
| 496 |
+
// rank column just shows the position in the current view.
|
| 497 |
+
const PODIUM_KEYS = new Set(["done", "completion", "correct", "edited"]);
|
| 498 |
+
const podium = sortDir === "desc" && PODIUM_KEYS.has(sortKey);
|
| 499 |
+
const MEDALS = ["gold", "silver", "bronze"];
|
| 500 |
+
|
| 501 |
+
const rows = reports.map((r, i) => {
|
| 502 |
const c = r.counts || {};
|
| 503 |
const p = pct(c, r.total || 0);
|
| 504 |
+
const rank = i + 1;
|
| 505 |
+
const rankCell = (podium && rank <= 3)
|
| 506 |
+
? `<td class="rank"><span class="medal ${MEDALS[rank - 1]}">${rank}</span></td>`
|
| 507 |
+
: `<td class="rank">${rank}</td>`;
|
| 508 |
return `<tr>
|
| 509 |
+
${rankCell}
|
| 510 |
<td class="name">${escapeHtml(r.annotator || "—")}<div class="muted" style="font-weight:400;font-size:12px">${escapeHtml(r.folder || "")}</div></td>
|
| 511 |
<td class="num">${r.total || 0}</td>
|
| 512 |
<td><span class="pill delete">${c.delete || 0}</span></td>
|
|
|
|
| 522 |
|
| 523 |
const ac = agg.counts || {};
|
| 524 |
const foot = `<tr>
|
| 525 |
+
<td class="rank"></td>
|
| 526 |
<td>Team total</td>
|
| 527 |
<td class="num">${agg.total}</td>
|
| 528 |
<td><span class="pill delete">${ac.delete || 0}</span></td>
|
|
|
|
| 537 |
|
| 538 |
document.getElementById("table-wrap").innerHTML = `<table>
|
| 539 |
<thead><tr>
|
| 540 |
+
<th class="rank-h" title="Rank in the current sort">#</th>
|
| 541 |
<th data-sort="name" class="sortable">Annotator${arrow("name")}</th>
|
| 542 |
<th data-sort="total" class="sortable num">Total${arrow("total")}</th>
|
| 543 |
<th data-sort="delete" class="sortable">Delete${arrow("delete")}</th>
|