${escapeHtml(label)}
${escapeHtml(value)}document.addEventListener("DOMContentLoaded", () => { const backendBaseUrl = document.body.dataset.backendBaseUrl || "http://127.0.0.1:8000"; const params = new URLSearchParams(window.location.search); const serverSlug = params.get("server") || ""; const matchId = params.get("match") || ""; const titleNode = document.getElementById("match-detail-title"); const summaryNode = document.getElementById("match-detail-summary"); const noteNode = document.getElementById("match-detail-note"); const stateNode = document.getElementById("match-detail-state"); const gridNode = document.getElementById("match-detail-grid"); const actionsNode = document.getElementById("match-detail-actions"); const playersSectionNode = document.getElementById("match-detail-players-section"); const playersNoteNode = document.getElementById("match-detail-players-note"); const playersStateNode = document.getElementById("match-detail-players-state"); const playersTableShellNode = document.getElementById("match-detail-players-table-shell"); const playersBodyNode = document.getElementById("match-detail-players-body"); if (!serverSlug || !matchId) { titleNode.textContent = "Partida no seleccionada"; summaryNode.textContent = "Vuelve al historico y abre una partida registrada."; noteNode.textContent = "Faltan parametros internos para cargar este detalle."; setState(stateNode, "No hay una partida seleccionada.", true); return; } void loadMatchDetail({ backendBaseUrl, serverSlug, matchId, titleNode, summaryNode, noteNode, stateNode, gridNode, actionsNode, playersSectionNode, playersNoteNode, playersStateNode, playersTableShellNode, playersBodyNode, }); }); async function loadMatchDetail({ backendBaseUrl, serverSlug, matchId, titleNode, summaryNode, noteNode, stateNode, gridNode, actionsNode, playersSectionNode, playersNoteNode, playersStateNode, playersTableShellNode, playersBodyNode, }) { try { const payload = await fetchJson( `${backendBaseUrl}/api/historical/matches/detail?server=${encodeURIComponent( serverSlug, )}&match=${encodeURIComponent(matchId)}`, ); const data = payload?.data; const item = data?.item; if (!data?.found || !item) { titleNode.textContent = "Detalle no disponible"; summaryNode.textContent = "La partida existe como enlace interno, pero todavia no hay detalle suficiente para mostrar."; noteNode.textContent = "El historico local puede tener solo una ventana RCON parcial o ningun registro ampliado."; setState(stateNode, "Detalle no disponible para esta partida."); return; } renderMatchDetail(item, { titleNode, summaryNode, noteNode, stateNode, gridNode, actionsNode, playersSectionNode, playersNoteNode, playersStateNode, playersTableShellNode, playersBodyNode, }); } catch (error) { titleNode.textContent = "Detalle no disponible"; summaryNode.textContent = "No se pudo conectar con el backend local."; noteNode.textContent = "Comprueba que el backend este levantado y vuelve a intentarlo."; setState(stateNode, "Error al cargar el detalle de la partida.", true); } } function renderMatchDetail( item, { titleNode, summaryNode, noteNode, stateNode, gridNode, actionsNode, playersSectionNode, playersNoteNode, playersStateNode, playersTableShellNode, playersBodyNode, }, ) { const mapName = item.map?.pretty_name || item.map?.name || "Mapa no disponible"; const serverName = item.server?.name || "Servidor no disponible"; titleNode.textContent = mapName; summaryNode.textContent = `${serverName} | Partida ${item.match_id || "sin id"}`; noteNode.textContent = buildDetailNote(item); gridNode.innerHTML = [ renderDetailCard("Servidor", serverName), renderDetailCard("Mapa", mapName), renderDetailCard("Inicio", formatTimestamp(item.started_at)), renderDetailCard("Fin", formatTimestamp(item.closed_at || item.ended_at)), renderDetailCard("Duracion", formatDuration(item.duration_seconds)), renderDetailCard("Jugadores media", formatNumber(item.player_count)), renderDetailCard("Pico jugadores", formatOptionalNumber(item.peak_players)), renderDetailCard("Muestras RCON", formatOptionalNumber(item.sample_count)), renderDetailCard("Marcador", formatScore(item.result)), renderDetailCard("Resultado", formatMatchResult(item.result)), renderDetailCard("Base de captura", formatCaptureBasis(item.capture_basis)), renderDetailCard("Capacidades", formatCapabilities(item.capabilities)), ].join(""); renderPlayerSection(item, { playersSectionNode, playersNoteNode, playersStateNode, playersTableShellNode, playersBodyNode, }); renderActions(item, actionsNode); stateNode.hidden = true; gridNode.hidden = false; } function renderPlayerSection( item, { playersSectionNode, playersNoteNode, playersStateNode, playersTableShellNode, playersBodyNode, }, ) { const players = Array.isArray(item.players) ? item.players : []; playersSectionNode.hidden = false; if (players.length === 0) { playersNoteNode.textContent = "Esta partida no tiene estadisticas por jugador disponibles en el detalle interno."; setState( playersStateNode, item.capture_basis === "rcon-competitive-window" ? "Las ventanas RCON actuales no incluyen desglose por jugador." : "No hay filas de jugador registradas para esta partida.", ); playersTableShellNode.hidden = true; playersBodyNode.innerHTML = ""; return; } playersNoteNode.textContent = `${formatNumber(players.length)} jugadores con estadisticas locales.`; playersStateNode.hidden = true; playersBodyNode.innerHTML = players.map((player) => renderPlayerRow(player)).join(""); playersTableShellNode.hidden = false; } function renderPlayerRow(player) { return `
${escapeHtml(label)}
${escapeHtml(value)}