From 84818f1b0294d8abfcd98b3dd46468fdf50b8338 Mon Sep 17 00:00:00 2001 From: devRaGonSa Date: Thu, 11 Jun 2026 14:57:51 +0200 Subject: [PATCH] Polish player profile layout name fallback and user copy --- ...file-layout-name-fallback-and-user-copy.md | 76 ++++++++++ frontend/assets/css/styles.css | 22 +++ frontend/assets/js/stats.js | 130 +++++++++++------- frontend/stats.html | 1 + 4 files changed, 181 insertions(+), 48 deletions(-) create mode 100644 ai/tasks/done/TASK-256-polish-player-profile-layout-name-fallback-and-user-copy.md diff --git a/ai/tasks/done/TASK-256-polish-player-profile-layout-name-fallback-and-user-copy.md b/ai/tasks/done/TASK-256-polish-player-profile-layout-name-fallback-and-user-copy.md new file mode 100644 index 0000000..830b3a7 --- /dev/null +++ b/ai/tasks/done/TASK-256-polish-player-profile-layout-name-fallback-and-user-copy.md @@ -0,0 +1,76 @@ +# TASK-256 - Polish player profile layout, name fallback and user copy + +## Summary + +This task polishes the public player profile in `stats.html` after TASK-253 and TASK-255. + +Changes included: + +- moved external profile links out of the lower summary grid +- kept selected search-result names as UI fallback when the profile payload has no player name +- replaced technical user-facing copy with player-friendly messages +- kept KPM visibility restricted to `kpm_status == ready` + +## External Profile Layout + +`Perfiles externos` now renders directly under the `Perfil personal` state text and before the weekly/monthly comparison cards. + +The lower summary grid now only contains: + +- `Identidad` +- `Ventana semanal` +- `Ventana mensual` + +The links remain responsive and wrap on narrow screens. + +## Player Name Fallback + +Root cause: + +- degraded weekly/monthly profile payloads can return no `player_name` +- the frontend then fell back to `Sin nombre` +- this ignored the player name already selected from search results + +Fix: + +- the selected result name is passed into `loadPlayerProfile(...)` +- visible name priority is: + - payload player name if it is usable + - selected search-result name + - current search text if it looks like a name + - `Jugador seleccionado` + +Raw Steam/Epic identifiers are not used as visible profile names. + +## User Copy + +Replaced visible technical wording: + +- `Backend no disponible...` -> `Servicio no disponible...` +- `Verifica backend...` -> `Intentalo de nuevo en unos segundos.` +- `Jugador sin estadisticas suficientes...` -> `Todavia no hay estadisticas suficientes para este jugador.` +- `Ranking ausente` -> `Sin posicion` +- backend/recalculation note -> `Comparativa basada en los datos disponibles actualmente.` + +Internal variable names and fetch URLs still use backend terminology where required by code. + +## Validation + +Executed: + +- `node --check frontend/assets/js/stats.js` + +Static checks: + +- no visible `Sin nombre` fallback remains in stats UI +- no visible profile copy mentions backend, endpoint, payload, read model, ranking block, or recalculation +- KPM is still rendered only for `kpm_status == ready` +- Steam profile links still resolve to Steam + Hellor + HLL Records + Helo +- Epic profile links still resolve to Hellor + HLL Records + +## Notes + +- No backend changes. +- No TeamKills changes. +- No asset changes. +- No scheduler/RCON/server configuration changes. diff --git a/frontend/assets/css/styles.css b/frontend/assets/css/styles.css index 3b0675f..d8cd776 100644 --- a/frontend/assets/css/styles.css +++ b/frontend/assets/css/styles.css @@ -1455,6 +1455,28 @@ h2 { margin-top: 10px; } +.stats-profile-links-bar { + margin: 0 0 18px; +} + +.stats-profile-links-strip { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px 14px; +} + +.stats-profile-links-strip__label { + color: var(--muted); + font-size: 0.82rem; + font-weight: 800; + text-transform: uppercase; +} + +.stats-profile-links-bar .stats-profile-links { + margin-top: 0; +} + .stats-profile-links a { display: inline-flex; align-items: center; diff --git a/frontend/assets/js/stats.js b/frontend/assets/js/stats.js index cdf2adb..c7be586 100644 --- a/frontend/assets/js/stats.js +++ b/frontend/assets/js/stats.js @@ -9,6 +9,7 @@ const profilePanel = document.getElementById("stats-profile-panel"); const profileTitle = document.getElementById("stats-profile-title"); const profileStateNode = document.getElementById("stats-profile-state"); + const profileLinksBarNode = document.getElementById("stats-profile-links-bar"); const comparisonGrid = document.getElementById("stats-comparison-grid"); const summaryGrid = document.getElementById("stats-summary-grid"); const weeklySummaryNode = document.getElementById("stats-weekly-summary"); @@ -43,14 +44,14 @@ const messages = { backendUnavailableForSearch: - "Backend no disponible. No se pueden buscar jugadores ni cargar su perfil.", + "Servicio no disponible. No se pueden buscar jugadores ni cargar su perfil.", backendUnavailableForAnnual: - "Backend no disponible. No se puede consultar el ranking anual.", + "Servicio no disponible. No se puede consultar el ranking anual.", searchNoInput: "Introduce al menos 4 caracteres para buscar un jugador.", searchLoading: "Buscando jugadores...", searchEmpty: "Sin resultados. Prueba con otro texto o ID.", searchError: - "Error al buscar jugadores. Verifica backend y reintenta.", + "Error al buscar jugadores. Intentalo de nuevo en unos segundos.", searchReadyPrefix: "Se encontraron ", searchReadySuffix: " jugador(es). Selecciona uno para ver sus estad\u00edsticas.", @@ -58,7 +59,7 @@ "Introduce al menos 4 caracteres para buscar un jugador.", profileLoading: "Cargando estad\u00edsticas personales...", profileNoStats: - "Jugador sin estad\u00edsticas suficientes para mostrar datos personales.", + "Todavia no hay estadisticas suficientes para este jugador.", profileError: "No fue posible cargar las estad\u00edsticas del jugador.", annualLoading: "Cargando ranking anual...", annualMissing: @@ -75,9 +76,9 @@ monthlyPlaceholder: "Sin datos mensuales. El ranking mensual se actualiza al cargar un jugador.", weeklyWindowUnavailable: - "No se pudo cargar el bloque semanal; se muestran los datos mensuales.", + "No se pudieron cargar los datos semanales; se muestran los datos mensuales.", monthlyWindowUnavailable: - "No se pudo cargar el bloque mensual; se muestran los datos semanales.", + "No se pudieron cargar los datos mensuales; se muestran los datos semanales.", weeklyWindowUnavailableSummary: "Resumen semanal no disponible temporalmente.", monthlyWindowUnavailableSummary: @@ -85,7 +86,7 @@ profileReadyTitle: "Perfil personal", profileEmptyTitle: "Selecciona un jugador para ver sus estad\u00edsticas.", partialProfileLoadWarning: - "Algunos bloques de ranking no se cargaron; se mantienen los disponibles.", + "Algunas lecturas no se cargaron; se mantienen las disponibles.", }; let isBackendOnline = false; @@ -158,6 +159,9 @@ if (comparisonGrid) { comparisonGrid.innerHTML = ""; } + if (profileLinksBarNode) { + profileLinksBarNode.innerHTML = ""; + } summaryGrid.innerHTML = ""; if (weeklySummaryNode) { weeklySummaryNode.textContent = "Cargando ..."; @@ -181,7 +185,7 @@ isBackendOnline = true; setAnnualState( "neutral", - "Backend disponible. Cargando ranking anual 2026.", + "Servicio disponible. Cargando ranking anual 2026.", ); void loadAnnualRanking(); } catch (error) { @@ -234,8 +238,9 @@ resultListNode.querySelectorAll("[data-player-id]").forEach((button) => { button.addEventListener("click", () => { const playerId = button.getAttribute("data-player-id"); + const playerName = button.getAttribute("data-player-name"); if (playerId) { - void loadPlayerProfile(playerId); + void loadPlayerProfile(playerId, playerName); } }); }); @@ -376,7 +381,7 @@ const rowsMarkup = items .map((item) => { const rank = safeInt(item.ranking_position, 0); - const playerName = escapeHtml(String(item.player_name || "Jugador sin nombre")); + const playerName = escapeHtml(resolveVisiblePlayerName(item.player_name, null, null)); const matches = safeInt(item.matches_considered, 0); const kills = safeInt(firstFiniteValue(item.kills, item.metric_value), 0); const killsPerMatch = formatKillsPerMatch( @@ -430,7 +435,7 @@ function renderResultItem(item) { const playerId = escapeHtml(String(item.player_id || "")); - const playerName = escapeHtml(String(item.player_name || "Jugador sin nombre")); + const playerName = escapeHtml(resolveVisiblePlayerName(item.player_name, null, null)); const matches = Number.isFinite(Number(item.matches_considered)) ? Number(item.matches_considered) : 0; @@ -448,6 +453,7 @@ class="stats-result-item__button" type="button" data-player-id="${playerId}" + data-player-name="${playerName}" aria-label="Cargar perfil de ${playerName}" >
@@ -463,7 +469,7 @@ `; } - async function loadPlayerProfile(playerId) { + async function loadPlayerProfile(playerId, selectedPlayerName = "") { if (!isBackendOnline) { markAsBackendUnavailable(); return; @@ -493,8 +499,10 @@ profilePanel.hidden = false; profileTitle.textContent = messages.profileReadyTitle; - const playerName = String( - (weeklyData?.player_name || monthlyData?.player_name || "Sin nombre"), + const playerName = resolveVisiblePlayerName( + weeklyData?.player_name || monthlyData?.player_name, + selectedPlayerName, + searchInput?.value, ); const profileIdentityData = weeklyData?.player_id || weeklyData?.external_profile_links @@ -519,8 +527,13 @@ monthlyData, weeklyFailed, monthlyFailed, - externalProfileBrands, }); + if (profileLinksBarNode) { + profileLinksBarNode.innerHTML = renderStatsExternalProfilesBar( + profileIdentityData, + externalProfileBrands, + ); + } if (partialLoadWarning) { profileStateNode.textContent += ` ${messages.partialProfileLoadWarning}`; @@ -567,6 +580,9 @@ if (comparisonGrid) { comparisonGrid.innerHTML = ""; } + if (profileLinksBarNode) { + profileLinksBarNode.innerHTML = ""; + } summaryGrid.innerHTML = ""; if (weeklySummaryNode) { weeklySummaryNode.textContent = messages.weeklyPlaceholder; @@ -579,12 +595,10 @@ function renderProfileSummaryCards({ playerName, - profileIdentityData, weeklyData, monthlyData, weeklyFailed, monthlyFailed, - externalProfileBrands, }) { const weeklySummary = weeklyFailed ? ` @@ -625,7 +639,6 @@

