const HISTORICAL_SERVER_SLUGS = Object.freeze([ "comunidad-hispana-01", "comunidad-hispana-02", ]); const DEFAULT_HISTORICAL_SERVER = HISTORICAL_SERVER_SLUGS[0]; const LEADERBOARD_METRICS = Object.freeze([ { key: "kills", title: "Top kills de los ultimos 7 dias", valueHeading: "Kills", emptyMessage: "Sin datos historicos suficientes para mostrar el top de kills semanal.", }, { key: "deaths", title: "Top muertes de los ultimos 7 dias", valueHeading: "Muertes", emptyMessage: "Sin datos historicos suficientes para mostrar el top de muertes semanal.", }, { key: "matches_over_100_kills", title: "Top partidas con 100+ kills", valueHeading: "Partidas 100+", emptyMessage: "Ningun jugador ha registrado partidas de 100+ kills en esta ventana semanal.", }, { key: "support", title: "Top puntos de soporte de los ultimos 7 dias", valueHeading: "Soporte", emptyMessage: "Sin datos historicos suficientes para mostrar el top de soporte semanal.", }, ]); const DEFAULT_LEADERBOARD_METRIC = LEADERBOARD_METRICS[0].key; document.addEventListener("DOMContentLoaded", () => { const backendBaseUrl = document.body.dataset.backendBaseUrl || "http://127.0.0.1:8000"; const selectorButtons = Array.from( document.querySelectorAll("[data-server-slug]"), ); const leaderboardTabButtons = Array.from( document.querySelectorAll("[data-leaderboard-metric]"), ); const summaryNode = document.getElementById("historical-summary"); const rangeNode = document.getElementById("historical-range"); const summaryNoteNode = document.getElementById("historical-summary-note"); const weeklyTitleNode = document.getElementById("weekly-ranking-title"); const weeklyStateNode = document.getElementById("weekly-leaderboard-state"); const weeklyTableNode = document.getElementById("weekly-leaderboard-table"); const weeklyBodyNode = document.getElementById("weekly-leaderboard-body"); const weeklyValueHeadingNode = document.getElementById("weekly-leaderboard-value-heading"); const weeklyWindowNoteNode = document.getElementById("weekly-window-note"); const recentStateNode = document.getElementById("recent-matches-state"); const recentListNode = document.getElementById("recent-matches-list"); const params = new URLSearchParams(window.location.search); let activeServerSlug = normalizeServerSlug(params.get("server")); let activeLeaderboardMetric = normalizeLeaderboardMetric(params.get("metric")); let refreshRequestId = 0; const refreshHistoricalView = async () => { const requestId = refreshRequestId + 1; refreshRequestId = requestId; const activeMetricConfig = getLeaderboardMetricConfig(activeLeaderboardMetric); syncActiveButtons(selectorButtons, activeServerSlug); syncLeaderboardTabs(leaderboardTabButtons, activeLeaderboardMetric); setRangeBadge(rangeNode, "Cargando rango temporal", false); summaryNoteNode.textContent = "Este bloque resume solo la cobertura ya registrada en la base local."; renderSummaryLoading(summaryNode); weeklyTitleNode.textContent = activeMetricConfig.title; weeklyValueHeadingNode.textContent = activeMetricConfig.valueHeading; weeklyWindowNoteNode.textContent = "Cargando ventana semanal..."; setState(weeklyStateNode, "Cargando ranking semanal..."); setState(recentStateNode, "Cargando partidas recientes..."); weeklyTableNode.hidden = true; recentListNode.innerHTML = ""; const [summaryResult, leaderboardResult, recentMatchesResult] = await Promise.allSettled([ fetchJson( `${backendBaseUrl}/api/historical/server-summary?server=${encodeURIComponent(activeServerSlug)}`, ), fetchJson( `${backendBaseUrl}/api/historical/weekly-leaderboard?server=${encodeURIComponent(activeServerSlug)}&metric=${encodeURIComponent(activeLeaderboardMetric)}&limit=10`, ), fetchJson( `${backendBaseUrl}/api/historical/recent-matches?server=${encodeURIComponent(activeServerSlug)}&limit=6`, ), ]); if (requestId !== refreshRequestId) { return; } hydrateSummary(summaryResult, summaryNode, rangeNode, summaryNoteNode); hydrateWeeklyLeaderboard( leaderboardResult, weeklyStateNode, weeklyTableNode, weeklyBodyNode, weeklyTitleNode, weeklyValueHeadingNode, weeklyWindowNoteNode, activeMetricConfig, ); hydrateRecentMatches(recentMatchesResult, recentStateNode, recentListNode); }; selectorButtons.forEach((button) => { button.addEventListener("click", () => { const nextServerSlug = normalizeServerSlug(button.dataset.serverSlug); if (nextServerSlug === activeServerSlug) { return; } activeServerSlug = nextServerSlug; params.set("server", activeServerSlug); params.set("metric", activeLeaderboardMetric); window.history.replaceState({}, "", `?${params.toString()}`); void refreshHistoricalView(); }); }); leaderboardTabButtons.forEach((button) => { button.addEventListener("click", () => { const nextMetric = normalizeLeaderboardMetric(button.dataset.leaderboardMetric); if (nextMetric === activeLeaderboardMetric) { return; } activeLeaderboardMetric = nextMetric; params.set("server", activeServerSlug); params.set("metric", activeLeaderboardMetric); window.history.replaceState({}, "", `?${params.toString()}`); void refreshHistoricalView(); }); }); void refreshHistoricalView(); }); function hydrateSummary(result, summaryNode, rangeNode, noteNode) { if (result.status !== "fulfilled") { renderSummaryError(summaryNode); setRangeBadge(rangeNode, "Resumen historico no disponible", false); noteNode.textContent = "No se pudo leer la cobertura historica registrada para este servidor."; return; } const payload = result.value?.data; const items = payload?.items; if (!Array.isArray(items) || items.length === 0) { renderSummaryEmpty(summaryNode); setRangeBadge(rangeNode, "Sin cobertura historica", false); noteNode.textContent = "Todavia no existe cobertura historica suficiente en la base local."; return; } const summary = items[0]; const coverage = summary.coverage || {}; const timeRange = summary.time_range || {}; const rangeLabel = buildCoverageBadgeLabel(coverage, timeRange); setRangeBadge( rangeNode, rangeLabel || "Cobertura registrada parcial", coverage.status === "week-plus", ); noteNode.textContent = buildSummaryNote( payload?.summary_basis, payload?.weekly_ranking_window_days, coverage, ); summaryNode.innerHTML = [ renderSummaryCard("Servidor", summary.server?.name || "Servidor no disponible"), renderSummaryCard( "Partidas registradas", formatNumber(summary.imported_matches_count ?? summary.matches_count), ), renderSummaryCard("Jugadores unicos", formatNumber(summary.unique_players)), renderSummaryCard( "Cobertura registrada", formatCoverageDays(coverage.coverage_days), ), renderSummaryCard("Primera partida", formatTimestamp(coverage.first_match_at)), renderSummaryCard("Ultima partida", formatTimestamp(coverage.last_match_at)), renderSummaryCard( "Mapas dominantes", formatTopMaps(summary.top_maps), ), ].join(""); } function hydrateWeeklyLeaderboard( result, stateNode, tableNode, bodyNode, titleNode, valueHeadingNode, noteNode, metricConfig, ) { titleNode.textContent = metricConfig.title; valueHeadingNode.textContent = metricConfig.valueHeading; if (result.status !== "fulfilled") { noteNode.textContent = "El ranking usa solo partidas cerradas dentro de la ultima ventana semanal."; setState(stateNode, "No se pudo cargar el ranking semanal.", true); tableNode.hidden = true; return; } const payload = result.value?.data; noteNode.textContent = buildWeeklyWindowNote(payload); const items = payload?.items; if (!Array.isArray(items) || items.length === 0) { setState(stateNode, metricConfig.emptyMessage); tableNode.hidden = true; return; } bodyNode.innerHTML = items .map( (item) => `
${escapeHtml(label)}
${escapeHtml(value)}