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() {
+