Partidas semanales: ${safeInt(weeklyData?.matches_considered, 0)}

Partidas mensuales: ${safeInt(monthlyData?.matches_considered, 0)}

- ${renderStatsExternalProfilesCard(profileIdentityData, externalProfileBrands)} ${weeklySummary} ${monthlySummary} `; @@ -800,7 +813,7 @@ Delta de partidas: ${formatSignedNumber(matchesDelta)}

- La lectura compara solo datos existentes del backend actual, sin recalcular rankings ni pedir nuevos endpoints. + Comparativa basada en los datos disponibles actualmente.

`; @@ -836,7 +849,7 @@ if (requestFailed) { return { tone: "warning", - title: "Bloque no disponible", + title: "Datos no disponibles", detail: `No fue posible cargar los datos de la ventana ${label} en este intento.`, }; } @@ -844,8 +857,8 @@ if (!ranking) { return { tone: "error", - title: "Ranking ausente", - detail: `No se recibio bloque de ranking ${label} desde el backend.`, + title: "Sin posicion", + detail: `Aun no hay datos de la ventana ${label} suficientes para calcular su posicion.`, }; } @@ -921,6 +934,30 @@ return labels[String(metric || "").trim()] || "Kills"; } + function resolveVisiblePlayerName(primaryName, selectedName, queryText) { + const candidates = [primaryName, selectedName, queryText]; + for (const candidate of candidates) { + const normalized = String(candidate || "").trim(); + if (isDisplayablePlayerName(normalized)) { + return normalized; + } + } + return "Jugador seleccionado"; + } + + function isDisplayablePlayerName(value) { + if (!value) { + return false; + } + if (/^\d{17}$/.test(value)) { + return false; + } + if (/^[0-9a-f]{32}$/i.test(value)) { + return false; + } + return true; + } + function labelForStatsWindowKind(windowKind) { const labels = { "current-week": "Semana actual", @@ -950,34 +987,31 @@ `; } - function renderStatsExternalProfilesCard(profileData, brands) { + function renderStatsExternalProfilesBar(profileData, brands) { const links = resolveStatsExternalProfileLinks(profileData, brands); + if (!links.length) { + return ""; + } return ` - + `; } diff --git a/frontend/stats.html b/frontend/stats.html index 2c034be..a276ceb 100644 --- a/frontend/stats.html +++ b/frontend/stats.html @@ -110,6 +110,7 @@

Selecciona un jugador para ver su panel personal.

+