diff --git a/apps/admin/site/app.js b/apps/admin/site/app.js index 4a4f928..e1683ea 100644 --- a/apps/admin/site/app.js +++ b/apps/admin/site/app.js @@ -94,7 +94,7 @@ document.getElementById("logout-btn").addEventListener("click", async () => { // ------------------------------------------------------------------ // Router // ------------------------------------------------------------------ -const VIEWS = ["dashboard", "bands", "queue", "runs", "logs", "checkpoints", "audit"]; +const VIEWS = ["dashboard", "bands", "queue", "runs", "logs", "geocoding", "conflicts", "jobs", "checkpoints", "audit"]; function router() { const hash = (location.hash || "#/dashboard").replace("#/", ""); @@ -108,9 +108,12 @@ function router() { const renderers = { dashboard: renderDashboard, bands: renderBands, + conflicts: renderConflicts, queue: renderQueue, runs: renderRuns, logs: renderLogs, + geocoding: renderGeocoding, + jobs: renderJobs, checkpoints: renderCheckpoints, audit: renderAudit, }; @@ -131,6 +134,26 @@ const content = () => document.getElementById("content"); // ------------------------------------------------------------------ // Dashboard // ------------------------------------------------------------------ +// Découpe un genre complexe ("Doom/Death Metal; Gothic/Progressive") en mots-clés +function splitGenreElements(genreRows) { + const counts = new Map(); + for (const { genre, total } of genreRows) { + if (!genre) continue; + // Retire les parenthèses et leur contenu (ex: "(early)", "(later)") + const cleaned = genre.replace(/\([^)]*\)/g, ""); + // Découpe sur / ; , + const tokens = cleaned.split(/[\/;,]+/); + for (const tok of tokens) { + const kw = tok.trim(); + if (kw.length < 3) continue; + counts.set(kw, (counts.get(kw) || 0) + total); + } + } + return [...counts.entries()] + .map(([kw, n]) => ({ kw, n })) + .sort((a, b) => b.n - a.n); +} + async function renderDashboard() { content().innerHTML = `
Chargement…
`; try { @@ -138,7 +161,8 @@ async function renderDashboard() { const t = r.totals; const maxStatus = Math.max(1, ...r.by_status.map((x) => x.total)); const maxCountry = Math.max(1, ...r.by_country.map((x) => x.total)); - const maxGenre = Math.max(1, ...r.by_genre.map((x) => x.total)); + const elements = splitGenreElements(r.by_genre); + const maxEl = Math.max(1, ...elements.map((x) => x.n)); content().innerHTML = `
@@ -160,9 +184,10 @@ async function renderDashboard() {

Par pays (${r.by_country.length})

${r.by_country.map((x) => barRow(x.country, x.total, maxCountry)).join("") || emptyRow()}
-
-

Par genre (${r.by_genre.length})

- ${r.by_genre.map((x) => barRow(x.genre, x.total, maxGenre)).join("") || emptyRow()} +
+

Éléments de genre (${elements.length} mots-clés)

+

Chaque genre complexe (ex: "Doom/Death Metal; Gothic") est découpé en mots-clés.

+ ${elements.map((x) => barRow(x.kw, x.n, maxEl)).join("") || emptyRow()}
`; @@ -280,6 +305,7 @@ async function renderBands() { +