Fix historical aggregate snapshots and empty states
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# TASK-055-fix-all-servers-historical-aggregate
|
||||
|
||||
## Goal
|
||||
Corregir el agregado histórico `all-servers` / `Totales / Todos` para que resumen, tops semanales y partidas recientes reflejen realmente la suma o agregación de los servidores con histórico disponible, en vez de devolver snapshots vacíos.
|
||||
|
||||
## Context
|
||||
La situación actual del histórico es esta:
|
||||
- `comunidad-hispana-01` muestra datos correctos
|
||||
- `comunidad-hispana-02` muestra datos correctos
|
||||
- `Totales / Todos` aparece vacío
|
||||
- `comunidad-hispana-03` todavía no debe considerarse error, porque no se ha ejecutado bootstrap para ese servidor
|
||||
|
||||
Esto indica un fallo específico en la generación o persistencia del agregado lógico `all-servers`, no un problema general de la capa histórica.
|
||||
|
||||
## Steps
|
||||
1. Revisar la implementación actual del agregado lógico `all-servers` en:
|
||||
- snapshots
|
||||
- payloads
|
||||
- rutas
|
||||
- generación/prewarm
|
||||
2. Verificar cómo se construyen actualmente:
|
||||
- resumen global
|
||||
- weekly leaderboards globales
|
||||
- recent matches globales
|
||||
3. Corregir la lógica para que `all-servers` agregue correctamente los servidores con histórico disponible.
|
||||
4. Asegurar que el agregado:
|
||||
- incluya `comunidad-hispana-01`
|
||||
- incluya `comunidad-hispana-02`
|
||||
- no dependa de que `comunidad-hispana-03` ya tenga histórico
|
||||
- no colapse a vacío si uno de los servidores no tiene datos aún
|
||||
5. Verificar que los snapshots globales se generen con datos reales y no se sobrescriban con vacíos.
|
||||
6. Regenerar los snapshots necesarios de `all-servers`.
|
||||
7. Validar que en la UI:
|
||||
- `Totales / Todos` deje de mostrar 0 partidas / 0 jugadores
|
||||
- los tops globales ya tengan datos
|
||||
- recent matches globales ya tengan contenido si existe histórico en #01 y #02
|
||||
8. Documentar brevemente la semántica final del agregado global.
|
||||
9. No tratar todavía `#03` como parte obligatoria del agregado si no hay bootstrap para ese servidor.
|
||||
10. Al completar la implementación:
|
||||
- dejar el repositorio consistente
|
||||
- hacer commit
|
||||
- hacer push al remoto si el entorno lo permite
|
||||
|
||||
## Files to Read First
|
||||
- AGENTS.md
|
||||
- backend/README.md
|
||||
- backend/app/historical_storage.py
|
||||
- backend/app/historical_snapshots.py
|
||||
- backend/app/historical_snapshot_storage.py
|
||||
- backend/app/payloads.py
|
||||
- backend/app/routes.py
|
||||
- frontend/assets/js/historico.js
|
||||
- frontend/historico.html
|
||||
|
||||
## Expected Files to Modify
|
||||
- backend/app/historical_storage.py
|
||||
- backend/app/historical_snapshots.py
|
||||
- backend/app/historical_snapshot_storage.py
|
||||
- backend/app/payloads.py
|
||||
- backend/app/routes.py
|
||||
- backend/README.md
|
||||
- opcionalmente frontend/assets/js/historico.js si hace falta ajustar cómo se interpreta el agregado global
|
||||
|
||||
## Constraints
|
||||
- No romper #01 ni #02.
|
||||
- No exigir histórico de #03 para que `all-servers` funcione.
|
||||
- No crear páginas nuevas.
|
||||
- No hacer cambios destructivos.
|
||||
- Mantener el trabajo centrado en arreglar el agregado global.
|
||||
|
||||
## Validation
|
||||
- `Totales / Todos` deja de estar vacío.
|
||||
- El resumen global tiene partidas, jugadores y mapas si #01 y #02 tienen histórico.
|
||||
- Los tops globales funcionan.
|
||||
- Las partidas recientes globales funcionan.
|
||||
- Los cambios quedan committeados y se hace push al remoto si el entorno lo permite.
|
||||
|
||||
## Change Budget
|
||||
- Preferir menos de 6 archivos modificados o creados.
|
||||
- Preferir menos de 240 líneas cambiadas.
|
||||
@@ -0,0 +1,73 @@
|
||||
# TASK-056-historical-empty-state-and-copy-polish
|
||||
|
||||
## Goal
|
||||
Mejorar los estados vacíos y el copy de la página histórica para que `comunidad-hispana-03` se muestre de forma honesta mientras no tenga bootstrap ejecutado, y para que la información de cobertura y fallback semanal suene más natural y menos técnica.
|
||||
|
||||
## Context
|
||||
Tras los últimos cambios, la página histórica ya funciona para #01 y #02, pero aún hay aspectos de UX/copy mejorables:
|
||||
- `comunidad-hispana-03` aparece vacía, lo cual ahora mismo es normal porque no se ha ejecutado bootstrap para ese servidor
|
||||
- algunos textos siguen sonando demasiado técnicos
|
||||
- labels como `672,1 días registrados` no son la mejor forma de presentar cobertura histórica
|
||||
- el texto del fallback semanal es correcto, pero demasiado largo y técnico
|
||||
|
||||
## Steps
|
||||
1. Revisar los textos actuales de:
|
||||
- resumen
|
||||
- badges de cobertura
|
||||
- periodo registrado
|
||||
- fallback semanal
|
||||
- estados vacíos de resumen / tops / recientes
|
||||
2. Hacer que `comunidad-hispana-03` muestre un estado explícito y honesto del tipo:
|
||||
- sin histórico registrado todavía
|
||||
- pendiente de bootstrap / pendiente de registro histórico
|
||||
- o equivalente mejor, siempre claro y natural
|
||||
3. Evitar que `#03` parezca un error roto si simplemente aún no se ha cargado histórico.
|
||||
4. Revisar la forma de mostrar la cobertura histórica. Priorizar formulaciones más naturales como:
|
||||
- cobertura histórica
|
||||
- desde X hasta Y
|
||||
- periodo registrado
|
||||
sobre expresiones demasiado técnicas o poco naturales como el número decimal de días aislado.
|
||||
5. Simplificar el texto del fallback semanal para que comunique:
|
||||
- que se está mostrando la última semana cerrada
|
||||
- porque la semana actual aún no tiene suficiente actividad
|
||||
sin meter demasiado detalle técnico en el bloque principal
|
||||
6. Mantener la estética actual de la página histórica.
|
||||
7. No cambiar la lógica de negocio más allá de lo necesario para mostrar estados/copy correctos.
|
||||
8. No crear páginas nuevas.
|
||||
9. Al completar la implementación:
|
||||
- dejar el repositorio consistente
|
||||
- hacer commit
|
||||
- hacer push al remoto si el entorno lo permite
|
||||
|
||||
## Files to Read First
|
||||
- AGENTS.md
|
||||
- frontend/historico.html
|
||||
- frontend/assets/js/historico.js
|
||||
- frontend/assets/css/historico.css
|
||||
- backend/app/payloads.py
|
||||
- backend/README.md
|
||||
|
||||
## Expected Files to Modify
|
||||
- frontend/historico.html
|
||||
- frontend/assets/js/historico.js
|
||||
- frontend/assets/css/historico.css
|
||||
- opcionalmente backend/app/payloads.py si hace falta ajustar labels o metadatos presentables
|
||||
- opcionalmente backend/README.md si el comportamiento visible cambia y conviene dejarlo documentado
|
||||
|
||||
## Constraints
|
||||
- No romper la UI histórica que ya funciona para #01 y #02.
|
||||
- No convertir esta task en un rediseño grande.
|
||||
- No introducir frameworks nuevos.
|
||||
- No hacer cambios destructivos.
|
||||
- Mantener el trabajo centrado en estados vacíos y copy.
|
||||
|
||||
## Validation
|
||||
- `#03` deja de parecer un fallo ambiguo y muestra un estado vacío claro y honesto.
|
||||
- La cobertura histórica se presenta de forma más natural.
|
||||
- El texto del fallback semanal se entiende mejor.
|
||||
- La UI sigue siendo coherente con la landing y con el resto de la página histórica.
|
||||
- Los cambios quedan committeados y se hace push al remoto si el entorno lo permite.
|
||||
|
||||
## Change Budget
|
||||
- Preferir menos de 5 archivos modificados.
|
||||
- Preferir menos de 220 líneas cambiadas.
|
||||
@@ -593,6 +593,9 @@ persistida por servidor para releer solo paginas recientes y absorber updates
|
||||
tardios sin reimportar todo el historico. Cuando una ejecucion termina
|
||||
correctamente, tambien recompone los snapshots historicos precalculados para el
|
||||
servidor afectado o para todos los servidores si la ingesta fue global.
|
||||
Si la recomposicion se lanza para un servidor fisico concreto, el backend
|
||||
rehace tambien el agregado logico `all-servers` para mantener `Todos`
|
||||
alineado con `#01` y `#02` aunque `#03` siga sin bootstrap.
|
||||
|
||||
El comando devuelve ademas un resumen de cobertura persistida por servidor. Esto
|
||||
ayuda a validar rapidamente cuantos matches reales quedaron importados, el rango
|
||||
|
||||
@@ -161,7 +161,7 @@ def build_all_historical_snapshots(
|
||||
db_path: Path | None = None,
|
||||
) -> list[dict[str, object]]:
|
||||
"""Build the full snapshot set for one server or for all configured servers."""
|
||||
target_server_keys = [server_key] if server_key else list_snapshot_server_keys(db_path=db_path)
|
||||
target_server_keys = _resolve_snapshot_target_keys(server_key=server_key, db_path=db_path)
|
||||
snapshots: list[dict[str, object]] = []
|
||||
for target_server_key in target_server_keys:
|
||||
snapshots.extend(
|
||||
@@ -340,6 +340,24 @@ def _build_recent_matches_snapshot(
|
||||
}
|
||||
|
||||
|
||||
def _resolve_snapshot_target_keys(
|
||||
*,
|
||||
server_key: str | None,
|
||||
db_path: Path | None = None,
|
||||
) -> list[str]:
|
||||
"""Expand targeted rebuilds so the logical global aggregate stays in sync."""
|
||||
if not server_key:
|
||||
return list_snapshot_server_keys(db_path=db_path)
|
||||
|
||||
normalized_server_key = server_key.strip()
|
||||
if not normalized_server_key:
|
||||
return list_snapshot_server_keys(db_path=db_path)
|
||||
if normalized_server_key == ALL_SERVERS_SLUG:
|
||||
return [ALL_SERVERS_SLUG]
|
||||
|
||||
return [normalized_server_key, ALL_SERVERS_SLUG]
|
||||
|
||||
|
||||
def _parse_optional_timestamp(value: object) -> datetime | None:
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
return None
|
||||
|
||||
@@ -370,6 +370,7 @@ function isActiveLeaderboardRequest(
|
||||
}
|
||||
|
||||
function hydrateSummary(result, summaryNode, rangeNode, noteNode, snapshotMetaNode) {
|
||||
const emptyState = getHistoricalEmptyState(activeServerSlug);
|
||||
if (result.status !== "fulfilled") {
|
||||
renderSummaryError(summaryNode);
|
||||
setRangeBadge(rangeNode, "Snapshot de resumen no disponible", false);
|
||||
@@ -381,12 +382,18 @@ function hydrateSummary(result, summaryNode, rangeNode, noteNode, snapshotMetaNo
|
||||
|
||||
const payload = result.value?.data;
|
||||
const summary = payload?.item;
|
||||
if (!payload?.found || !summary) {
|
||||
renderSummaryEmpty(summaryNode);
|
||||
setRangeBadge(rangeNode, "Sin snapshot de resumen", false);
|
||||
noteNode.textContent =
|
||||
"Todavia no existe un snapshot de resumen listo para el alcance seleccionado.";
|
||||
setSnapshotMeta(snapshotMetaNode, "Snapshot de resumen pendiente de generacion.");
|
||||
const hasHistoricalData =
|
||||
Number(summary?.imported_matches_count ?? summary?.matches_count ?? 0) > 0;
|
||||
if (!payload?.found || !summary || !hasHistoricalData) {
|
||||
renderSummaryEmpty(summaryNode, emptyState.summaryMessage);
|
||||
setRangeBadge(rangeNode, emptyState.rangeLabel, false);
|
||||
noteNode.textContent = emptyState.summaryNote;
|
||||
setSnapshotMeta(
|
||||
snapshotMetaNode,
|
||||
payload?.generated_at
|
||||
? buildSnapshotMetaText(payload, "Snapshot de resumen pendiente de generacion.")
|
||||
: "Snapshot de resumen pendiente de generacion.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -395,10 +402,10 @@ function hydrateSummary(result, summaryNode, rangeNode, noteNode, snapshotMetaNo
|
||||
const rangeLabel = buildCoverageBadgeLabel(coverage, {
|
||||
start: payload?.source_range_start || timeRange.start,
|
||||
end: payload?.source_range_end || timeRange.end,
|
||||
});
|
||||
}, summary.server?.slug);
|
||||
setRangeBadge(
|
||||
rangeNode,
|
||||
rangeLabel || "Cobertura registrada parcial",
|
||||
rangeLabel || "Cobertura historica disponible",
|
||||
coverage.status === "week-plus" && !payload?.is_stale,
|
||||
);
|
||||
noteNode.textContent = buildSummaryNote(
|
||||
@@ -419,11 +426,11 @@ function hydrateSummary(result, summaryNode, rangeNode, noteNode, snapshotMetaNo
|
||||
),
|
||||
renderSummaryCard("Jugadores unicos", formatNumber(summary.unique_players)),
|
||||
renderSummaryCard(
|
||||
"Periodo registrado",
|
||||
formatCoverageDays(coverage.coverage_days),
|
||||
"Cobertura historica",
|
||||
buildCoveragePeriodLabel(coverage, timeRange, summary.server?.slug),
|
||||
),
|
||||
renderSummaryCard("Primera partida", formatTimestamp(coverage.first_match_at)),
|
||||
renderSummaryCard("Ultima partida", formatTimestamp(coverage.last_match_at)),
|
||||
renderSummaryCard("Inicio de registro", formatTimestamp(coverage.first_match_at)),
|
||||
renderSummaryCard("Ultimo cierre", formatTimestamp(coverage.last_match_at)),
|
||||
renderSummaryCard(
|
||||
"Mapas frecuentes",
|
||||
formatTopMaps(summary.top_maps),
|
||||
@@ -442,6 +449,7 @@ function hydrateWeeklyLeaderboard(
|
||||
snapshotMetaNode,
|
||||
metricConfig,
|
||||
) {
|
||||
const targetServerSlug = result.value?.data?.server_slug || activeServerSlug;
|
||||
valueHeadingNode.textContent = metricConfig.valueHeading;
|
||||
if (result.status !== "fulfilled") {
|
||||
titleNode.textContent = metricConfig.title;
|
||||
@@ -461,14 +469,14 @@ function hydrateWeeklyLeaderboard(
|
||||
buildSnapshotMetaText(payload, "Snapshot semanal pendiente de generacion."),
|
||||
);
|
||||
if (!payload?.found) {
|
||||
setState(stateNode, metricConfig.emptyMessage);
|
||||
setState(stateNode, buildLeaderboardEmptyMessage(metricConfig, targetServerSlug));
|
||||
tableNode.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const items = payload?.items;
|
||||
if (!Array.isArray(items) || items.length === 0) {
|
||||
setState(stateNode, metricConfig.emptyMessage);
|
||||
setState(stateNode, buildLeaderboardEmptyMessage(metricConfig, targetServerSlug));
|
||||
tableNode.hidden = true;
|
||||
return;
|
||||
}
|
||||
@@ -490,6 +498,7 @@ function hydrateWeeklyLeaderboard(
|
||||
}
|
||||
|
||||
function hydrateRecentMatches(result, stateNode, listNode, snapshotMetaNode) {
|
||||
const emptyState = getHistoricalEmptyState(activeServerSlug);
|
||||
if (result.status !== "fulfilled") {
|
||||
setState(stateNode, "No se pudieron cargar las partidas recientes.", true);
|
||||
setSnapshotMeta(snapshotMetaNode, "Error al leer el snapshot de partidas.");
|
||||
@@ -502,13 +511,13 @@ function hydrateRecentMatches(result, stateNode, listNode, snapshotMetaNode) {
|
||||
buildSnapshotMetaText(payload, "Snapshot de partidas pendiente de generacion."),
|
||||
);
|
||||
if (!payload?.found) {
|
||||
setState(stateNode, "Todavia no hay snapshot de partidas recientes disponible.");
|
||||
setState(stateNode, emptyState.recentMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const items = payload?.items;
|
||||
if (!Array.isArray(items) || items.length === 0) {
|
||||
setState(stateNode, "Todavia no hay partidas recientes disponibles.");
|
||||
setState(stateNode, emptyState.recentMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -557,8 +566,8 @@ function renderSummaryError(summaryNode) {
|
||||
summaryNode.innerHTML = renderSummaryCard("Estado", "Error al cargar el resumen");
|
||||
}
|
||||
|
||||
function renderSummaryEmpty(summaryNode) {
|
||||
summaryNode.innerHTML = renderSummaryCard("Estado", "Sin datos historicos suficientes");
|
||||
function renderSummaryEmpty(summaryNode, message = "Sin datos historicos suficientes") {
|
||||
summaryNode.innerHTML = renderSummaryCard("Estado", message);
|
||||
}
|
||||
|
||||
function renderSummaryCard(label, value) {
|
||||
@@ -655,16 +664,39 @@ function buildRangeLabel(start, end) {
|
||||
return `${formatTimestamp(start)} a ${formatTimestamp(end)}`;
|
||||
}
|
||||
|
||||
function buildCoverageBadgeLabel(coverage, timeRange) {
|
||||
const coverageDays = formatCoverageDays(coverage?.coverage_days);
|
||||
const rangeLabel = buildRangeLabel(
|
||||
coverage?.first_match_at || timeRange?.start,
|
||||
coverage?.last_match_at || timeRange?.end,
|
||||
);
|
||||
if (coverageDays !== "Cobertura no disponible" && rangeLabel) {
|
||||
return `${coverageDays} registrados`;
|
||||
function buildCoverageBadgeLabel(coverage, timeRange, serverSlug) {
|
||||
const rangeStart = coverage?.first_match_at || timeRange?.start;
|
||||
const rangeEnd = coverage?.last_match_at || timeRange?.end;
|
||||
if (!rangeStart && !rangeEnd) {
|
||||
return serverSlug === "comunidad-hispana-03"
|
||||
? "Pendiente de historico"
|
||||
: "Sin cobertura registrada";
|
||||
}
|
||||
return rangeLabel;
|
||||
if (coverage?.status === "under-week") {
|
||||
return "Cobertura inicial";
|
||||
}
|
||||
if (coverage?.status === "week-plus") {
|
||||
return "Cobertura historica";
|
||||
}
|
||||
return "Periodo registrado";
|
||||
}
|
||||
|
||||
function buildCoveragePeriodLabel(coverage, timeRange, serverSlug) {
|
||||
const start = coverage?.first_match_at || timeRange?.start;
|
||||
const end = coverage?.last_match_at || timeRange?.end;
|
||||
if (start && end) {
|
||||
return `Desde ${formatDateOnly(start)} hasta ${formatDateOnly(end)}`;
|
||||
}
|
||||
if (start) {
|
||||
return `Desde ${formatDateOnly(start)}`;
|
||||
}
|
||||
if (end) {
|
||||
return `Hasta ${formatDateOnly(end)}`;
|
||||
}
|
||||
if (serverSlug === "comunidad-hispana-03") {
|
||||
return "Pendiente de bootstrap historico";
|
||||
}
|
||||
return "Sin cobertura registrada";
|
||||
}
|
||||
|
||||
function buildSummaryNote(summaryBasis, weeklyWindowDays, coverage, serverSlug) {
|
||||
@@ -672,21 +704,25 @@ function buildSummaryNote(summaryBasis, weeklyWindowDays, coverage, serverSlug)
|
||||
summaryBasis === "snapshot-precomputed"
|
||||
? "el snapshot precalculado del historico local"
|
||||
: "el historico persistido disponible";
|
||||
const weeklyWindowLabel = Number.isFinite(Number(weeklyWindowDays))
|
||||
? `${weeklyWindowDays} dias`
|
||||
: "la ultima semana";
|
||||
const status = coverage?.status;
|
||||
void weeklyWindowDays;
|
||||
if (serverSlug === "comunidad-hispana-03" && status === "empty") {
|
||||
return "Comunidad Hispana #03 todavia no tiene historico registrado. Este bloque se activara cuando se ejecute su primer bootstrap.";
|
||||
}
|
||||
if (status === "under-week") {
|
||||
return `Este bloque resume ${basisLabel}. Ahora mismo esa cobertura todavia no alcanza ${weeklyWindowLabel}.`;
|
||||
return `Este bloque resume ${basisLabel}. La cobertura registrada todavia es inicial y puede crecer en los proximos dias.`;
|
||||
}
|
||||
if (serverSlug === "all-servers") {
|
||||
return `Resumen global servido desde ${basisLabel}.`;
|
||||
return `Resumen global servido desde ${basisLabel} y combinado solo con los servidores que ya tienen historico disponible.`;
|
||||
}
|
||||
return `Resumen servido desde ${basisLabel}.`;
|
||||
}
|
||||
|
||||
function buildWeeklyWindowNote(payload) {
|
||||
if (!payload?.found) {
|
||||
if ((payload?.server_slug || activeServerSlug) === "comunidad-hispana-03") {
|
||||
return "El ranking semanal aparecera cuando Comunidad Hispana #03 tenga su primer bootstrap historico.";
|
||||
}
|
||||
return "No existe un snapshot semanal listo para esta metrica en el alcance activo.";
|
||||
}
|
||||
|
||||
@@ -694,24 +730,25 @@ function buildWeeklyWindowNote(payload) {
|
||||
const end = formatTimestamp(payload?.window_end);
|
||||
const windowLabel = payload?.window_label || "Semana activa";
|
||||
if (payload?.uses_fallback) {
|
||||
const minimumMatches =
|
||||
payload?.sufficient_sample?.minimum_closed_matches || payload?.minimum_closed_matches || 0;
|
||||
const currentWeekMatches =
|
||||
payload?.current_week_closed_matches ??
|
||||
payload?.sufficient_sample?.current_week_closed_matches ??
|
||||
0;
|
||||
return `${windowLabel}: ${start} a ${end}. Se muestra temporalmente porque la semana actual solo tiene ${formatNumber(currentWeekMatches)} cierres y el minimo operativo es ${formatNumber(minimumMatches)}.`;
|
||||
return `${windowLabel}: ${start} a ${end}. Se muestra la ultima semana cerrada porque la actual todavia solo suma ${formatNumber(currentWeekMatches)} cierres.`;
|
||||
}
|
||||
return `${windowLabel}: ${start} a ${end}.`;
|
||||
}
|
||||
|
||||
function buildLeaderboardTitle(metricConfig, serverSlug) {
|
||||
return `${metricConfig.title} · ${getHistoricalServerLabel(serverSlug)}`;
|
||||
return `${metricConfig.title} - ${getHistoricalServerLabel(serverSlug)}`;
|
||||
}
|
||||
|
||||
function buildRecentMatchesNote(serverSlug) {
|
||||
if (serverSlug === "all-servers") {
|
||||
return "Lista de cierres ya registrados para todos los servidores.";
|
||||
return "Lista de cierres ya registrados para los servidores con historico disponible.";
|
||||
}
|
||||
if (serverSlug === "comunidad-hispana-03") {
|
||||
return "Este bloque quedara activo cuando Comunidad Hispana #03 tenga su primer registro historico.";
|
||||
}
|
||||
return `Lista de cierres ya registrados para ${getHistoricalServerLabel(serverSlug)}.`;
|
||||
}
|
||||
@@ -746,15 +783,19 @@ function formatTopMaps(topMaps) {
|
||||
.join(" / ");
|
||||
}
|
||||
|
||||
function formatCoverageDays(value) {
|
||||
const parsedValue = Number(value);
|
||||
if (!Number.isFinite(parsedValue) || parsedValue <= 0) {
|
||||
return "Cobertura no disponible";
|
||||
function formatDateOnly(timestamp) {
|
||||
if (!timestamp) {
|
||||
return "Fecha no disponible";
|
||||
}
|
||||
return `${new Intl.NumberFormat("es-ES", {
|
||||
maximumFractionDigits: 1,
|
||||
minimumFractionDigits: parsedValue < 10 ? 1 : 0,
|
||||
}).format(parsedValue)} dias`;
|
||||
|
||||
const value = new Date(timestamp);
|
||||
if (Number.isNaN(value.getTime())) {
|
||||
return "Fecha no disponible";
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat("es-ES", {
|
||||
dateStyle: "medium",
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
function formatMatchResult(result) {
|
||||
@@ -893,3 +934,31 @@ function escapeHtml(value) {
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
function buildLeaderboardEmptyMessage(metricConfig, serverSlug) {
|
||||
if (serverSlug === "comunidad-hispana-03") {
|
||||
return "Comunidad Hispana #03 todavia no tiene historico registrado para mostrar este ranking.";
|
||||
}
|
||||
return metricConfig.emptyMessage;
|
||||
}
|
||||
|
||||
function getHistoricalEmptyState(serverSlug) {
|
||||
if (serverSlug === "comunidad-hispana-03") {
|
||||
return {
|
||||
rangeLabel: "Pendiente de historico",
|
||||
summaryMessage: "Sin historico registrado todavia",
|
||||
summaryNote:
|
||||
"Comunidad Hispana #03 sigue pendiente de bootstrap historico. Cuando se registren sus primeras partidas, aqui apareceran su cobertura y sus totales.",
|
||||
recentMessage:
|
||||
"Comunidad Hispana #03 todavia no tiene partidas historicas registradas.",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
rangeLabel: "Sin cobertura registrada",
|
||||
summaryMessage: "Sin datos historicos suficientes",
|
||||
summaryNote:
|
||||
"Todavia no existe un snapshot de resumen listo para el alcance seleccionado.",
|
||||
recentMessage: "Todavia no hay partidas recientes disponibles.",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user