feat: filtre par catégorie dans la GUI
Docker / docker (push) Successful in 1m15s

- arte_api.py : GENRE_PAGES devient une liste de (nom, url), chaque
  concert reçoit un champ "categories" avec ses genres d'appartenance
- main.py : endpoint /api/categories + param ?category= sur /api/concerts
- index.html : barre de pills catégories (Tout + 10 genres)
- style.css : styles .cat-bar / .cat-pill avec pill active en or
- app.js : chargement dynamique des pills, filtre catégorie dans le state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dev
2026-04-26 13:03:52 +02:00
parent 1815a4e8c2
commit 16736e2e7a
5 changed files with 122 additions and 30 deletions
+28 -1
View File
@@ -4,6 +4,7 @@
const state = {
page: 1,
search: '',
category: '',
pageSize: 24,
totalPages: 1,
current: null, // concert object shown in modal
@@ -21,6 +22,7 @@ const modalOverlay = $('modal-overlay');
const dlPanel = $('dl-panel');
const dlPanelBody = $('dl-panel-body');
const dlBadge = $('dl-badge');
const catBar = $('cat-bar');
// ── Helpers ──────────────────────────────────────────────────────────────────
function fmtDuration(secs) {
@@ -45,12 +47,37 @@ function debounce(fn, ms) {
let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); };
}
// ── Categories ───────────────────────────────────────────────────────────────
async function loadCategories() {
try {
const cats = await fetch('/api/categories').then(r => r.json());
cats.forEach(cat => {
const btn = document.createElement('button');
btn.className = 'cat-pill';
btn.dataset.cat = cat;
btn.textContent = cat;
catBar.appendChild(btn);
});
} catch {}
}
catBar.addEventListener('click', e => {
const pill = e.target.closest('.cat-pill');
if (!pill) return;
catBar.querySelectorAll('.cat-pill').forEach(p => p.classList.remove('active'));
pill.classList.add('active');
state.category = pill.dataset.cat;
state.page = 1;
refresh();
});
// ── Concerts ─────────────────────────────────────────────────────────────────
async function loadConcerts() {
const params = new URLSearchParams({
page: state.page,
search: state.search,
page_size: state.pageSize,
category: state.category,
});
const res = await fetch(`/api/concerts?${params}`);
if (!res.ok) throw new Error(res.statusText);
@@ -359,6 +386,6 @@ document.addEventListener('keydown', e => {
// ── Init ─────────────────────────────────────────────────────────────────────
(async () => {
await refreshDlHistory();
await Promise.all([loadCategories(), refreshDlHistory()]);
await refresh();
})();
+39
View File
@@ -195,6 +195,45 @@ body {
letter-spacing: 0.02em;
}
/* ══ CATEGORY BAR ══════════════════════════════════════════════════════════ */
.cat-bar {
display: flex;
gap: 8px;
margin-bottom: 20px;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
padding-bottom: 2px;
}
.cat-bar::-webkit-scrollbar { display: none; }
.cat-pill {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 20px;
color: var(--text-dim);
cursor: pointer;
font-family: 'Inter', sans-serif;
font-size: 13px;
padding: 6px 16px;
white-space: nowrap;
transition: color var(--transition), border-color var(--transition), background var(--transition);
flex-shrink: 0;
}
.cat-pill:hover {
color: var(--text);
border-color: rgba(255,255,255,0.15);
}
.cat-pill.active {
background: var(--gold);
border-color: var(--gold);
color: #000;
font-weight: 600;
}
/* ══ GRID ══════════════════════════════════════════════════════════════════ */
.grid {
display: grid;