update: one concert tab instead of 2

This commit is contained in:
Nathan FONTEYNE 2026-07-09 18:38:22 +02:00
parent 23fc039ac3
commit 8ef9d1db42
11 changed files with 161 additions and 153 deletions

View file

@ -176,7 +176,7 @@ async function onSaveSlotSettings(e) {
</div>
<h2>Créneaux de répétition</h2>
<p class="note">Horaires utilisés pour calculer les disponibilités sur `/calendar.html`.</p>
<p class="note">Horaires utilisés pour calculer les disponibilités sur <code>/calendar.html</code>.</p>
<div class="panel">
<form id="slot-settings-form" class="stacked-form">
<label>Semaine début <input type="time" name="weekdayStart" required></label>
@ -198,7 +198,7 @@ async function onSaveSlotSettings(e) {
<h2>Calendriers des membres</h2>
<p class="note">
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 <code>/calendar.html</code>.
L'application ne conserve jamais le contenu de ces calendriers seul un statut disponible/occupé
par créneau est déduit et enregistré.
</p>

View file

@ -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 = `<div class="error-banner">${escapeHtml(message)}</div>`;
}
// ---------- 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) {
<div class="timeline-songs">${encore.map(timelineSongRow).join('')}</div>
</div>` : ''}
<a class="back-link" href="/history-detail.html?id=${concert.id}">Voir / modifier ce concert &rarr;</a>
<a class="back-link" href="#" data-concert-id="${concert.id}">Voir / modifier ce concert &rarr;</a>
</div>
`;
}
@ -60,7 +76,7 @@ function timelineConcertBlock(concert) {
function concertCardTemplate(c) {
return `
<div class="card">
<a href="/history-detail.html?id=${c.id}" style="text-decoration:none;color:inherit">
<a href="#" data-concert-id="${c.id}" style="text-decoration:none;color:inherit">
<div class="card-title">${escapeHtml(c.name || 'Concert')}</div>
<div class="card-subtitle">${escapeHtml(c.venue || '')} · ${formatDateShort(c.concert_date)}</div>
</a>
@ -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 = `<div class="error-banner">${escapeHtml(message)}</div>`;
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);
}

View file

@ -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 = '<p class="empty">Concert introuvable.</p>';
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();
})();

View file

@ -28,9 +28,8 @@ async function initNav(activePage) {
<div class="nav-links" id="nav-links">
<a href="/index.html" class="${activePage === 'repertoire' ? 'active' : ''}">Répertoire</a>
<a href="/suggestions.html" class="${activePage === 'suggestions' ? 'active' : ''}">Suggestions</a>
<a href="/setlist.html" class="${activePage === 'setlist' ? 'active' : ''}">Prochain concert</a>
<a href="/concerts.html" class="${activePage === 'concerts' ? 'active' : ''}">Concerts</a>
<a href="/calendar.html" class="${activePage === 'calendar' ? 'active' : ''}">Disponibilités</a>
<a href="/history.html" class="${activePage === 'history' ? 'active' : ''}">Historique</a>
</div>
<div class="nav-user">
<div class="nav-profile" id="nav-profile">

View file

@ -1,12 +0,0 @@
(async function init() {
await initNav('setlist');
const allSongs = await api.get('/api/songs');
const editor = createSetlistEditor({
allSongs,
getSetlist: () => api.get('/api/setlists/next'),
createSetlist: (data) => api.post('/api/setlists', data),
emptyMessage: 'Aucun concert à venir pour le moment.',
});
await editor.load();
})();

View file

@ -1,7 +1,7 @@
// Shared setlist editor used by both "prochain concert" (setlist.js) and
// "historique" (history-detail.js): choose songs from the repertoire, order
// them, add per-song notes, mark encore songs, save. Parameterized so the
// caller decides how to load/create/delete the underlying setlist.
// Shared setlist editor used by concerts.js for both the "prochain concert"
// tab and a past concert's detail view: choose songs from the repertoire,
// order them, add per-song notes, mark encore songs, save. Parameterized so
// the caller decides how to load/create/delete the underlying setlist.
function createSetlistEditor({
containerId = 'content',
allSongs,