Merge remote-tracking branch 'origin/codex/fix-recent-match-card-alignment'
This commit is contained in:
82
ai/tasks/done/TASK-139-align-recent-match-cards.md
Normal file
82
ai/tasks/done/TASK-139-align-recent-match-cards.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
id: TASK-139
|
||||||
|
title: Align recent match cards
|
||||||
|
status: done
|
||||||
|
type: frontend
|
||||||
|
team: Frontend Senior
|
||||||
|
supporting_teams: [Experto en interfaz]
|
||||||
|
roadmap_item: foundation
|
||||||
|
priority: medium
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK-139 - Align recent match cards
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Make the recent match cards in `historico.html` use consistent metadata columns and a right-aligned action area across static and dynamic renderers.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The recent matches list should stay compact and clean while preserving the HLL Vietnam dark tactical theme. It must not show raw match IDs, internal status/source/debug text, or the public scoreboard action in the list. The public scoreboard link remains available on the match detail page.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Inspect the listed files first.
|
||||||
|
2. Normalize the static and dynamic recent card markup.
|
||||||
|
3. Use CSS grid for deterministic metadata/action alignment.
|
||||||
|
4. Validate with syntax checks and the requested Docker frontend checks.
|
||||||
|
|
||||||
|
## Files to Read First
|
||||||
|
|
||||||
|
- `ai/architecture-index.md`
|
||||||
|
- `ai/repo-context.md`
|
||||||
|
- `ai/orchestrator/frontend-senior.md`
|
||||||
|
- `frontend/historico.html`
|
||||||
|
- `frontend/assets/js/historico.js`
|
||||||
|
- `frontend/assets/js/historico-recent-live.js`
|
||||||
|
- `frontend/assets/css/historico.css`
|
||||||
|
|
||||||
|
## Expected Files to Modify
|
||||||
|
|
||||||
|
- `frontend/assets/js/historico.js`
|
||||||
|
- `frontend/assets/js/historico-recent-live.js`
|
||||||
|
- `frontend/assets/css/historico.css`
|
||||||
|
- `ai/tasks/done/TASK-139-align-recent-match-cards.md`
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- Keep the change minimal.
|
||||||
|
- Preserve the HLL Vietnam visual identity.
|
||||||
|
- Do not introduce frameworks or backend changes.
|
||||||
|
- Do not alter detail page scoreboard link behavior.
|
||||||
|
- Do not remove `match_url` support.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- `node --check frontend/assets/js/historico-recent-live.js`
|
||||||
|
- `node --check frontend/assets/js/historico.js`
|
||||||
|
- `node --check frontend/assets/js/historico-partida.js`
|
||||||
|
- `docker compose build --no-cache frontend`
|
||||||
|
- `docker compose up -d frontend`
|
||||||
|
- Manual verification on `http://localhost:8080/historico.html?nocache=alignment`
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
Implemented a shared clean recent-card structure for the static snapshot renderer and dynamic live renderer. The recent list now shows only map title, Servidor, Cierre, Jugadores, Marcador and right-aligned actions. The public scoreboard action keeps its `match_url` link in markup with a recent-list-only hidden class, while the detail page scoreboard action remains visible.
|
||||||
|
|
||||||
|
Validation completed:
|
||||||
|
|
||||||
|
- `node --check frontend/assets/js/historico-recent-live.js`
|
||||||
|
- `node --check frontend/assets/js/historico.js`
|
||||||
|
- `node --check frontend/assets/js/historico-partida.js`
|
||||||
|
- `docker compose build --no-cache frontend`
|
||||||
|
- `docker compose up -d frontend`
|
||||||
|
- Browser verification on `http://localhost:8080/historico.html?nocache=alignment`
|
||||||
|
- Browser verification on the supplied `historico-partida.html` detail URL
|
||||||
|
|
||||||
|
Manual verification found 13 recent cards, 13 visible `Ver detalles` links, zero visible `Ver partida` links in the recent list, no forbidden internal/debug text in the recent list, 0px column spread across all desktop metadata columns, and a visible public scoreboard link on the detail page.
|
||||||
|
|
||||||
|
## Change Budget
|
||||||
|
|
||||||
|
- Prefer fewer than 5 modified files.
|
||||||
|
- Prefer changes under 200 lines when feasible.
|
||||||
@@ -776,58 +776,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.historical-match-meta--clean {
|
.historical-match-meta--clean {
|
||||||
grid-template-columns: minmax(180px, 1.3fr) minmax(120px, 0.8fr) minmax(110px, 0.7fr) minmax(110px, 0.7fr) auto;
|
grid-template-columns:
|
||||||
|
minmax(220px, 1.4fr)
|
||||||
|
minmax(130px, 0.75fr)
|
||||||
|
minmax(110px, 0.65fr)
|
||||||
|
minmax(110px, 0.65fr)
|
||||||
|
260px;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.historical-match-meta--clean > article {
|
||||||
|
align-self: stretch;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.historical-match-card__actions-cell {
|
.historical-match-card__actions-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
min-width: 250px;
|
min-width: 260px;
|
||||||
}
|
|
||||||
|
|
||||||
.historical-match-card__actions-cell .historical-match-card__actions {
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 920px) {
|
|
||||||
.historical-match-meta--clean {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.historical-match-card__actions-cell {
|
|
||||||
justify-content: flex-start;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.historical-match-card__actions-cell .historical-match-card__actions {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.historical-match-card--clean {
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.historical-match-card__top--clean {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.historical-match-meta--clean {
|
|
||||||
grid-template-columns: minmax(180px, 1.3fr) minmax(120px, 0.8fr) minmax(110px, 0.7fr) minmax(110px, 0.7fr) auto;
|
|
||||||
align-items: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.historical-match-card__actions-cell {
|
|
||||||
display: flex;
|
|
||||||
align-items: end;
|
|
||||||
justify-content: flex-end;
|
|
||||||
min-width: 250px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.historical-match-card__actions-cell .historical-match-card__actions {
|
.historical-match-card__actions-cell .historical-match-card__actions {
|
||||||
|
gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
@@ -849,6 +822,6 @@
|
|||||||
|
|
||||||
/* Hide public scoreboard action only in the recent matches list.
|
/* Hide public scoreboard action only in the recent matches list.
|
||||||
The internal detail page can still show its own scoreboard button. */
|
The internal detail page can still show its own scoreboard button. */
|
||||||
#recent-matches-list .historical-match-card__link[href^="https://scoreboard.comunidadhll.es"] {
|
#recent-matches-list .historical-match-card__link--scoreboard {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
(() => {
|
(() => {
|
||||||
const RECENT_MATCHES_ENDPOINT = "/api/historical/recent-matches";
|
const RECENT_MATCHES_ENDPOINT = "/api/historical/recent-matches";
|
||||||
const REFRESH_DELAYS_MS = [150, 1000, 3000];
|
const REFRESH_DELAYS_MS = [150, 1000, 3000];
|
||||||
|
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
const stateNode = document.getElementById("recent-matches-state");
|
const stateNode = document.getElementById("recent-matches-state");
|
||||||
const metaNode = document.getElementById("recent-matches-snapshot-meta");
|
const metaNode = document.getElementById("recent-matches-snapshot-meta");
|
||||||
const noteNode = document.getElementById("recent-matches-note");
|
const noteNode = document.getElementById("recent-matches-note");
|
||||||
|
|
||||||
if (!listNode || !stateNode || !metaNode) {
|
if (!listNode || !stateNode || !metaNode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -35,17 +36,18 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${backendBaseUrl}${RECENT_MATCHES_ENDPOINT}?server=${encodeURIComponent(
|
`${backendBaseUrl}${RECENT_MATCHES_ENDPOINT}?server=${encodeURIComponent(serverSlug)}&limit=20`,
|
||||||
serverSlug,
|
|
||||||
)}&limit=10`,
|
|
||||||
{ cache: "no-store" },
|
{ cache: "no-store" },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP ${response.status}`);
|
throw new Error(`HTTP ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = await response.json();
|
const payload = await response.json();
|
||||||
const data = payload?.data || {};
|
const data = payload?.data || {};
|
||||||
const items = Array.isArray(data.items) ? data.items : [];
|
const items = Array.isArray(data.items) ? data.items : [];
|
||||||
|
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
setDynamicState(stateNode, "No hay partidas recientes disponibles para este alcance.");
|
setDynamicState(stateNode, "No hay partidas recientes disponibles para este alcance.");
|
||||||
listNode.innerHTML = "";
|
listNode.innerHTML = "";
|
||||||
@@ -55,16 +57,72 @@
|
|||||||
|
|
||||||
listNode.innerHTML = items.map((item) => renderDynamicRecentMatchCard(item)).join("");
|
listNode.innerHTML = items.map((item) => renderDynamicRecentMatchCard(item)).join("");
|
||||||
stateNode.hidden = true;
|
stateNode.hidden = true;
|
||||||
|
|
||||||
if (noteNode) {
|
if (noteNode) {
|
||||||
noteNode.textContent = "Lista dinamica de partidas registradas por el modelo RCON reciente.";
|
noteNode.textContent = "Lista dinámica de partidas registradas.";
|
||||||
}
|
}
|
||||||
metaNode.textContent = buildDynamicRecentMeta(data, items);
|
|
||||||
|
metaNode.textContent = buildDynamicRecentMeta(items);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setDynamicState(stateNode, "No se pudieron cargar las partidas recientes dinamicas.", true);
|
setDynamicState(stateNode, "No se pudieron cargar las partidas recientes dinámicas.", true);
|
||||||
metaNode.textContent = "Error al leer las partidas recientes dinamicas.";
|
metaNode.textContent = "Error al leer las partidas recientes dinámicas.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderDynamicRecentMatchCard(item) {
|
||||||
|
const mapName = item?.map?.pretty_name || item?.map?.name || "Mapa no disponible";
|
||||||
|
const serverName = item?.server?.name || "Servidor no disponible";
|
||||||
|
const closedAt = item?.closed_at || item?.ended_at || item?.started_at;
|
||||||
|
const detailUrl = buildDynamicInternalMatchDetailUrl(item);
|
||||||
|
const externalUrl = normalizeDynamicExternalMatchUrl(item?.match_url);
|
||||||
|
|
||||||
|
const actionLinks = [
|
||||||
|
`<span class="historical-match-card__result">${escapeDynamicHtml(formatDynamicResultLabel(item?.result))}</span>`,
|
||||||
|
detailUrl
|
||||||
|
? `<a class="historical-match-card__link" href="${escapeDynamicHtml(detailUrl)}">Ver detalles</a>`
|
||||||
|
: "",
|
||||||
|
externalUrl
|
||||||
|
? `<a class="historical-match-card__link historical-match-card__link--scoreboard" href="${escapeDynamicHtml(externalUrl)}" target="_blank" rel="noopener noreferrer">Ver partida</a>`
|
||||||
|
: "",
|
||||||
|
].join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<article class="historical-match-card historical-match-card--clean">
|
||||||
|
<div class="historical-match-card__top historical-match-card__top--clean">
|
||||||
|
<h3 class="historical-match-card__title">${escapeDynamicHtml(mapName)}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="historical-match-meta historical-match-meta--clean">
|
||||||
|
<article>
|
||||||
|
<p class="historical-match-meta__label">Servidor</p>
|
||||||
|
<strong>${escapeDynamicHtml(serverName)}</strong>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<p class="historical-match-meta__label">Cierre</p>
|
||||||
|
<strong>${escapeDynamicHtml(formatDynamicTimestamp(closedAt))}</strong>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<p class="historical-match-meta__label">Jugadores</p>
|
||||||
|
<strong>${escapeDynamicHtml(formatDynamicNumber(item?.player_count))}</strong>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<p class="historical-match-meta__label">Marcador</p>
|
||||||
|
<strong>${escapeDynamicHtml(formatDynamicScore(item?.result))}</strong>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="historical-match-card__actions-cell" aria-label="Acciones de la partida">
|
||||||
|
<div class="historical-match-card__actions">
|
||||||
|
${actionLinks}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function readServerFromUrl() {
|
function readServerFromUrl() {
|
||||||
return new URLSearchParams(window.location.search).get("server") || "all-servers";
|
return new URLSearchParams(window.location.search).get("server") || "all-servers";
|
||||||
}
|
}
|
||||||
@@ -77,92 +135,9 @@
|
|||||||
return "all-servers";
|
return "all-servers";
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDynamicRecentMatchCard(item) {
|
function buildDynamicRecentMeta(items) {
|
||||||
const mapName = item?.map?.pretty_name || item?.map?.name || "Mapa no disponible";
|
|
||||||
const serverName = item?.server?.name || "Servidor no disponible";
|
|
||||||
const closedAt = item?.closed_at || item?.ended_at || item?.started_at;
|
|
||||||
const detailUrl = buildDynamicInternalMatchDetailUrl(item);
|
|
||||||
const externalUrl = normalizeDynamicExternalMatchUrl(item?.match_url);
|
|
||||||
|
|
||||||
const actions = [
|
|
||||||
`<span class="historical-match-card__result">${escapeDynamicHtml(formatDynamicResultLabel(item?.result))}</span>`,
|
|
||||||
detailUrl
|
|
||||||
? `<a class="historical-match-card__link" href="${escapeDynamicHtml(detailUrl)}">Ver detalles</a>`
|
|
||||||
: "",
|
|
||||||
externalUrl
|
|
||||||
? `<a class="historical-match-card__link" href="${escapeDynamicHtml(externalUrl)}" target="_blank" rel="noopener noreferrer">Ver partida</a>`
|
|
||||||
: "",
|
|
||||||
].join("");
|
|
||||||
|
|
||||||
return `
|
|
||||||
<article class="historical-match-card historical-match-card--clean">
|
|
||||||
<div class="historical-match-card__top historical-match-card__top--clean">
|
|
||||||
<h3 class="historical-match-card__title">${escapeDynamicHtml(mapName)}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="historical-match-meta historical-match-meta--clean">
|
|
||||||
<article>
|
|
||||||
<p class="historical-match-meta__label">Servidor</p>
|
|
||||||
<strong>${escapeDynamicHtml(serverName)}</strong>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<p class="historical-match-meta__label">Cierre</p>
|
|
||||||
<strong>${escapeDynamicHtml(formatDynamicTimestamp(closedAt))}</strong>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<p class="historical-match-meta__label">Jugadores</p>
|
|
||||||
<strong>${escapeDynamicHtml(formatDynamicNumber(item?.player_count))}</strong>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<p class="historical-match-meta__label">Marcador</p>
|
|
||||||
<strong>${escapeDynamicHtml(formatDynamicScore(item?.result))}</strong>
|
|
||||||
</article>
|
|
||||||
<article class="historical-match-card__actions-cell">
|
|
||||||
<div class="historical-match-card__actions">
|
|
||||||
${actions}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDynamicResultLabel(result) {
|
|
||||||
const winner = String(result?.winner || "").toLowerCase();
|
|
||||||
if (winner === "allies" || winner === "allied") {
|
|
||||||
return "Victoria aliada";
|
|
||||||
}
|
|
||||||
if (winner === "axis") {
|
|
||||||
return "Victoria axis";
|
|
||||||
}
|
|
||||||
return "Empate";
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildDynamicInternalMatchDetailUrl(item) {
|
|
||||||
const serverSlug = item?.server?.slug;
|
|
||||||
const matchId = item?.internal_detail_match_id || item?.match_id;
|
|
||||||
if (!serverSlug || matchId === undefined || matchId === null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return `./historico-partida.html?server=${encodeURIComponent(String(serverSlug))}&match=${encodeURIComponent(String(matchId))}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeDynamicExternalMatchUrl(value) {
|
|
||||||
if (typeof value !== "string" || !value.trim()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const url = new URL(value.trim());
|
|
||||||
return ["http:", "https:"].includes(url.protocol) ? url.href : "";
|
|
||||||
} catch (error) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildDynamicRecentMeta(data, items) {
|
|
||||||
const newest = items[0]?.closed_at || items[0]?.ended_at || items[0]?.started_at;
|
const newest = items[0]?.closed_at || items[0]?.ended_at || items[0]?.started_at;
|
||||||
const source = data.selected_source || data.source || "rcon";
|
return newest ? `Actualizado: ${formatDynamicTimestamp(newest)}` : "Actualizado recientemente";
|
||||||
const captureText = newest ? `Actualizado: ${formatDynamicTimestamp(newest)}` : "Actualizado recientemente";
|
|
||||||
return `${captureText} | Fuente: ${source}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDynamicState(node, message, isError = false) {
|
function setDynamicState(node, message, isError = false) {
|
||||||
@@ -175,10 +150,12 @@
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
return "Fecha no disponible";
|
return "Fecha no disponible";
|
||||||
}
|
}
|
||||||
|
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
if (Number.isNaN(date.getTime())) {
|
if (Number.isNaN(date.getTime())) {
|
||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Intl.DateTimeFormat("es-ES", {
|
return new Intl.DateTimeFormat("es-ES", {
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
month: "numeric",
|
month: "numeric",
|
||||||
@@ -196,12 +173,52 @@
|
|||||||
function formatDynamicScore(result) {
|
function formatDynamicScore(result) {
|
||||||
const allied = result?.allied_score;
|
const allied = result?.allied_score;
|
||||||
const axis = result?.axis_score;
|
const axis = result?.axis_score;
|
||||||
|
|
||||||
if (Number.isFinite(Number(allied)) && Number.isFinite(Number(axis))) {
|
if (Number.isFinite(Number(allied)) && Number.isFinite(Number(axis))) {
|
||||||
return `${allied} - ${axis}`;
|
return `${allied} - ${axis}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "- - -";
|
return "- - -";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDynamicResultLabel(result) {
|
||||||
|
const winner = String(result?.winner || "").toLowerCase();
|
||||||
|
|
||||||
|
if (winner === "allies" || winner === "allied") {
|
||||||
|
return "Victoria aliada";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (winner === "axis") {
|
||||||
|
return "Victoria axis";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Empate";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDynamicInternalMatchDetailUrl(item) {
|
||||||
|
const serverSlug = item?.server?.slug;
|
||||||
|
const matchId = item?.internal_detail_match_id || item?.match_id;
|
||||||
|
|
||||||
|
if (!serverSlug || matchId === undefined || matchId === null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `./historico-partida.html?server=${encodeURIComponent(String(serverSlug))}&match=${encodeURIComponent(String(matchId))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDynamicExternalMatchUrl(value) {
|
||||||
|
if (typeof value !== "string" || !value.trim()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(value.trim());
|
||||||
|
return ["http:", "https:"].includes(url.protocol) ? url.href : "";
|
||||||
|
} catch (error) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function escapeDynamicHtml(value) {
|
function escapeDynamicHtml(value) {
|
||||||
return String(value ?? "")
|
return String(value ?? "")
|
||||||
.replaceAll("&", "&")
|
.replaceAll("&", "&")
|
||||||
|
|||||||
@@ -805,6 +805,7 @@ function renderRecentMatchCard(item) {
|
|||||||
const matchUrl = normalizeExternalMatchUrl(item.match_url);
|
const matchUrl = normalizeExternalMatchUrl(item.match_url);
|
||||||
const detailUrl = buildInternalMatchDetailUrl(item);
|
const detailUrl = buildInternalMatchDetailUrl(item);
|
||||||
const actionLinks = [
|
const actionLinks = [
|
||||||
|
`<span class="historical-match-card__result">${escapeHtml(formatMatchResult(item.result))}</span>`,
|
||||||
detailUrl
|
detailUrl
|
||||||
? `
|
? `
|
||||||
<a
|
<a
|
||||||
@@ -818,7 +819,7 @@ function renderRecentMatchCard(item) {
|
|||||||
matchUrl
|
matchUrl
|
||||||
? `
|
? `
|
||||||
<a
|
<a
|
||||||
class="historical-match-card__link"
|
class="historical-match-card__link historical-match-card__link--scoreboard"
|
||||||
href="${escapeHtml(matchUrl)}"
|
href="${escapeHtml(matchUrl)}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@@ -829,18 +830,12 @@ function renderRecentMatchCard(item) {
|
|||||||
: "",
|
: "",
|
||||||
].join("");
|
].join("");
|
||||||
return `
|
return `
|
||||||
<article class="historical-match-card">
|
<article class="historical-match-card historical-match-card--clean">
|
||||||
<div class="historical-match-card__top">
|
<div class="historical-match-card__top historical-match-card__top--clean">
|
||||||
<div>
|
|
||||||
<p class="historical-match-meta__label">Partida ${escapeHtml(item.match_id || "sin id")}</p>
|
|
||||||
<h3 class="historical-match-card__title">${escapeHtml(mapName)}</h3>
|
<h3 class="historical-match-card__title">${escapeHtml(mapName)}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="historical-match-card__actions">
|
|
||||||
<span class="historical-match-card__result">${escapeHtml(formatMatchResult(item.result))}</span>
|
<div class="historical-match-meta historical-match-meta--clean">
|
||||||
${actionLinks}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="historical-match-meta">
|
|
||||||
<article>
|
<article>
|
||||||
<p class="historical-match-meta__label">Servidor</p>
|
<p class="historical-match-meta__label">Servidor</p>
|
||||||
<strong>${escapeHtml(item.server?.name || "Servidor no disponible")}</strong>
|
<strong>${escapeHtml(item.server?.name || "Servidor no disponible")}</strong>
|
||||||
@@ -857,9 +852,10 @@ function renderRecentMatchCard(item) {
|
|||||||
<p class="historical-match-meta__label">Marcador</p>
|
<p class="historical-match-meta__label">Marcador</p>
|
||||||
<strong>${escapeHtml(formatScore(item.result))}</strong>
|
<strong>${escapeHtml(formatScore(item.result))}</strong>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article class="historical-match-card__actions-cell" aria-label="Acciones de la partida">
|
||||||
<p class="historical-match-meta__label">Estado</p>
|
<div class="historical-match-card__actions">
|
||||||
<strong>${escapeHtml(formatRecentMatchStatus(item))}</strong>
|
${actionLinks}
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
Reference in New Issue
Block a user