Fix historical leaderboard table columns and title labels

This commit is contained in:
devRaGonSa
2026-06-11 13:43:25 +02:00
parent b4ed3d7a97
commit 49602c2975
2 changed files with 181 additions and 5 deletions

View File

@@ -0,0 +1,159 @@
# TASK-252 - Fix historical leaderboard table columns and title labels
## Summary
This task fixes two regressions introduced after TASK-250 in the public historical leaderboard:
- the section title could render as `undefined ...`
- the primary metric column header could disappear because `hydrateWeeklyLeaderboard(...)` was called with the wrong argument order in some refresh paths
It also tightens the leaderboard table behavior so each top shows only the columns that match its metric.
## Files Read First
- `frontend/historico.html`
- `frontend/assets/js/historico.js`
## Root Cause
### 1. `undefined` in title
`buildLeaderboardTitle(...)` expects a valid metric config with a `.title`.
After TASK-250, some calls to `hydrateWeeklyLeaderboard(...)` still used the old function signature and omitted the new `ratioHeadingNode` argument. That shifted the remaining arguments:
- `weeklyWindowNoteNode` was received as `ratioHeadingNode`
- `weeklySnapshotMetaNode` was received as `noteNode`
- the metric config was received as `snapshotMetaNode`
- the timeframe string was received as `metricConfig`
That left `metricConfig.title` undefined and produced titles like:
- `undefined Semanal - Todos los servidores`
### 2. Primary metric header missing
The same bad argument order also broke the header sync path:
- the value column header was still updated
- but the ratio-heading logic was running against the wrong node
- depending on the metric, the table could lose the intended visible structure and appear with the main metric column effectively unlabeled in production
## Changes Applied
- Updated every `hydrateWeeklyLeaderboard(...)` call to pass `weeklyRatioHeadingNode` in the correct position.
- Hardened `buildLeaderboardTitle(...)` so it always falls back to a safe metric config and never emits `undefined`.
- Adjusted metric labels to match the required public wording:
- `kills` -> title `Top kills`, value heading `Kills`
- `deaths` -> title `Top muertes`, value heading `Muertes`
- `matches_over_100_kills` -> title `Partidas 100+ kills`, value heading `Partidas 100+ kills`
- `support` -> title `Soporte`, value heading `Soporte`
- Kept ratio-column behavior metric-aware:
- kills -> `Kills/partida`
- deaths -> `Muertes/partida`
- matches_over_100_kills -> no ratio column
- support -> `Soporte/partida` only when rows exist; otherwise the ratio column stays hidden
## Final Headers By Top
### Top kills
- `Pos.`
- `Jugador`
- `Kills`
- `Partidas`
- `Kills/partida`
### Top muertes
- `Pos.`
- `Jugador`
- `Muertes`
- `Partidas`
- `Muertes/partida`
### Partidas 100+ kills
- `Pos.`
- `Jugador`
- `Partidas 100+ kills`
- `Partidas`
### Soporte
If rows exist:
- `Pos.`
- `Jugador`
- `Soporte`
- `Partidas`
- `Soporte/partida`
If no rows exist:
- `Pos.`
- `Jugador`
- `Soporte`
- `Partidas`
## Support Status
Current support payload behavior was reviewed only to the extent needed to diagnose the UI:
- the RCON historical leaderboard code documents support as a special case
- when support data is not available from the materialized RCON read model, it returns an empty supported payload instead of falling back to unrelated public-scoreboard totals
Frontend behavior now matches that contract:
- no `undefined`
- no stale `Kills/partida`
- no ratio column for support when there are no rows
## Validation
Executed:
- `node --check frontend/assets/js/historico.js`
Static checks:
- every `hydrateWeeklyLeaderboard(...)` call now passes `weeklyRatioHeadingNode`
- `buildLeaderboardTitle(...)` has a safe fallback path and no longer depends on an unchecked `metricConfig.title`
- header/value mappings were checked for:
- `kills`
- `deaths`
- `matches_over_100_kills`
- `support`
Expected output matrix:
- kills -> `Pos., Jugador, Kills, Partidas, Kills/partida`
- deaths -> `Pos., Jugador, Muertes, Partidas, Muertes/partida`
- matches_over_100_kills -> `Pos., Jugador, Partidas 100+ kills, Partidas`
- support -> `Pos., Jugador, Soporte, Partidas`, plus `Soporte/partida` only when rows exist
## Post-Deploy Visual Check
1. Open `historico.html`
2. Select `Semanal + Top kills`
- title must be `Top kills Semanal - ...`
- `Kills` column visible
- `Kills/partida` visible
3. Select `Mensual + Top muertes`
- title must be `Top muertes Mensual - ...`
- `Muertes` column visible
- `Muertes/partida` visible
4. Select `Partidas 100+ kills`
- title must be `Partidas 100+ kills ...`
- `Partidas 100+ kills` column visible
- no ratio column
5. Select `Soporte`
- title must be `Soporte ...`
- no `undefined`
- no `Kills/partida`
- if no rows, empty state remains intact
## Notes
- No backend changes were required.
- No assets, maps, brands, clan images or weapon images were modified.

