Files
photoBooth/frontend/templates/admin/events.html
T
admin c89924ea18
🚀 Deploy — JH Photomaton / 🔍 Vérification (push) Has been cancelled
🚀 Deploy — JH Photomaton / 🍓 Deploy sur le Pi (push) Has been cancelled
feat: admin gallery - lightbox with icons, filters by date/event/status; fix: event badges - only current slug shows En cours; fix: archive_all_open on new event; fix: mtime_index uses stem for event photo filter
2026-07-17 15:59:51 +02:00

231 lines
8.0 KiB
HTML

{% extends "base.html" %}
{% block title %}Événements — JH Photomaton{% endblock %}
{% block nav_links %}
<a href="/admin">Dashboard</a>
<a href="/admin/gallery">Galerie</a>
<a href="/admin/actions">Actions</a>
<a href="/admin/print">Impression</a>
<a href="/admin/settings">Réglages</a>
<a href="/admin/events" class="active">Événements</a>
<a href="/admin/logout">Déconnexion</a>
{% endblock %}
{% block head %}
<style>
.events-container {
max-width: 1000px;
margin: 1.5rem auto;
padding: 0 1rem;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1rem;
}
.card-title {
font-size: 1.1rem;
font-weight: 700;
color: var(--primary);
margin-bottom: 1.25rem;
}
.event-row {
display: grid;
grid-template-columns: 1fr auto auto auto;
gap: 1rem;
align-items: center;
padding: .75rem 0;
border-bottom: 1px solid var(--border);
}
.event-row:last-child { border-bottom: none; }
.event-name { font-weight: 600; }
.event-meta { font-size: .78rem; color: var(--text-muted); margin-top: .2rem; }
.badge {
display: inline-block;
padding: .2rem .6rem;
border-radius: 20px;
font-size: .75rem;
font-weight: 600;
}
.badge-active { background: #1a3a1a; color: #4caf50; }
.badge-done { background: var(--surface-2, #222); color: var(--text-muted); }
.stats-row {
display: flex;
gap: 1.5rem;
font-size: .82rem;
color: var(--text-muted);
}
.stats-row span strong { color: var(--text); }
</style>
{% endblock %}
{% block content %}
<div class="events-container">
<h1 class="page-title">📅 Historique des événements</h1>
<!-- Événement en cours -->
<div class="card">
<div class="card-title">🟢 Événement en cours</div>
<div id="current-event-loading" style="color:var(--text-muted)">Chargement…</div>
<div id="current-event" style="display:none">
<div class="event-row">
<div>
<div class="event-name" id="cur-name"></div>
<div class="event-meta">Slug : <code id="cur-slug"></code> &nbsp;|&nbsp; Démarré le <span id="cur-started"></span></div>
<div class="stats-row" style="margin-top:.5rem">
<span>📷 <strong id="cur-photos">0</strong> photos</span>
<span>🖨️ <strong id="cur-prints">0</strong> impressions</span>
<span>⬇️ <strong id="cur-downloads">0</strong> téléchargements</span>
</div>
</div>
<button class="btn btn-ghost btn-sm" onclick="filterGallery(document.getElementById('cur-slug').textContent)">
🖼️ Voir photos
</button>
<a id="cur-export-btn" href="#" class="btn btn-primary btn-sm" download>
⬇️ Export ZIP
</a>
</div>
</div>
</div>
<!-- Filtre galerie -->
<div class="card" id="gallery-filter-card" style="display:none">
<div class="card-title">🖼️ Photos de l'événement : <span id="filter-event-name"></span></div>
<div id="gallery-grid" style="display:grid; grid-template-columns: repeat(auto-fill, minmax(120px,1fr)); gap:.5rem;"></div>
<div style="margin-top:.75rem; display:flex; gap:.5rem; align-items:center;">
<button class="btn btn-ghost btn-sm" onclick="closeGallery()">✕ Fermer</button>
<span id="gallery-count" style="font-size:.82rem; color:var(--text-muted)"></span>
</div>
</div>
<!-- Historique -->
<div class="card">
<div class="card-title">📋 Historique</div>
<div id="history-loading" style="color:var(--text-muted)">Chargement…</div>
<div id="history-list" style="display:none"></div>
</div>
</div>
<script>
async function api(method, url) {
const r = await fetch('/api' + url, { method });
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
}
function showToast(msg, type='info') {
const t = document.createElement('div');
t.className = `toast toast-${type}`;
t.textContent = msg;
document.getElementById('toasts').appendChild(t);
setTimeout(() => t.remove(), 3000);
}
async function loadCurrentEvent() {
try {
const d = await api('GET', '/event');
document.getElementById('current-event-loading').style.display = 'none';
document.getElementById('current-event').style.display = 'block';
document.getElementById('cur-name').textContent = d.name;
document.getElementById('cur-slug').textContent = d.slug;
document.getElementById('cur-started').textContent = d.started_at_iso
? new Date(d.started_at * 1000).toLocaleString('fr-FR')
: '—';
document.getElementById('cur-photos').textContent = d.stats.photos_taken;
document.getElementById('cur-prints').textContent = d.stats.prints_done;
document.getElementById('cur-downloads').textContent = d.stats.downloads;
document.getElementById('cur-export-btn').href = `/api/event/${d.slug}/export`;
} catch(e) {
document.getElementById('current-event-loading').textContent = 'Erreur chargement';
}
}
let _currentSlug = '';
async function loadHistory() {
try {
// Récupère d'abord le slug courant pour l'affichage correct des badges
try { const cur = await api('GET', '/event'); _currentSlug = cur.slug || ''; } catch(e) {}
const d = await api('GET', '/event/history');
document.getElementById('history-loading').style.display = 'none';
const list = document.getElementById('history-list');
list.style.display = 'block';
if (!d.events.length) {
list.innerHTML = '<p style="color:var(--text-muted)">Aucun événement enregistré.</p>';
return;
}
list.innerHTML = d.events.map(ev => {
// "En cours" uniquement si c'est le slug actif ET sans ended_at
const isActive = !ev.ended_at && ev.slug === _currentSlug;
return `
<div class="event-row">
<div>
<div class="event-name">
${ev.name}
<span class="badge ${isActive ? 'badge-active' : 'badge-done'}">
${isActive ? 'En cours' : (ev.ended_at ? 'Terminé' : 'Archivé')}
</span>
</div>
<div class="event-meta">
${ev.started_at_iso || '—'}
${ev.ended_at_iso ? ' → ' + ev.ended_at_iso : ''}
</div>
<div class="stats-row" style="margin-top:.35rem">
<span>📷 <strong>${ev.photos_taken}</strong></span>
<span>🖨️ <strong>${ev.prints_done}</strong></span>
<span>⬇️ <strong>${ev.downloads}</strong></span>
</div>
</div>
<button class="btn btn-ghost btn-sm" onclick="filterGallery('${ev.slug}', '${ev.name}')">
🖼️ Photos
</button>
<a href="/api/event/${ev.slug}/export" class="btn btn-primary btn-sm" download>
⬇️ ZIP
</a>
</div>`;
}).join('');
} catch(e) {
document.getElementById('history-loading').textContent = 'Erreur chargement';
}
}
async function filterGallery(slug, name) {
const card = document.getElementById('gallery-filter-card');
const grid = document.getElementById('gallery-grid');
const countEl = document.getElementById('gallery-count');
document.getElementById('filter-event-name').textContent = name || slug;
card.style.display = 'block';
grid.innerHTML = '<div style="color:var(--text-muted)">Chargement…</div>';
card.scrollIntoView({ behavior: 'smooth' });
try {
const d = await fetch(`/api/gallery/photos?event_slug=${slug}&limit=100`).then(r => r.json());
countEl.textContent = `${d.total} photo(s)`;
if (!d.photos.length) {
grid.innerHTML = '<p style="color:var(--text-muted)">Aucune photo pour cet événement.</p>';
return;
}
grid.innerHTML = d.photos.map(p => `
<a href="${p.full_url}" target="_blank">
<img src="${p.thumb_url}" alt="" style="width:100%;border-radius:6px;object-fit:cover;aspect-ratio:1">
</a>
`).join('');
} catch(e) {
grid.innerHTML = '<p style="color:var(--text-muted)">Erreur chargement photos.</p>';
}
}
function closeGallery() {
document.getElementById('gallery-filter-card').style.display = 'none';
}
loadCurrentEvent();
loadHistory();
</script>
{% endblock %}