diff --git a/README.md b/README.md index 4009341..8422f32 100644 --- a/README.md +++ b/README.md @@ -239,12 +239,11 @@ erDiagram |---|---|---| | `/index.html` | Tous (lecture et écriture) | Répertoire des morceaux travaillés, avec recherche instantanée (titre/artiste), liens/vignettes YouTube et Spotify, tutos embarqués par morceau et par instrument. Ajout avec autocomplete titre/artiste + liens auto-trouvés ([détails](#recherche-automatique-de-morceaux)) | | `/suggestions.html` | Tous | Proposer un morceau (liens YouTube et Spotify + note libre), voter approuver/rejeter avec commentaire (attribué nominativement), ajouter une suggestion au répertoire | -| `/setlist.html` | Tous (lecture et écriture) | Setlist du prochain concert : choix des morceaux du répertoire, ordre, notes, section rappel, lien "Écouter la setlist sur YouTube" | -| `/history.html`, `/history-detail.html` | Tous (lecture et écriture) | Historique des concerts passés, modifiable (date, morceaux, ordre, rappel) et supprimable, avec le même lien playlist YouTube. `/history.html` propose deux vues : chronologique (par défaut, tous les concerts détaillés avec YouTube embarqué par morceau, du plus récent au plus ancien) et réduite (liste compacte, comme avant) | +| `/concerts.html` | Tous (lecture et écriture) | Deux onglets sur une même page. **Prochain concert** : choix des morceaux du répertoire, ordre, notes, section rappel, lien "Écouter la setlist sur YouTube". **Historique** : concerts passés, modifiables (date, morceaux, ordre, rappel) et supprimables, avec le même lien playlist ; deux vues, chronologique (par défaut, tous les concerts détaillés avec YouTube embarqué par morceau, du plus récent au plus ancien) et réduite (liste compacte) ; ouvrir un concert passé (`?tab=history&id=...`) reste partageable en lien direct | | `/profile.html` | Chacun voit le sien | Profil issu d'Authentik (nom, avatar, groupes) + activité personnelle (morceaux ajoutés, suggestions, votes) | | `/calendar.html` | Tous (lecture et écriture) | Disponibilités du groupe pour les 3 prochaines semaines (calendrier, filtres par personne, modale par jour) — [détails](#disponibilités-calendrier) | -Le mode par défaut est la consultation ; les pages Répertoire, Setlist et Suggestions sont interactives pour toute personne connectée (chaque action reste attribuée nominativement via Authentik). +Le mode par défaut est la consultation ; les pages Répertoire, Concerts et Suggestions sont interactives pour toute personne connectée (chaque action reste attribuée nominativement via Authentik). Un bouton clair/sombre dans la barre de navigation permet de forcer un thème (mémorisé par navigateur) ; sans préférence explicite, l'app suit le thème du système. diff --git a/public/concerts.html b/public/concerts.html new file mode 100644 index 0000000..2daa9d4 --- /dev/null +++ b/public/concerts.html @@ -0,0 +1,51 @@ + + + + + +Concerts — Octane + + + + +
+ +
+
+
+

Concerts

+
+ + +
+
+
+ +
+
Chargement…
+
+ + +
+ + + + + + + + diff --git a/public/history-detail.html b/public/history-detail.html deleted file mode 100644 index 950f563..0000000 --- a/public/history-detail.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - -Détail du concert — Octane - - - - -
- -
-
- ← Retour à l'historique -
-
Chargement…
-
- - - - - - - - diff --git a/public/history.html b/public/history.html deleted file mode 100644 index b10ae4d..0000000 --- a/public/history.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - -Historique des concerts — Octane - - - - -
- -
-
-
-
-

Historique des concerts

-

Du plus récent au plus ancien en défilant vers le bas.

-
- -
-
- -
- -
- - - - - - - diff --git a/public/js/admin.js b/public/js/admin.js index 0d3a1b4..41a02a0 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -176,7 +176,7 @@ async function onSaveSlotSettings(e) {

Créneaux de répétition

-

Horaires utilisés pour calculer les disponibilités sur `/calendar.html`.

+

Horaires utilisés pour calculer les disponibilités sur /calendar.html.

@@ -198,7 +198,7 @@ async function onSaveSlotSettings(e) {

Calendriers des membres

Chaque utilisateur de l'application peut avoir plusieurs calendriers (Google, Outlook, Apple...). - Seuls les utilisateurs avec au moins un calendrier configuré apparaissent sur `/calendar.html`. + Seuls les utilisateurs avec au moins un calendrier configuré apparaissent sur /calendar.html. L'application ne conserve jamais le contenu de ces calendriers — seul un statut disponible/occupé par créneau est déduit et enregistré.

diff --git a/public/js/history.js b/public/js/concerts.js similarity index 51% rename from public/js/history.js rename to public/js/concerts.js index 3aa3eb2..eeeff44 100644 --- a/public/js/history.js +++ b/public/js/concerts.js @@ -1,5 +1,6 @@ +let allSongs = []; let concerts = []; -let viewMode = 'timeline'; +let historyViewMode = 'timeline'; function formatDate(dateStr) { const d = new Date(dateStr); @@ -11,6 +12,21 @@ function formatDateShort(dateStr) { return d.toLocaleDateString('fr-FR', { year: 'numeric', month: 'long', day: 'numeric' }); } +function showError(message) { + document.getElementById('error').innerHTML = `
${escapeHtml(message)}
`; +} + +// ---------- Tabs ---------- + +function switchTab(tab) { + document.getElementById('next-view').style.display = tab === 'next' ? '' : 'none'; + document.getElementById('history-view').style.display = tab === 'history' ? '' : 'none'; + document.getElementById('tab-next').classList.toggle('active', tab === 'next'); + document.getElementById('tab-history').classList.toggle('active', tab === 'history'); +} + +// ---------- Historique: liste (timeline / compact) ---------- + function timelineSongRow(song) { const embed = youtubeEmbedUrl(song.youtube_url); return ` @@ -52,7 +68,7 @@ function timelineConcertBlock(concert) {
${encore.map(timelineSongRow).join('')}
` : ''} - Voir / modifier ce concert → + Voir / modifier ce concert → `; } @@ -60,7 +76,7 @@ function timelineConcertBlock(concert) { function concertCardTemplate(c) { return `
- +
${escapeHtml(c.name || 'Concert')}
${escapeHtml(c.venue || '')} · ${formatDateShort(c.concert_date)}
@@ -68,6 +84,15 @@ function concertCardTemplate(c) { `; } +function bindConcertLinks(container) { + container.querySelectorAll('[data-concert-id]').forEach((el) => { + el.addEventListener('click', (e) => { + e.preventDefault(); + showHistoryDetail(el.dataset.concertId); + }); + }); +} + function renderTimeline() { const container = document.getElementById('timeline-view'); if (!concerts.length) { @@ -75,18 +100,20 @@ function renderTimeline() { return; } container.innerHTML = concerts.map(timelineConcertBlock).join(''); + bindConcertLinks(container); } function renderCompact() { const container = document.getElementById('compact-view'); renderList(container, concerts, concertCardTemplate, 'Aucun concert passé enregistré.'); + bindConcertLinks(container); } -function applyViewMode() { +function applyHistoryViewMode() { const timelineEl = document.getElementById('timeline-view'); const compactEl = document.getElementById('compact-view'); - const btn = document.getElementById('toggle-view-btn'); - if (viewMode === 'timeline') { + const btn = document.getElementById('toggle-history-mode-btn'); + if (historyViewMode === 'timeline') { timelineEl.style.display = 'block'; compactEl.style.display = 'none'; btn.textContent = 'Vue réduite'; @@ -97,23 +124,81 @@ function applyViewMode() { } } -function showError(message) { - document.getElementById('error').innerHTML = `
${escapeHtml(message)}
`; +async function loadHistory() { + concerts = await api.get('/api/setlists/history?full=1'); + renderTimeline(); + renderCompact(); + applyHistoryViewMode(); +} + +// ---------- Historique: détail d'un concert ---------- + +function showHistoryList() { + document.getElementById('history-detail-view').style.display = 'none'; + document.getElementById('history-list-view').style.display = 'block'; + history.pushState({}, '', '/concerts.html?tab=history'); +} + +function showHistoryDetail(id) { + document.getElementById('history-list-view').style.display = 'none'; + document.getElementById('history-detail-view').style.display = 'block'; + history.pushState({}, '', `/concerts.html?tab=history&id=${id}`); + + const editor = createSetlistEditor({ + containerId: 'history-detail-content', + allSongs, + getSetlist: async () => { + try { + return await api.get(`/api/setlists/${id}`); + } catch (err) { + return null; + } + }, + emptyMessage: 'Concert introuvable.', + allowDelete: true, + onDeleted: async () => { + showHistoryList(); + await loadHistory(); + }, + }); + editor.load(); } (async function init() { - await initNav('history'); + await initNav('concerts'); + allSongs = await api.get('/api/songs'); - document.getElementById('toggle-view-btn').addEventListener('click', () => { - viewMode = viewMode === 'timeline' ? 'compact' : 'timeline'; - applyViewMode(); + const nextEditor = createSetlistEditor({ + containerId: 'next-content', + allSongs, + getSetlist: () => api.get('/api/setlists/next'), + createSetlist: (data) => api.post('/api/setlists', data), + emptyMessage: 'Aucun concert à venir pour le moment.', + }); + + document.getElementById('tab-next').addEventListener('click', () => switchTab('next')); + document.getElementById('tab-history').addEventListener('click', () => switchTab('history')); + document.getElementById('toggle-history-mode-btn').addEventListener('click', () => { + historyViewMode = historyViewMode === 'timeline' ? 'compact' : 'timeline'; + applyHistoryViewMode(); + }); + document.getElementById('back-to-history-link').addEventListener('click', (e) => { + e.preventDefault(); + showHistoryList(); }); try { - concerts = await api.get('/api/setlists/history?full=1'); - renderTimeline(); - renderCompact(); - applyViewMode(); + await nextEditor.load(); + await loadHistory(); + + const params = new URLSearchParams(window.location.search); + const deepId = params.get('id'); + if (deepId) { + switchTab('history'); + showHistoryDetail(deepId); + } else if (params.get('tab') === 'history') { + switchTab('history'); + } } catch (err) { showError(err.message); } diff --git a/public/js/history-detail.js b/public/js/history-detail.js deleted file mode 100644 index d772f4b..0000000 --- a/public/js/history-detail.js +++ /dev/null @@ -1,29 +0,0 @@ -(async function init() { - await initNav('history'); - const params = new URLSearchParams(window.location.search); - const id = params.get('id'); - const container = document.getElementById('content'); - if (!id) { - container.innerHTML = '

Concert introuvable.

'; - return; - } - - const allSongs = await api.get('/api/songs'); - - const editor = createSetlistEditor({ - allSongs, - getSetlist: async () => { - try { - return await api.get(`/api/setlists/${id}`); - } catch (err) { - return null; - } - }, - emptyMessage: 'Concert introuvable.', - allowDelete: true, - onDeleted: () => { - window.location.href = '/history.html'; - }, - }); - await editor.load(); -})(); diff --git a/public/js/nav.js b/public/js/nav.js index 2c02e55..33aebcf 100644 --- a/public/js/nav.js +++ b/public/js/nav.js @@ -28,9 +28,8 @@ async function initNav(activePage) {