Top ${limit} anual
${renderAnnualRows(items)}document.addEventListener("DOMContentLoaded", () => {
const backendBaseUrl = document.body.dataset.backendBaseUrl || "http://127.0.0.1:8000";
const searchForm = document.getElementById("stats-search-form");
const searchInput = document.getElementById("stats-search-input");
const searchStateNode = document.getElementById("stats-search-state");
const searchHelpNode = document.getElementById("stats-search-help");
const resultListNode = document.getElementById("stats-result-list");
const backendStateNode = document.getElementById("stats-backend-state");
const profilePanel = document.getElementById("stats-profile-panel");
const profileTitle = document.getElementById("stats-profile-title");
const profileStateNode = document.getElementById("stats-profile-state");
const summaryGrid = document.getElementById("stats-summary-grid");
const weeklySummaryNode = document.getElementById("stats-weekly-summary");
const monthlySummaryNode = document.getElementById("stats-monthly-summary");
const annualForm = document.getElementById("stats-annual-form");
const annualYearInput = document.getElementById("stats-annual-year");
const annualStateNode = document.getElementById("stats-annual-state");
const annualContentNode = document.getElementById("stats-annual-content");
const annualDefaultYear = new Date().getUTCFullYear();
const annualMetric = "kills";
const annualLimit = 20;
const annualServerId = "all";
let isBackendOnline = false;
setBackendState("Comprobando disponibilidad del backend", false);
if (annualYearInput) {
annualYearInput.value = String(annualDefaultYear);
}
refreshBackendHealth();
if (annualForm && annualYearInput) {
annualForm.addEventListener("submit", (event) => {
event.preventDefault();
void loadAnnualRanking();
});
}
if (searchForm && searchInput) {
searchForm.addEventListener("submit", (event) => {
event.preventDefault();
const query = (searchInput.value || "").trim();
if (!query) {
setSearchState("error", "Escribe un nombre o ID para buscar.");
return;
}
void searchPlayers(query);
});
}
function setBackendState(label, isOnline) {
isBackendOnline = isOnline;
if (!backendStateNode) {
return;
}
backendStateNode.textContent = label;
backendStateNode.classList.toggle("status-chip--ok", isOnline);
backendStateNode.classList.toggle("status-chip--fallback", !isOnline);
}
function markAsBackendUnavailable() {
setBackendState("Backend no disponible", false);
if (searchStateNode) {
searchStateNode.textContent = "Backend no disponible. Reintenta en unos segundos.";
searchStateNode.className = "stats-state stats-state--error";
}
setAnnualState("error", "Backend no disponible. No se puede cargar ranking anual.");
}
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") {
setBackendState("Backend operativo", true);
setAnnualState("loading", "Cargando ranking anual...");
void loadAnnualRanking();
return;
}
throw new Error("Unexpected health payload");
} catch (error) {
markAsBackendUnavailable();
console.warn("Backend health check failed", error);
}
}
async function searchPlayers(query) {
if (!resultListNode) {
return;
}
if (!isBackendOnline) {
markAsBackendUnavailable();
return;
}
setSearchState("loading", "Buscando jugadores...");
clearProfilePanel();
resultListNode.innerHTML = "";
try {
const searchResponse = await fetch(
`${backendBaseUrl}/api/stats/players/search?q=${encodeURIComponent(query)}`,
);
if (!searchResponse.ok) {
throw new Error(`Search request failed with ${searchResponse.status}`);
}
const payload = await searchResponse.json();
if (!payload || payload.status !== "ok") {
throw new Error(payload?.message || "Respuesta de búsqueda inválida");
}
const items = normalizeArray(payload.data?.items);
if (!items.length) {
setSearchState("empty", "Sin resultados.");
return;
}
setSearchState("ready", `Se encontraron ${items.length} jugadores.`);
resultListNode.innerHTML = items.map(renderResultItem).join("");
resultListNode.querySelectorAll("[data-player-id]").forEach((button) => {
button.addEventListener("click", () => {
const playerId = button.getAttribute("data-player-id");
if (playerId) {
void loadPlayerProfile(playerId);
}
});
});
} catch (error) {
console.warn("Player search failed", error);
markAsBackendUnavailable();
setSearchState("error", "Error al buscar. Verifica backend y reintenta.");
}
}
async function loadAnnualRanking() {
if (!annualForm || !annualYearInput || !annualContentNode) {
return;
}
if (!isBackendOnline) {
markAsBackendUnavailable();
return;
}
const year = resolveAnnualYear();
if (year === null) {
setAnnualState("error", "El ano ingresado no es valido.");
return;
}
setAnnualState("loading", "Cargando ranking anual...");
annualContentNode.innerHTML = "";
try {
const annualUrl =
`${backendBaseUrl}/api/stats/rankings/annual?` +
`year=${encodeURIComponent(year)}&` +
`server_id=${encodeURIComponent(annualServerId)}&` +
`metric=${encodeURIComponent(annualMetric)}&` +
`limit=${encodeURIComponent(annualLimit)}`;
const annualResponse = await fetch(annualUrl);
if (!annualResponse.ok) {
throw new Error(`Annual ranking request failed with ${annualResponse.status}`);
}
const payload = await annualResponse.json();
if (!payload || payload.status !== "ok") {
throw new Error(payload?.message || "Respuesta de ranking anual invalida");
}
renderAnnualRanking(payload.data || {});
} catch (error) {
console.warn("Annual ranking failed", error);
markAsBackendUnavailable();
setAnnualState("error", "Error al cargar ranking anual.");
annualContentNode.innerHTML = "";
}
}
function resolveAnnualYear() {
const normalized = Number.parseInt(String(annualYearInput?.value || "").trim(), 10);
if (!Number.isFinite(normalized) || normalized <= 0) {
return null;
}
return normalized;
}
function renderAnnualRanking(data) {
if (!annualContentNode || !annualStateNode) {
return;
}
const snapshotStatus = String(data.snapshot_status || "").toLowerCase();
const items = normalizeArray(data.items);
const limit = safeInt(data.limit, 0);
const serverId = String(data.server_id || annualServerId);
const sourceMatches = safeInt(data.source_matches_count, 0);
const generatedAt = formatDateTime(data.generated_at);
if (snapshotStatus !== "ready" || !items.length) {
const isReadyButEmpty = snapshotStatus === "ready" && !items.length;
setAnnualState(
"neutral",
isReadyButEmpty
? "Ranking anual listo pero sin resultados para el ano seleccionado."
: "No hay ranking anual generado para el ano seleccionado.",
);
if (!items.length) {
annualContentNode.innerHTML = "";
}
return;
}
setAnnualState(
"neutral",
`Ranking anual listo para ${serverId}. Generado: ${generatedAt || "sin marca de tiempo"}.`,
);
annualContentNode.innerHTML = `
Top ${limit} anual #${rank} ${playerName} (ID: ${playerId}) Valor: ${metricValue} · Partidas: ${matches} Kills: ${kills} · Deaths: ${deaths} · Teamkills: ${teamkills} · K/D: ${kd} Identidad Jugador: ${escapeHtml(playerName)} ID: ${escapeHtml(playerIdText)} Partidas: ${safeInt(data.matches_considered, 0)} Totales Kills: ${safeInt(data.kills, 0)} Deaths: ${safeInt(data.deaths, 0)} Teamkills: ${safeInt(data.teamkills, 0)} Ratios K/D: ${safeDecimal(data.kd_ratio, 2, "0.00")} Kills/partida: ${safeDecimal(data.kills_per_match, 2, "0.00")} Deaths/partida: ${safeDecimal(data.deaths_per_match, 2, "0.00")}
Posición: ${rank}
Metric: ${metric}
${windowRange}
`; } function safeInt(value, fallback = 0) { const parsed = Number(value); if (!Number.isFinite(parsed)) { return fallback; } return parsed; } function safeDecimal(value, maximumFractionDigits, fallback = "0") { const parsed = Number(value); if (!Number.isFinite(parsed)) { return fallback; } return parsed.toLocaleString("es-ES", { minimumFractionDigits: maximumFractionDigits, maximumFractionDigits, }); } function safeParseNumber(value) { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : Number.NaN; } function formatDateTime(value) { if (!value) { return "No disponible"; } const parsedDate = new Date(String(value)); if (Number.isNaN(parsedDate.getTime())) { return "No disponible"; } return new Intl.DateTimeFormat("es-ES", { dateStyle: "short", timeStyle: "short", }).format(parsedDate); } function normalizeArray(items) { return Array.isArray(items) ? items : []; } function escapeHtml(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } });