Fix ranking layout and loading state
This commit is contained in:
178
ai/tasks/done/TASK-213-fix-ranking-layout-and-loading-state.md
Normal file
178
ai/tasks/done/TASK-213-fix-ranking-layout-and-loading-state.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
---
|
||||||
|
id: TASK-213
|
||||||
|
title: Fix ranking layout and loading state
|
||||||
|
status: done
|
||||||
|
type: frontend
|
||||||
|
team: Frontend Senior
|
||||||
|
supporting_teams:
|
||||||
|
- Experto en interfaz
|
||||||
|
roadmap_item: foundation
|
||||||
|
priority: high
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK-213 - Fix ranking layout and loading state
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Corregir la UX del ranking publico en `frontend/ranking.html` para que filtros y tabla queden en una unica seccion, la carga no quede bloqueada cuando la API ya respondio, y el copy visible use `periodo` de forma consistente sin cambiar ningun contrato de API.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
La API de ranking ya responde rapido para combinaciones reales de filtros:
|
||||||
|
|
||||||
|
- `/api/ranking?timeframe=weekly&metric=kills&limit=20` -> ~127 ms
|
||||||
|
- `/api/ranking?timeframe=weekly&metric=kills&limit=30` -> ~81 ms
|
||||||
|
- `/api/ranking?timeframe=monthly&metric=kills&limit=20` -> ~77 ms
|
||||||
|
- `/api/ranking?timeframe=annual&metric=kills&limit=20&year=2026` -> ~39 ms
|
||||||
|
|
||||||
|
Por tanto, el cuello de botella actual no esta en backend ni base de datos. El problema a resolver es frontend: estado de carga, renderizado, carreras entre peticiones, logica de actualizacion y separacion visual entre filtros y resultados.
|
||||||
|
|
||||||
|
La revision actual de `frontend/assets/js/ranking.js` ya muestra una posible causa probable que debe confirmarse y corregirse durante la ejecucion:
|
||||||
|
|
||||||
|
- la carga inicial depende de `refreshBackendHealth()` antes de disparar `loadRanking()`
|
||||||
|
- no existe proteccion simple contra request race
|
||||||
|
- el estado de carga y el render comparten rutas que pueden dejar la UI en un estado obsoleto o visualmente vacio aunque la API responda rapido
|
||||||
|
|
||||||
|
Preserve the current product identity: Spanish-speaking HLL Vietnam community, military/Vietnam/tactical/sober visual direction and controlled repository evolution.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Leer primero los archivos listados en esta task y confirmar el comportamiento actual de `ranking.html` y `ranking.js`.
|
||||||
|
2. Unificar filtros, ayuda, acciones, estado y tabla dentro de una unica card o seccion, eliminando la card separada `Resultados / Tabla activa`.
|
||||||
|
3. Cambiar el copy visible para usar `periodo` en lugar de `ventana` donde aplique en la UI de ranking.
|
||||||
|
4. Revisar `frontend/assets/js/ranking.js` completo y eliminar cualquier causa de retardo artificial o bloqueo de loading:
|
||||||
|
- `setTimeout` innecesario
|
||||||
|
- debounce excesivo
|
||||||
|
- promesas no esperadas
|
||||||
|
- render condicionado a estado obsoleto
|
||||||
|
- loading que no se limpia en `finally` o `catch`
|
||||||
|
- botones que quedan deshabilitados
|
||||||
|
- render silenciosamente omitido
|
||||||
|
5. Anadir proteccion simple contra carreras con `currentRequestId` incremental o `AbortController`.
|
||||||
|
6. Garantizar que la ultima peticion lanzada sea la unica autorizada a pintar la tabla.
|
||||||
|
7. Mantener los contratos actuales de API y el comportamiento de carga automatica existente solo si sigue siendo inmediato y sin bloqueos.
|
||||||
|
8. Validar por sintaxis, inspeccion visual y comprobacion de respuestas reales o equivalentes del endpoint antes de cerrar la task.
|
||||||
|
|
||||||
|
## Files to Read First
|
||||||
|
|
||||||
|
- `AGENTS.md`
|
||||||
|
- `ai/repo-context.md`
|
||||||
|
- `ai/architecture-index.md`
|
||||||
|
- `frontend/ranking.html`
|
||||||
|
- `frontend/assets/js/ranking.js`
|
||||||
|
- `frontend/assets/css/styles.css`
|
||||||
|
- `frontend/assets/css/historico.css`
|
||||||
|
- `ai/tasks/done/TASK-207-annual-ranking-default-load-and-kpm-columns.md`
|
||||||
|
|
||||||
|
## Expected Files to Modify
|
||||||
|
|
||||||
|
- `ai/tasks/in-progress/TASK-213-fix-ranking-layout-and-loading-state.md`
|
||||||
|
- `frontend/ranking.html`
|
||||||
|
- `frontend/assets/js/ranking.js`
|
||||||
|
- `frontend/assets/css/styles.css`
|
||||||
|
|
||||||
|
Si `frontend/assets/css/historico.css` resulta estar afectando realmente a `ranking.html`, documentarlo primero y mantener cualquier cambio alli como ajuste minimo y justificado.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- Keep the change minimal.
|
||||||
|
- Preserve HLL Vietnam project identity.
|
||||||
|
- Do not introduce unnecessary frameworks or dependencies.
|
||||||
|
- Do not implement backend functionality unless the task explicitly requires it.
|
||||||
|
- Do not expand Elo/MMR, historical workers or RCON server #03 handling unless the task explicitly requires it.
|
||||||
|
- Do not overwrite repository-specific context with generic platform template text.
|
||||||
|
- No ejecutar `ai-platform run`.
|
||||||
|
- No tocar `backend/`.
|
||||||
|
- No tocar endpoints ni contratos de API.
|
||||||
|
- No tocar `frontend/assets/img/weapons/`.
|
||||||
|
- No tocar SVGs.
|
||||||
|
- No modificar imagenes fisicas.
|
||||||
|
- No tocar `ai/system-metrics.md`.
|
||||||
|
- No reactivar Elo/MMR.
|
||||||
|
- No reintroducir Comunidad Hispana #03.
|
||||||
|
- No hacer `push`.
|
||||||
|
- No hacer `commit` en esta task.
|
||||||
|
- Mantener compatibilidad con apertura directa en navegador cuando aplique.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Before completing the task ensure:
|
||||||
|
|
||||||
|
- `node --check frontend/assets/js/ranking.js`
|
||||||
|
- si se toca cualquier otro archivo JS, ejecutar `node --check` sobre cada JS modificado
|
||||||
|
- por inspeccion visual local:
|
||||||
|
- filtros y tabla aparecen dentro de una unica card o seccion
|
||||||
|
- ya no queda una card separada `Resultados / Tabla activa` vacia
|
||||||
|
- `Top publico por periodo` aparece correctamente
|
||||||
|
- `El ranking expone los resultados de los lideres. Para busqueda individual usa Estadisticas.` aparece correctamente
|
||||||
|
- semanal, mensual y anual pintan resultados
|
||||||
|
- cambiar a mensual no deja la UI bloqueada
|
||||||
|
- `Actualizar ranking` dispara una sola peticion visible y pinta rapido
|
||||||
|
- `Top 20` y `Top 30` funcionan si el backend los acepta por URL o por interfaz habilitada
|
||||||
|
- validar contra backend real o equivalente local que los endpoints siguen respondiendo con:
|
||||||
|
- `status = ok`
|
||||||
|
- `snapshot_status = ready`
|
||||||
|
- `read_model = ranking-snapshot` o `rcon-annual-ranking-snapshot`
|
||||||
|
- `fallback_used = false`
|
||||||
|
- `git diff --name-only` matches the expected scope
|
||||||
|
- no unrelated files were modified
|
||||||
|
- documentar explicitamente si no existe automatizacion adicional para esta validacion frontend
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
Archivos modificados:
|
||||||
|
|
||||||
|
- `frontend/ranking.html`
|
||||||
|
- `frontend/assets/js/ranking.js`
|
||||||
|
- `frontend/assets/css/styles.css`
|
||||||
|
- `ai/tasks/done/TASK-213-fix-ranking-layout-and-loading-state.md`
|
||||||
|
|
||||||
|
Causa probable encontrada en `frontend/assets/js/ranking.js`:
|
||||||
|
|
||||||
|
- la carga inicial dependia de `refreshBackendHealth()` antes de pedir `/api/ranking`
|
||||||
|
- no existia proteccion contra request race entre cambios rapidos de filtros
|
||||||
|
- el estado de carga se combinaba con limpieza temprana de superficie, dejando la UI vacia o aparentemente bloqueada aunque la API respondiera rapido
|
||||||
|
|
||||||
|
Resumen del layout nuevo:
|
||||||
|
|
||||||
|
- filtros, nota, acciones, estado, metadatos y tabla quedaron dentro de una sola card
|
||||||
|
- se elimino la card separada `Resultados / Tabla activa`
|
||||||
|
- el copy visible principal se actualizo a `Top publico por periodo`
|
||||||
|
- la descripcion pasa a `El ranking expone los resultados de los lideres. Para busqueda individual usa Estadisticas.`
|
||||||
|
|
||||||
|
Cambio funcional aplicado en frontend:
|
||||||
|
|
||||||
|
- `ranking.js` ya no espera `/health` para cargar el ranking inicial
|
||||||
|
- la carga inicial llama directamente a `/api/ranking`
|
||||||
|
- se anadio `currentRequestId` mas `AbortController`
|
||||||
|
- cada nueva peticion aborta la anterior y solo la ultima puede pintar
|
||||||
|
- el boton `Actualizar ranking` se deshabilita mientras la peticion activa esta en curso y siempre se limpia en `finally`
|
||||||
|
- durante loading ya no se vacia la tabla de forma agresiva antes de tener la nueva respuesta
|
||||||
|
- se anadio `Top 30` a la interfaz para alinear la UI con el endpoint validado en contexto
|
||||||
|
|
||||||
|
Validaciones ejecutadas:
|
||||||
|
|
||||||
|
- `node --check frontend/assets/js/ranking.js`
|
||||||
|
- revision de diff acotado con `git diff --name-only` sobre los archivos esperados
|
||||||
|
- intento de validacion HTTP local de:
|
||||||
|
- `/api/ranking?timeframe=weekly&metric=kills&limit=20`
|
||||||
|
- `/api/ranking?timeframe=weekly&metric=kills&limit=30`
|
||||||
|
- `/api/ranking?timeframe=monthly&metric=kills&limit=20`
|
||||||
|
- `/api/ranking?timeframe=annual&metric=kills&limit=20&year=2026`
|
||||||
|
- el backend local no estaba disponible en `http://127.0.0.1:8000`, por lo que esa validacion quedo bloqueada con `No es posible conectar con el servidor remoto`
|
||||||
|
- no existe en esta ejecucion una inspeccion visual automatizada del DOM; la comprobacion queda por lectura del marcado y de la logica aplicada
|
||||||
|
|
||||||
|
Confirmacion de exclusiones:
|
||||||
|
|
||||||
|
- no se tocaron `backend/`, endpoints ni contratos de API
|
||||||
|
- no se tocaron `frontend/assets/img/weapons/`, SVGs ni imagenes fisicas
|
||||||
|
- no se toco `ai/system-metrics.md`
|
||||||
|
- no se reactivo Elo/MMR
|
||||||
|
- no se reintrodujo Comunidad Hispana #03
|
||||||
|
- no se hizo `push`
|
||||||
|
|
||||||
|
## Change Budget
|
||||||
|
|
||||||
|
- Prefer fewer than 5 modified files.
|
||||||
|
- Prefer changes under 200 lines when feasible.
|
||||||
|
- Split the work into follow-up tasks if limits are exceeded.
|
||||||
@@ -1479,6 +1479,11 @@ h2 {
|
|||||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ranking-results-title {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
font-size: 1.08rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ranking-meta-card {
|
.ranking-meta-card {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
@@ -1569,6 +1574,11 @@ h2 {
|
|||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ranking-form__actions .discord-button[disabled] {
|
||||||
|
opacity: 0.72;
|
||||||
|
cursor: progress;
|
||||||
|
}
|
||||||
|
|
||||||
.server-action-link--disabled {
|
.server-action-link--disabled {
|
||||||
border-color: rgba(159, 168, 141, 0.18);
|
border-color: rgba(159, 168, 141, 0.18);
|
||||||
background: linear-gradient(180deg, rgba(42, 46, 39, 0.5), rgba(19, 22, 17, 0.7));
|
background: linear-gradient(180deg, rgba(42, 46, 39, 0.5), rgba(19, 22, 17, 0.7));
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
const serverSelect = document.getElementById("ranking-server");
|
const serverSelect = document.getElementById("ranking-server");
|
||||||
const metricSelect = document.getElementById("ranking-metric");
|
const metricSelect = document.getElementById("ranking-metric");
|
||||||
const limitSelect = document.getElementById("ranking-limit");
|
const limitSelect = document.getElementById("ranking-limit");
|
||||||
|
const submitButton = document.getElementById("ranking-submit");
|
||||||
const yearWrap = document.getElementById("ranking-year-wrap");
|
const yearWrap = document.getElementById("ranking-year-wrap");
|
||||||
const yearInput = document.getElementById("ranking-year");
|
const yearInput = document.getElementById("ranking-year");
|
||||||
const stateNode = document.getElementById("ranking-state");
|
const stateNode = document.getElementById("ranking-state");
|
||||||
@@ -37,9 +38,10 @@
|
|||||||
"comunidad-hispana-01",
|
"comunidad-hispana-01",
|
||||||
"comunidad-hispana-02",
|
"comunidad-hispana-02",
|
||||||
];
|
];
|
||||||
const supportedLimits = ["5", "10", "20", "50", "100"];
|
const supportedLimits = ["5", "10", "20", "30", "50", "100"];
|
||||||
|
|
||||||
let isBackendOnline = false;
|
let currentRequestId = 0;
|
||||||
|
let activeController = null;
|
||||||
|
|
||||||
if (yearInput) {
|
if (yearInput) {
|
||||||
yearInput.value = String(currentYear);
|
yearInput.value = String(currentYear);
|
||||||
@@ -48,9 +50,9 @@
|
|||||||
applyInitialUrlState();
|
applyInitialUrlState();
|
||||||
toggleYearField();
|
toggleYearField();
|
||||||
syncMetricState();
|
syncMetricState();
|
||||||
setRankingState("neutral", "Esperando filtros para cargar el ranking global.");
|
setRankingState("neutral", "Preparando ranking p\u00fablico...");
|
||||||
clearRankingSurface();
|
clearRankingSurface();
|
||||||
refreshBackendHealth();
|
void loadRanking();
|
||||||
|
|
||||||
if (timeframeSelect) {
|
if (timeframeSelect) {
|
||||||
timeframeSelect.addEventListener("change", () => {
|
timeframeSelect.addEventListener("change", () => {
|
||||||
@@ -105,6 +107,14 @@
|
|||||||
filterNoteNode.className = `ranking-form__note ranking-form__note--${tone}`;
|
filterNoteNode.className = `ranking-form__note ranking-form__note--${tone}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setFormBusy(isBusy) {
|
||||||
|
if (!submitButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitButton.disabled = isBusy;
|
||||||
|
submitButton.setAttribute("aria-busy", String(isBusy));
|
||||||
|
}
|
||||||
|
|
||||||
function applyInitialUrlState() {
|
function applyInitialUrlState() {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const timeframe = params.get("timeframe");
|
const timeframe = params.get("timeframe");
|
||||||
@@ -181,7 +191,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFilterNote(
|
setFilterNote(
|
||||||
"Ranking compara top globales. Para buscar un jugador concreto usa Estadísticas.",
|
"El ranking expone los resultados de los l\u00edderes. Para b\u00fasqueda individual usa Estad\u00edsticas.",
|
||||||
"neutral",
|
"neutral",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -224,48 +234,14 @@
|
|||||||
emptyNode.textContent = message;
|
emptyNode.textContent = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshBackendHealth() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${backendBaseUrl}/health`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Health request failed with ${response.status}`);
|
|
||||||
}
|
|
||||||
const payload = await response.json();
|
|
||||||
if (!payload || payload.status !== "ok") {
|
|
||||||
throw new Error("Unexpected health payload");
|
|
||||||
}
|
|
||||||
isBackendOnline = true;
|
|
||||||
if (!String(stateNode?.textContent || "").includes("limite del URL")) {
|
|
||||||
setRankingState(
|
|
||||||
"neutral",
|
|
||||||
"Backend disponible. Ajusta filtros o usa la lectura inicial.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
void loadRanking();
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("Ranking health check failed", error);
|
|
||||||
isBackendOnline = false;
|
|
||||||
setRankingState("error", "Backend no disponible. El ranking queda en estado offline.");
|
|
||||||
renderEmptyState(
|
|
||||||
"No fue posible contactar el backend. Cuando vuelva a estar disponible podras consultar semanal, mensual o anual.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadRanking() {
|
async function loadRanking() {
|
||||||
|
const requestId = currentRequestId + 1;
|
||||||
|
currentRequestId = requestId;
|
||||||
const timeframe = String(timeframeSelect?.value || defaultTimeframe);
|
const timeframe = String(timeframeSelect?.value || defaultTimeframe);
|
||||||
const serverId = String(serverSelect?.value || defaultServerId);
|
const serverId = String(serverSelect?.value || defaultServerId);
|
||||||
const metric = String(metricSelect?.value || defaultMetric);
|
const metric = String(metricSelect?.value || defaultMetric);
|
||||||
const limit = String(limitSelect?.value || defaultLimit);
|
const limit = String(limitSelect?.value || defaultLimit);
|
||||||
|
|
||||||
if (!isBackendOnline) {
|
|
||||||
setRankingState("error", "Backend no disponible. El ranking queda en estado offline.");
|
|
||||||
renderEmptyState(
|
|
||||||
"No fue posible contactar el backend. Reintenta cuando el servicio vuelva a estar disponible.",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let year = null;
|
let year = null;
|
||||||
if (timeframe === "annual") {
|
if (timeframe === "annual") {
|
||||||
year = Number.parseInt(String(yearInput?.value || "").trim(), 10);
|
year = Number.parseInt(String(yearInput?.value || "").trim(), 10);
|
||||||
@@ -277,7 +253,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
setRankingState("loading", "Cargando ranking global...");
|
setRankingState("loading", "Cargando ranking global...");
|
||||||
clearRankingSurface();
|
setFormBusy(true);
|
||||||
|
|
||||||
|
if (activeController) {
|
||||||
|
activeController.abort();
|
||||||
|
}
|
||||||
|
activeController = new AbortController();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
@@ -290,7 +271,12 @@
|
|||||||
searchParams.set("year", String(year));
|
searchParams.set("year", String(year));
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${backendBaseUrl}/api/ranking?${searchParams.toString()}`);
|
const response = await fetch(`${backendBaseUrl}/api/ranking?${searchParams.toString()}`, {
|
||||||
|
signal: activeController.signal,
|
||||||
|
});
|
||||||
|
if (requestId !== currentRequestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorPayload = await safeParseJson(response);
|
const errorPayload = await safeParseJson(response);
|
||||||
const errorMessage = String(
|
const errorMessage = String(
|
||||||
@@ -307,12 +293,22 @@
|
|||||||
|
|
||||||
renderRanking(payload.data || {});
|
renderRanking(payload.data || {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error?.name === "AbortError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (requestId !== currentRequestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.warn("Ranking request failed", error);
|
console.warn("Ranking request failed", error);
|
||||||
isBackendOnline = false;
|
|
||||||
setRankingState("error", "Error controlado al cargar el ranking.");
|
setRankingState("error", "Error controlado al cargar el ranking.");
|
||||||
renderEmptyState(
|
renderEmptyState(
|
||||||
"La lectura del ranking fall\u00f3 en este intento. Revisa el backend o actualiza la p\u00e1gina.",
|
"La lectura del ranking fall\u00f3 en este intento. Revisa el backend o vuelve a intentarlo.",
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
if (requestId === currentRequestId) {
|
||||||
|
setFormBusy(false);
|
||||||
|
activeController = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +317,7 @@
|
|||||||
if (statusCode === 400 && normalizedMessage.includes("limit")) {
|
if (statusCode === 400 && normalizedMessage.includes("limit")) {
|
||||||
setRankingState("warning", "El limite solicitado no es valido.");
|
setRankingState("warning", "El limite solicitado no es valido.");
|
||||||
renderEmptyState(
|
renderEmptyState(
|
||||||
"Usa un limite permitido por la interfaz o por el backend. Esta vista admite Top 5, 10, 20, 50 y 100.",
|
"Usa un limite permitido por la interfaz o por el backend. Esta vista admite Top 5, 10, 20, 30, 50 y 100.",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -350,7 +346,7 @@
|
|||||||
}
|
}
|
||||||
if (statusCode === 400 && normalizedMessage.includes("timeframe")) {
|
if (statusCode === 400 && normalizedMessage.includes("timeframe")) {
|
||||||
setRankingState("warning", "El periodo solicitado no esta soportado.");
|
setRankingState("warning", "El periodo solicitado no esta soportado.");
|
||||||
renderEmptyState("Usa una ventana semanal, mensual o anual.");
|
renderEmptyState("Usa un periodo semanal, mensual o anual.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setRankingState(
|
setRankingState(
|
||||||
@@ -402,7 +398,7 @@
|
|||||||
renderEmptyState(
|
renderEmptyState(
|
||||||
timeframe === "annual"
|
timeframe === "annual"
|
||||||
? "La lectura anual existe pero no devolvio filas para este filtro."
|
? "La lectura anual existe pero no devolvio filas para este filtro."
|
||||||
: "No se encontraron jugadores con actividad suficiente en esta ventana.",
|
: "No se encontraron jugadores con actividad suficiente en este periodo.",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -433,7 +429,7 @@
|
|||||||
{ label: "Servidor activo", value: labelForServer(data.server_id), active: true },
|
{ label: "Servidor activo", value: labelForServer(data.server_id), active: true },
|
||||||
{ label: "M\u00e9trica activa", value: labelForMetric(metric), active: true },
|
{ label: "M\u00e9trica activa", value: labelForMetric(metric), active: true },
|
||||||
{ label: "L\u00edmite", value: `Top ${safeInt(data.limit, safeInt(defaultLimit, 20))}` },
|
{ label: "L\u00edmite", value: `Top ${safeInt(data.limit, safeInt(defaultLimit, 20))}` },
|
||||||
{ label: "Ventana", value: labelForWindow(data) },
|
{ label: "Periodo", value: labelForWindow(data) },
|
||||||
{ label: "Actualizado", value: formatDateTime(source.generated_at) },
|
{ label: "Actualizado", value: formatDateTime(source.generated_at) },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -501,7 +497,7 @@
|
|||||||
if (String(data.timeframe || "") === "annual") {
|
if (String(data.timeframe || "") === "annual") {
|
||||||
return `A\u00f1o ${safeInt(data.year, currentYear)}`;
|
return `A\u00f1o ${safeInt(data.year, currentYear)}`;
|
||||||
}
|
}
|
||||||
return String(data.window_label || data.window_kind || "Ventana activa");
|
return String(data.window_label || data.window_kind || "Periodo activo");
|
||||||
}
|
}
|
||||||
|
|
||||||
function labelForServer(serverId) {
|
function labelForServer(serverId) {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
<span class="hero__title-accent">Global</span>
|
<span class="hero__title-accent">Global</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="hero__text">
|
<p class="hero__text">
|
||||||
Consulta los líderes públicos de la comunidad, cambia de periodo y servidor sin salir de la página y revisa quién destaca en cada ventana.
|
Consulta los líderes públicos de la comunidad, cambia de periodo y servidor sin salir de la página y revisa quién destaca en cada periodo.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,10 +59,10 @@
|
|||||||
<div class="panel__shell">
|
<div class="panel__shell">
|
||||||
<div class="panel__header">
|
<div class="panel__header">
|
||||||
<p class="eyebrow eyebrow--section">Filtros de lectura</p>
|
<p class="eyebrow eyebrow--section">Filtros de lectura</p>
|
||||||
<h2>Top público por ventana</h2>
|
<h2>Top público por periodo</h2>
|
||||||
</div>
|
</div>
|
||||||
<p class="panel__intro panel__intro--tight">
|
<p class="panel__intro panel__intro--tight">
|
||||||
El ranking expone la lectura pública de líderes. Para búsqueda individual usa Estadísticas.
|
El ranking expone los resultados de los líderes. Para búsqueda individual usa Estadísticas.
|
||||||
</p>
|
</p>
|
||||||
<form class="ranking-form" id="ranking-form">
|
<form class="ranking-form" id="ranking-form">
|
||||||
<div class="ranking-form__grid">
|
<div class="ranking-form__grid">
|
||||||
@@ -99,6 +99,7 @@
|
|||||||
<option value="5">Top 5</option>
|
<option value="5">Top 5</option>
|
||||||
<option value="10">Top 10</option>
|
<option value="10">Top 10</option>
|
||||||
<option value="20" selected>Top 20</option>
|
<option value="20" selected>Top 20</option>
|
||||||
|
<option value="30">Top 30</option>
|
||||||
<option value="50">Top 50</option>
|
<option value="50">Top 50</option>
|
||||||
<option value="100">Top 100</option>
|
<option value="100">Top 100</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -121,24 +122,16 @@
|
|||||||
Consulta líderes públicos y cambia de periodo o servidor sin salir de la página.
|
Consulta líderes públicos y cambia de periodo o servidor sin salir de la página.
|
||||||
</p>
|
</p>
|
||||||
<div class="hero__actions ranking-form__actions">
|
<div class="hero__actions ranking-form__actions">
|
||||||
<button class="discord-button" type="submit">Actualizar ranking</button>
|
<button class="discord-button" id="ranking-submit" type="submit">Actualizar ranking</button>
|
||||||
<a class="secondary-button secondary-button--compact" href="./stats.html">
|
<a class="secondary-button secondary-button--compact" href="./stats.html">
|
||||||
Buscar jugador en Estadísticas
|
Buscar jugador en Estadísticas
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p class="stats-state stats-state--neutral" id="ranking-state" role="status" aria-live="polite">
|
<p class="stats-state stats-state--neutral" id="ranking-state" role="status" aria-live="polite">
|
||||||
Esperando filtros para cargar el ranking global.
|
Preparando ranking público...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<h3 class="ranking-results-title" id="ranking-title">Tabla activa</h3>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="panel">
|
|
||||||
<div class="panel__shell">
|
|
||||||
<div class="panel__header">
|
|
||||||
<p class="eyebrow eyebrow--section">Resultados</p>
|
|
||||||
<h2 id="ranking-title">Tabla activa</h2>
|
|
||||||
</div>
|
|
||||||
<div class="ranking-meta" id="ranking-meta"></div>
|
<div class="ranking-meta" id="ranking-meta"></div>
|
||||||
<div class="ranking-table-shell">
|
<div class="ranking-table-shell">
|
||||||
<table class="historical-table ranking-table" id="ranking-table" hidden>
|
<table class="historical-table ranking-table" id="ranking-table" hidden>
|
||||||
|
|||||||
Reference in New Issue
Block a user