View File

@@ -59,15 +59,15 @@ const LEADERBOARD_METRICS = Object.freeze([
}, },
{ {
key: "matches_over_100_kills", key: "matches_over_100_kills",
title: "Top partidas con 100+ kills", title: "Partidas 100+ kills",
valueHeading: "Partidas 100+", valueHeading: "Partidas 100+ kills",
ratioHeading: null, ratioHeading: null,
ratioMode: null, ratioMode: null,
emptyMessage: "Ningun jugador ha registrado partidas de 100+ kills en esta ventana.", emptyMessage: "Ningun jugador ha registrado partidas de 100+ kills en esta ventana.",
}, },
{ {
key: "support", key: "support",
title: "Top puntos de soporte", title: "Soporte",
valueHeading: "Soporte", valueHeading: "Soporte",
ratioHeading: "Soporte/partida", ratioHeading: "Soporte/partida",
ratioMode: "support", ratioMode: "support",
@@ -312,6 +312,7 @@ document.addEventListener("DOMContentLoaded", () => {
weeklyBodyNode, weeklyBodyNode,
weeklyTitleNode, weeklyTitleNode,
weeklyValueHeadingNode, weeklyValueHeadingNode,
weeklyRatioHeadingNode,
weeklyWindowNoteNode, weeklyWindowNoteNode,
weeklySnapshotMetaNode, weeklySnapshotMetaNode,
activeMetricConfig, activeMetricConfig,
@@ -360,6 +361,7 @@ document.addEventListener("DOMContentLoaded", () => {
weeklyBodyNode, weeklyBodyNode,
weeklyTitleNode, weeklyTitleNode,
weeklyValueHeadingNode, weeklyValueHeadingNode,
weeklyRatioHeadingNode,
weeklyWindowNoteNode, weeklyWindowNoteNode,
weeklySnapshotMetaNode, weeklySnapshotMetaNode,
metricConfig, metricConfig,
@@ -399,6 +401,7 @@ document.addEventListener("DOMContentLoaded", () => {
weeklyBodyNode, weeklyBodyNode,
weeklyTitleNode, weeklyTitleNode,
weeklyValueHeadingNode, weeklyValueHeadingNode,
weeklyRatioHeadingNode,
weeklyWindowNoteNode, weeklyWindowNoteNode,
weeklySnapshotMetaNode, weeklySnapshotMetaNode,
metricConfig, metricConfig,
@@ -1509,8 +1512,12 @@ function buildWeeklyWindowNote(payload) {
} }
function buildLeaderboardTitle(metricConfig, serverSlug, timeframeKey) { function buildLeaderboardTitle(metricConfig, serverSlug, timeframeKey) {
const safeMetricConfig = metricConfig?.key
? metricConfig
: getLeaderboardMetricConfig(metricConfig?.key);
const timeframeLabel = getLeaderboardTimeframeConfig(timeframeKey).label; const timeframeLabel = getLeaderboardTimeframeConfig(timeframeKey).label;
return `${metricConfig.title} ${timeframeLabel} - ${getHistoricalServerLabel(serverSlug)}`; const titleLabel = safeMetricConfig?.title || LEADERBOARD_METRICS[0].title;
return `${titleLabel} ${timeframeLabel} - ${getHistoricalServerLabel(serverSlug)}`;
} }
function buildRecentMatchesNote(serverSlug) { function buildRecentMatchesNote(serverSlug) {
@@ -1739,7 +1746,7 @@ function syncLeaderboardRatioColumn(tableNode, ratioHeadingNode, bodyNode, metri
if (!tableNode || !ratioHeadingNode || !bodyNode) { if (!tableNode || !ratioHeadingNode || !bodyNode) {
return; return;
} }
const showRatio = Boolean(metricConfig?.ratioMode); const showRatio = shouldShowLeaderboardRatioColumn(metricConfig, bodyNode);
ratioHeadingNode.hidden = !showRatio; ratioHeadingNode.hidden = !showRatio;
ratioHeadingNode.textContent = metricConfig?.ratioHeading || ""; ratioHeadingNode.textContent = metricConfig?.ratioHeading || "";
const ratioColumnIndex = ratioHeadingNode.cellIndex; const ratioColumnIndex = ratioHeadingNode.cellIndex;
@@ -1751,6 +1758,16 @@ function syncLeaderboardRatioColumn(tableNode, ratioHeadingNode, bodyNode, metri
}); });
} }
function shouldShowLeaderboardRatioColumn(metricConfig, bodyNode) {
if (!metricConfig?.ratioMode) {
return false;
}
if (metricConfig.ratioMode === "support") {
return bodyNode.children.length > 0;
}
return true;
}
function formatHistoricalRatio(item, metricConfig, matches) { function formatHistoricalRatio(item, metricConfig, matches) {
if (!metricConfig?.ratioMode) { if (!metricConfig?.ratioMode) {
return ""; return "";