feat: gestion événements — historique, export ZIP, filtre galerie
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
{% 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> | 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';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHistory() {
|
||||
try {
|
||||
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 => `
|
||||
<div class="event-row">
|
||||
<div>
|
||||
<div class="event-name">
|
||||
${ev.name}
|
||||
<span class="badge ${ev.ended_at ? 'badge-done' : 'badge-active'}">
|
||||
${ev.ended_at ? 'Terminé' : 'En cours'}
|
||||
</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 %}
|
||||
Reference in New Issue
Block a user