diff --git a/backend/api/admin_gallery_api.py b/backend/api/admin_gallery_api.py index 4c555ad..127b30d 100644 --- a/backend/api/admin_gallery_api.py +++ b/backend/api/admin_gallery_api.py @@ -1,6 +1,7 @@ """API galerie admin — impression et suppression de photos.""" import logging +from datetime import datetime from pathlib import Path from fastapi import APIRouter, Request, Query from fastapi.responses import JSONResponse @@ -46,6 +47,9 @@ async def admin_get_photos( request: Request, page: int = Query(default=1, ge=1), limit: int = Query(default=24, ge=1, le=100), + event_slug: str = Query(default=None), + date_from: float = Query(default=None), + date_to: float = Query(default=None), ): """Liste des photos pour la galerie admin, annotées avec leurs demandes d'impression.""" if not _require_auth(request): @@ -53,23 +57,54 @@ async def admin_get_photos( pb = request.app.state.photobooth_service printer_svc = request.app.state.printer_service + cfg = request.app.state.config + event_svc = getattr(request.app.state, "event_service", None) + media_dir = Path(cfg.photobooth.media_dir) # Récupère toutes les photos all_photos = await pb.get_media_collection(limit=500) photos = [p for p in all_photos if _is_image(p)] + # Enrichit chaque photo avec mtime depuis le disque + mtime_index: dict[str, float] = {} + if media_dir.exists(): + for f in media_dir.iterdir(): + mtime_index[f.stem] = f.stat().st_mtime + + for p in photos: + pid = _get_id(p) + p["photo_id"] = pid + mt = mtime_index.get(pid, 0.0) + p["mtime"] = mt + p["date_iso"] = datetime.fromtimestamp(mt).strftime("%Y-%m-%d") if mt else "" + p["date_label"] = datetime.fromtimestamp(mt).strftime("%d/%m/%Y %H:%M") if mt else "" + + # Filtre par événement + if event_slug and event_svc: + ev = await event_svc.get_event_by_slug(event_slug) + if ev: + t0 = ev["started_at"] or 0.0 + t1 = ev["ended_at"] or datetime.now().timestamp() + photos = [p for p in photos if t0 <= p["mtime"] <= t1] + + # Filtre par plage de dates + if date_from is not None: + photos = [p for p in photos if p["mtime"] >= date_from] + if date_to is not None: + photos = [p for p in photos if p["mtime"] <= date_to] + total = len(photos) start = (page - 1) * limit page_photos = photos[start:start + limit] # Construit les URLs for p in page_photos: - pid = _get_id(p) - p["photo_id"] = pid + pid = p["photo_id"] p["full_url"] = pb.media_url(pid) p["thumb_url"] = pb.thumbnail_url(pid) + p["download_url"] = f"/api/gallery/download/{pid}" - # Croise avec la file d'impression en attente (une seule requête SQLite) + # Croise avec la file d'impression en attente try: pending_map = await printer_svc.get_pending_by_photo_id() for p in page_photos: diff --git a/backend/api/event_api.py b/backend/api/event_api.py index 7186e55..4304324 100644 --- a/backend/api/event_api.py +++ b/backend/api/event_api.py @@ -63,9 +63,9 @@ async def update_event(request: Request, payload: EventUpdate): old_slug = cfg.event.slug - if payload.new_event and old_slug and old_slug != new_slug: - # Archive l'ancien événement - await event_svc.archive_event(old_slug) + if payload.new_event: + # Archive TOUS les événements encore ouverts (ended_at IS NULL) + await event_svc.archive_all_open() # Crée ou met à jour la ligne dans la DB await event_svc.ensure_event(new_slug, payload.name, now if payload.new_event else cfg.event.started_at or now) diff --git a/backend/api/gallery.py b/backend/api/gallery.py index 1b919d8..6690530 100644 --- a/backend/api/gallery.py +++ b/backend/api/gallery.py @@ -44,25 +44,32 @@ async def api_gallery_photos( # Filtre par événement si demandé if event_slug: - from pathlib import Path - from datetime import datetime event_svc = getattr(request.app.state, "event_service", None) if event_svc: ev = await event_svc.get_event_by_slug(event_slug) if ev: started_at = ev["started_at"] or 0.0 ended_at = ev["ended_at"] or datetime.now().timestamp() - media_dir = Path(cfg.photobooth.media_dir) - # Construire un index mtime par nom de fichier + # Construire un index mtime par stem ET par nom complet mtime_index: dict[str, float] = {} - if media_dir.exists(): - for f in media_dir.iterdir(): - mtime_index[f.name] = f.stat().st_mtime - # Filtrer les photos par mtime + for media_dir_candidate in [ + Path(cfg.photobooth.media_dir), + Path("/home/pi/photobooth-data/media/processed_full"), + Path("/home/pi/photobooth-data/media"), + ]: + if media_dir_candidate.exists(): + for f in media_dir_candidate.iterdir(): + if f.is_file(): + mt = f.stat().st_mtime + mtime_index[f.name] = mt # uuid.jpg + mtime_index[f.stem] = mt # uuid + break + def _in_event(p: dict) -> bool: pid = _get_id(p) - mtime = mtime_index.get(pid, mtime_index.get(pid + ".jpg", 0.0)) - return started_at <= mtime <= ended_at + stem = pid.rsplit(".", 1)[0] if "." in pid else pid + mt = mtime_index.get(pid) or mtime_index.get(stem) or 0.0 + return mt > 0 and started_at <= mt <= ended_at photos = [p for p in photos if _in_event(p)] total = len(photos) diff --git a/backend/services/event_service.py b/backend/services/event_service.py index 69422a5..4fcea19 100644 --- a/backend/services/event_service.py +++ b/backend/services/event_service.py @@ -80,6 +80,14 @@ class EventService: ) await self._db.commit() + async def archive_all_open(self): + """Marque TOUS les événements ouverts comme terminés.""" + await self._db.execute( + "UPDATE events SET ended_at = ? WHERE ended_at IS NULL", + (datetime.now().timestamp(),), + ) + await self._db.commit() + async def increment(self, slug: str, counter: str): """Incrémente un compteur de l'événement identifié par son slug. diff --git a/frontend/templates/admin/events.html b/frontend/templates/admin/events.html index 3910959..9d6efaa 100644 --- a/frontend/templates/admin/events.html +++ b/frontend/templates/admin/events.html @@ -142,8 +142,13 @@ async function loadCurrentEvent() { } } +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'); @@ -154,13 +159,16 @@ async function loadHistory() { return; } - list.innerHTML = d.events.map(ev => ` + 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 `