Galerie booth : QR avec auto-login, event_id aleatoire, flyer imprimable

- QR code inclut le mot de passe (?p=XXXX) pour acces direct sans saisie
- Supprime le code texte visible sur l'ecran d'accueil, QR plus petit
- Bouton admin 'Imprimer flyer QR galerie' genere un 10x15 avec
  nom evenement, QR code, texte invitation, design assorti
- Auto-login ajoute cote serveur booth (booth.copydev.fr)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 20:07:52 +02:00
parent 4d2d3e7a93
commit 98c738cf79
6 changed files with 90 additions and 17 deletions

View File

@@ -819,6 +819,77 @@ async def api_qr_galerie():
return {"chemin": f"/data/exports/{chemin.name}"} return {"chemin": f"/data/exports/{chemin.name}"}
@app.post("/api/booth/imprimer-qr")
async def api_booth_imprimer_qr():
"""Genere et imprime un flyer QR pour la galerie booth."""
config = charger_config()
booth = config.get("booth", {})
event = config.get("evenement", {})
if not booth.get("actif") or not booth.get("url"):
return JSONResponse({"erreur": "Booth non configure"}, status_code=400)
info = recuperer_booth_password()
password = info.get("password", "")
event_id = booth.get("event_id", "default")
nom_event = event.get("nom", "Photobooth")
url_galerie = f"{booth['url']}/{event_id}?p={password}"
from PIL import Image, ImageDraw, ImageFont
import qrcode
W, H = 1200, 1800 # 10x15 portrait @ 300dpi
img = Image.new("RGB", (W, H), "#0a0a1a")
draw = ImageDraw.Draw(img)
try:
font_big = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 72)
font_med = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 42)
font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 32)
except OSError:
font_big = ImageFont.load_default()
font_med = font_big
font_small = font_big
# Titre evenement
draw.text((W // 2, 120), "📸", font=font_big, anchor="mt", fill="#e91e63")
draw.text((W // 2, 220), nom_event, font=font_big, anchor="mt", fill="#ffffff")
# Ligne decorative
draw.line([(150, 320), (W - 150, 320)], fill="#e91e63", width=3)
# Texte d'invitation
draw.text((W // 2, 400), "Retrouvez toutes les photos", font=font_med, anchor="mt", fill="#cccccc")
draw.text((W // 2, 460), "en direct sur la galerie !", font=font_med, anchor="mt", fill="#cccccc")
# QR code
qr = qrcode.QRCode(version=None, error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=12, border=3)
qr.add_data(url_galerie)
qr.make(fit=True)
qr_img = qr.make_image(fill_color="#ffffff", back_color="#0a0a1a").convert("RGB")
qr_size = 700
qr_img = qr_img.resize((qr_size, qr_size), Image.NEAREST)
img.paste(qr_img, ((W - qr_size) // 2, 540))
# Instruction
draw.text((W // 2, 1300), "Scannez ce QR code", font=font_med, anchor="mt", fill="#ffffff")
draw.text((W // 2, 1360), "avec votre téléphone", font=font_med, anchor="mt", fill="#ffffff")
# Ligne decorative bas
draw.line([(150, 1460), (W - 150, 1460)], fill="#e91e63", width=3)
draw.text((W // 2, 1520), "Les photos apparaissent en temps réel", font=font_small, anchor="mt", fill="#888888")
draw.text((W // 2, 1570), "Téléchargez-les directement !", font=font_small, anchor="mt", fill="#888888")
# Sauvegarder
chemin = DOSSIER_EXPORTS / "flyer_qr_booth.jpg"
img.save(str(chemin), "JPEG", quality=95)
# Imprimer
ok = imprimer(str(chemin), copies=1)
return {"succes": ok, "chemin": f"/data/exports/flyer_qr_booth.jpg"}
# --- API Galerie --- # --- API Galerie ---
@app.get("/api/galerie") @app.get("/api/galerie")

View File

@@ -299,16 +299,9 @@ html, body {
} }
.booth-qr { .booth-qr {
width: 270px; width: 100px;
height: 270px; height: 100px;
border-radius: 10px; border-radius: 8px;
}
.booth-code {
font-size: 1.4rem;
font-weight: bold;
color: rgba(255,255,255,0.7);
letter-spacing: 4px;
} }
/* === Capture / Preview live === */ /* === Capture / Preview live === */

View File

@@ -44,10 +44,9 @@
<div id="compteur-accueil" class="compteur-accueil cache"> <div id="compteur-accueil" class="compteur-accueil cache">
<span id="compteur-restant">400</span> / <span id="compteur-limite">400</span> <span id="compteur-restant">400</span> / <span id="compteur-limite">400</span>
</div> </div>
<!-- Booth : code + QR en bas a gauche --> <!-- Booth : QR discret en bas a gauche -->
<div id="booth-info" class="booth-info cache"> <div id="booth-info" class="booth-info cache">
<img id="booth-qr" class="booth-qr" src=""> <img id="booth-qr" class="booth-qr" src="">
<span id="booth-code" class="booth-code"></span>
</div> </div>
<!-- Icone admin coin bas-droit --> <!-- Icone admin coin bas-droit -->
<div id="btn-admin" class="btn-admin" onclick="ouvrirAdmin()">&#9881;</div> <div id="btn-admin" class="btn-admin" onclick="ouvrirAdmin()">&#9881;</div>
@@ -644,6 +643,7 @@
</div> </div>
<button class="btn-action" onclick="sauvegarderBooth()">Sauvegarder booth</button> <button class="btn-action" onclick="sauvegarderBooth()">Sauvegarder booth</button>
<button class="btn-secondaire" onclick="regenererBoothPassword()">Nouveau code</button> <button class="btn-secondaire" onclick="regenererBoothPassword()">Nouveau code</button>
<button class="btn-secondaire" onclick="imprimerFlyerQR()">&#128424; Imprimer flyer QR galerie</button>
<h3>Galerie locale</h3> <h3>Galerie locale</h3>
<div class="champ"> <div class="champ">

View File

@@ -922,6 +922,16 @@ async function chargerInfosSysteme() {
} }
} }
async function imprimerFlyerQR() {
afficherStatut('Generation du flyer QR...', 'succes');
const r = await apiPost('/api/booth/imprimer-qr', {});
if (r.succes) {
afficherStatut('Flyer QR envoye a l\'imprimante !', 'succes');
} else {
afficherStatut(r.erreur || 'Erreur impression flyer', 'erreur');
}
}
async function regenererBoothPassword() { async function regenererBoothPassword() {
const booth = config.booth || {}; const booth = config.booth || {};
const url = getValue('admin-booth-url').replace(/\/$/, ''); const url = getValue('admin-booth-url').replace(/\/$/, '');

View File

@@ -335,10 +335,8 @@ async function chargerBoothInfo() {
try { try {
const info = await apiGet('/api/booth/info'); const info = await apiGet('/api/booth/info');
if (info.password) { if (info.password) {
document.getElementById('booth-code').textContent = info.password;
// QR code vers le site booth
const eventId = booth.event_id || 'default'; const eventId = booth.event_id || 'default';
const qrUrl = `${booth.url}/${eventId}`; const qrUrl = `${booth.url}/${eventId}?p=${info.password}`;
document.getElementById('booth-qr').src = `/api/qr?url=${encodeURIComponent(qrUrl)}`; document.getElementById('booth-qr').src = `/api/qr?url=${encodeURIComponent(qrUrl)}`;
el.classList.remove('cache'); el.classList.remove('cache');
} }

View File

@@ -158,9 +158,10 @@ async function afficherQR() {
if (!zone.classList.contains('cache')) { if (!zone.classList.contains('cache')) {
const booth = config.booth || {}; const booth = config.booth || {};
if (booth.actif && booth.url) { if (booth.actif && booth.url) {
// QR vers la galerie live const info = await apiGet('/api/booth/info').catch(() => null);
const eventId = booth.event_id || 'default'; const eventId = booth.event_id || 'default';
const qrUrl = `${booth.url}/${eventId}`; const pw = info?.password || '';
const qrUrl = `${booth.url}/${eventId}?p=${pw}`;
document.getElementById('img-qr').src = `/api/qr?url=${encodeURIComponent(qrUrl)}`; document.getElementById('img-qr').src = `/api/qr?url=${encodeURIComponent(qrUrl)}`;
} else { } else {
const resultat = await apiGet('/api/qr/galerie'); const resultat = await apiGet('/api/qr/galerie');