367 lines
14 KiB
HTML
367 lines
14 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Galerie photos — Photomaton LSDW{% endblock %}
|
||
|
||
{% block head %}
|
||
<style>
|
||
.pub-print-badge {
|
||
position: absolute; top: 6px; left: 6px;
|
||
background: rgba(30,160,70,.9); color: #fff;
|
||
border-radius: 12px; padding: .15rem .5rem;
|
||
font-size: .72rem; font-weight: 700;
|
||
pointer-events: none;
|
||
}
|
||
.photo-card { position: relative; }
|
||
|
||
/* ── Bouton imprimer sur la card ── */
|
||
.photo-card-actions { display: flex; gap: .35rem; flex-wrap: wrap; }
|
||
|
||
/* ── Bandeau quota ── */
|
||
#quota-bar {
|
||
display: none;
|
||
align-items: center;
|
||
gap: .6rem;
|
||
background: rgba(25,108,176,.12);
|
||
border: 1px solid rgba(25,108,176,.3);
|
||
border-radius: 8px;
|
||
padding: .45rem .85rem;
|
||
font-size: .88rem;
|
||
margin-bottom: .75rem;
|
||
}
|
||
#quota-bar.quota-warn {
|
||
background: rgba(224,123,0,.12);
|
||
border-color: rgba(224,123,0,.35);
|
||
}
|
||
#quota-bar.quota-empty {
|
||
background: rgba(192,0,0,.1);
|
||
border-color: rgba(192,0,0,.3);
|
||
}
|
||
|
||
/* ── Modal copies ── */
|
||
.modal-overlay {
|
||
display: none;
|
||
position: fixed; inset: 0;
|
||
background: rgba(0,0,0,.65);
|
||
z-index: 2000;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.modal-overlay.open { display: flex; }
|
||
.modal-box {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 14px;
|
||
padding: 1.5rem;
|
||
max-width: 340px;
|
||
width: 90%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
.modal-title { font-size: 1.05rem; font-weight: 700; }
|
||
.modal-preview { width: 100%; border-radius: 8px; object-fit: cover; max-height: 180px; }
|
||
.copies-row { display: flex; align-items: center; gap: .75rem; }
|
||
.copies-btn { background: var(--bg-muted); border: 1px solid var(--border); color: var(--text);
|
||
width: 2rem; height: 2rem; border-radius: 6px; cursor: pointer; font-size: 1.1rem;
|
||
display: flex; align-items: center; justify-content: center; }
|
||
.copies-val { font-size: 1.2rem; font-weight: 700; min-width: 1.5rem; text-align: center; }
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container">
|
||
<div class="flex items-center justify-between mb-2">
|
||
<h1 class="page-title" style="margin:0">📷 Vos photos</h1>
|
||
<div class="flex gap-1 items-center">
|
||
<span class="text-sm text-muted" id="photo-count">Chargement…</span>
|
||
<button class="btn btn-ghost btn-sm" onclick="loadPhotos(1)">↻</button>
|
||
</div>
|
||
</div>
|
||
<p class="text-sm text-muted mb-2">
|
||
Retrouvez vos photos ci-dessous. Scannez le QR code depuis la galerie du photomaton pour télécharger votre photo directement sur votre téléphone.
|
||
</p>
|
||
|
||
<!-- Bandeau quota -->
|
||
<div id="quota-bar">
|
||
<span id="quota-icon">🖨</span>
|
||
<span id="quota-text"></span>
|
||
</div>
|
||
|
||
<!-- Navigation pages -->
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<button class="btn btn-ghost btn-sm" id="prev-btn" onclick="changePage(-1)" disabled>← Préc.</button>
|
||
<span class="text-sm text-muted">Page <span id="page-cur">1</span> / <span id="page-total">1</span></span>
|
||
<button class="btn btn-ghost btn-sm" id="next-btn" onclick="changePage(1)">Suiv. →</button>
|
||
</div>
|
||
|
||
<div class="photo-grid" id="photo-grid">
|
||
<div class="empty-state"><div class="icon">⏳</div>Chargement…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Lightbox -->
|
||
<div class="lightbox" id="lightbox" onclick="closeLightbox(event)">
|
||
<button class="lightbox-close">×</button>
|
||
<img id="lightbox-img" src="" alt="Photo">
|
||
<div class="lightbox-actions">
|
||
<a class="btn btn-primary" id="lb-download">⬇ Télécharger la photo</a>
|
||
<button class="btn btn-primary" id="lb-print" style="display:none" onclick="openPrintModal()">🖨 Imprimer</button>
|
||
</div>
|
||
<div class="text-xs text-muted">Connectez-vous au WiFi Photomaton pour télécharger</div>
|
||
</div>
|
||
|
||
<!-- Modal sélection copies -->
|
||
<div class="modal-overlay" id="print-modal" onclick="closePrintModal(event)">
|
||
<div class="modal-box" onclick="event.stopPropagation()">
|
||
<div class="modal-title">🖨 Demande d'impression</div>
|
||
<img id="pm-preview" src="" alt="" class="modal-preview">
|
||
<div id="pm-copies-row" class="copies-row" style="display:none">
|
||
<span style="flex:1;font-size:.9rem">Nombre de copies</span>
|
||
<button class="copies-btn" onclick="changeCopies(-1)">−</button>
|
||
<span class="copies-val" id="pm-copies">1</span>
|
||
<button class="copies-btn" onclick="changeCopies(1)">+</button>
|
||
</div>
|
||
<p class="text-sm text-muted" id="pm-quota-info" style="margin:0"></p>
|
||
<div class="flex gap-1">
|
||
<button class="btn btn-primary" onclick="confirmPrint()" id="pm-confirm">🖨 Confirmer</button>
|
||
<button class="btn btn-ghost" onclick="closePrintModal()">Annuler</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
<script>
|
||
// ── État ──────────────────────────────────────────────────────────────────────
|
||
let currentPage = 1;
|
||
let totalPages = 1;
|
||
let printCfg = { user_print_enabled: false, user_print_quota: 0,
|
||
user_print_max_copies: 1, remaining_quota: -1 };
|
||
let currentPrintPhotoId = null;
|
||
let currentPrintThumb = null;
|
||
let currentCopies = 1;
|
||
|
||
// ── Quota ─────────────────────────────────────────────────────────────────────
|
||
async function loadPrintStatus() {
|
||
try {
|
||
const s = await api('GET', '/api/print/user-print-status');
|
||
printCfg = s;
|
||
updateQuotaBar();
|
||
} catch(e) {}
|
||
}
|
||
|
||
function updateQuotaBar() {
|
||
const bar = document.getElementById('quota-bar');
|
||
const text = document.getElementById('quota-text');
|
||
const icon = document.getElementById('quota-icon');
|
||
|
||
if (!printCfg.user_print_enabled) { bar.style.display = 'none'; return; }
|
||
|
||
bar.style.display = 'flex';
|
||
bar.className = '';
|
||
|
||
if (printCfg.user_print_quota === 0) {
|
||
// Illimité — pas besoin d'afficher le bandeau
|
||
bar.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const rem = printCfg.remaining_quota;
|
||
if (rem <= 0) {
|
||
bar.classList.add('quota-empty');
|
||
icon.textContent = '🚫';
|
||
text.textContent = `Vous avez utilisé votre quota d'impression (${printCfg.user_print_quota} max).`;
|
||
} else if (rem === 1) {
|
||
bar.classList.add('quota-warn');
|
||
icon.textContent = '⚠️';
|
||
text.textContent = `Dernière impression disponible pour cette session.`;
|
||
} else {
|
||
icon.textContent = '🖨';
|
||
text.textContent = `${rem} impression(s) restante(s) sur ${printCfg.user_print_quota}.`;
|
||
}
|
||
}
|
||
|
||
// ── Photos ────────────────────────────────────────────────────────────────────
|
||
async function loadPhotos(page = 1) {
|
||
try {
|
||
const data = await api('GET', `/api/gallery/photos?page=${page}&limit=24`);
|
||
const { photos, total, pages } = data;
|
||
|
||
currentPage = page;
|
||
totalPages = pages;
|
||
|
||
document.getElementById('photo-count').textContent = `${total} photo(s)`;
|
||
document.getElementById('page-cur').textContent = page;
|
||
document.getElementById('page-total').textContent = pages;
|
||
document.getElementById('prev-btn').disabled = page <= 1;
|
||
document.getElementById('next-btn').disabled = page >= pages;
|
||
|
||
const grid = document.getElementById('photo-grid');
|
||
|
||
if (!photos || !photos.length) {
|
||
grid.innerHTML = `
|
||
<div class="empty-state" style="grid-column:1/-1">
|
||
<div class="icon">📷</div>
|
||
Aucune photo pour l'instant — prenez la pose !
|
||
</div>`;
|
||
return;
|
||
}
|
||
|
||
const canPrint = printCfg.user_print_enabled &&
|
||
(printCfg.user_print_quota === 0 || printCfg.remaining_quota > 0);
|
||
|
||
grid.innerHTML = photos.map(p => {
|
||
const copies = p.print_done_copies || 0;
|
||
const printedBadge = copies > 0
|
||
? `<div class="pub-print-badge">🖨 ${copies}×</div>`
|
||
: '';
|
||
const printBtn = printCfg.user_print_enabled
|
||
? `<button class="btn btn-sm ${canPrint ? 'btn-ghost' : ''}"
|
||
style="${canPrint ? '' : 'opacity:.4;cursor:not-allowed'}"
|
||
onclick="event.stopPropagation();${canPrint ? `askPrint('${p.id}','${p.thumb_url}')` : 'showToast(\'Quota atteint\',\'error\')'}"
|
||
title="Imprimer cette photo">🖨</button>`
|
||
: '';
|
||
return `
|
||
<div class="photo-card" onclick="openLightbox('${p.full_url}','${p.download_url}','${p.id}','${p.thumb_url}')">
|
||
<img src="${p.thumb_url}" loading="lazy" alt="Photo" onerror="this.src='${p.full_url}'">
|
||
${printedBadge}
|
||
<div class="photo-card-actions">
|
||
<a class="btn btn-primary btn-sm" href="${p.download_url}" onclick="event.stopPropagation()">⬇ Télécharger</a>
|
||
${printBtn}
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
} catch(e) {
|
||
document.getElementById('photo-grid').innerHTML = `
|
||
<div class="empty-state" style="grid-column:1/-1">
|
||
<div class="icon">⚠️</div>
|
||
Impossible de charger les photos. Êtes-vous connecté au WiFi Photomaton ?
|
||
</div>`;
|
||
}
|
||
}
|
||
|
||
function changePage(delta) { loadPhotos(currentPage + delta); }
|
||
|
||
// ── Lightbox ──────────────────────────────────────────────────────────────────
|
||
function openLightbox(fullUrl, downloadUrl, photoId, thumbUrl) {
|
||
document.getElementById('lightbox-img').src = fullUrl;
|
||
document.getElementById('lb-download').href = downloadUrl;
|
||
|
||
const lbPrint = document.getElementById('lb-print');
|
||
if (printCfg.user_print_enabled) {
|
||
lbPrint.style.display = '';
|
||
const canPrint = printCfg.user_print_quota === 0 || printCfg.remaining_quota > 0;
|
||
lbPrint.disabled = !canPrint;
|
||
lbPrint.title = canPrint ? 'Demander une impression' : 'Quota atteint';
|
||
lbPrint.onclick = () => askPrint(photoId, thumbUrl);
|
||
} else {
|
||
lbPrint.style.display = 'none';
|
||
}
|
||
|
||
document.getElementById('lightbox').classList.add('open');
|
||
}
|
||
|
||
function closeLightbox(e) {
|
||
if (!e || e.target.id === 'lightbox' || e.target.classList.contains('lightbox-close')) {
|
||
document.getElementById('lightbox').classList.remove('open');
|
||
}
|
||
}
|
||
document.querySelector('.lightbox-close').addEventListener('click', closeLightbox);
|
||
document.addEventListener('keydown', e => { if (e.key === 'Escape') { closeLightbox({}); closePrintModal(); } });
|
||
|
||
// ── Modal impression ──────────────────────────────────────────────────────────
|
||
function askPrint(photoId, thumbUrl) {
|
||
currentPrintPhotoId = photoId;
|
||
currentPrintThumb = thumbUrl;
|
||
currentCopies = 1;
|
||
|
||
document.getElementById('pm-preview').src = thumbUrl;
|
||
document.getElementById('pm-copies').textContent = '1';
|
||
|
||
// Afficher le sélecteur de copies seulement si max > 1
|
||
const copiesRow = document.getElementById('pm-copies-row');
|
||
copiesRow.style.display = printCfg.user_print_max_copies > 1 ? 'flex' : 'none';
|
||
|
||
// Info quota
|
||
const info = document.getElementById('pm-quota-info');
|
||
if (printCfg.user_print_quota > 0) {
|
||
const rem = printCfg.remaining_quota;
|
||
info.textContent = `Il vous reste ${rem} impression(s) pour cette session.`;
|
||
} else {
|
||
info.textContent = '';
|
||
}
|
||
|
||
document.getElementById('print-modal').classList.add('open');
|
||
}
|
||
|
||
function openPrintModal() { /* appelé depuis le bouton lightbox — déjà configuré via askPrint */ }
|
||
|
||
function closePrintModal(e) {
|
||
if (!e || e.target.id === 'print-modal') {
|
||
document.getElementById('print-modal').classList.remove('open');
|
||
}
|
||
}
|
||
|
||
function changeCopies(delta) {
|
||
currentCopies = Math.max(1, Math.min(printCfg.user_print_max_copies, currentCopies + delta));
|
||
document.getElementById('pm-copies').textContent = currentCopies;
|
||
}
|
||
|
||
async function confirmPrint() {
|
||
if (!currentPrintPhotoId) return;
|
||
const btn = document.getElementById('pm-confirm');
|
||
btn.disabled = true;
|
||
btn.textContent = '⏳ Envoi…';
|
||
|
||
try {
|
||
const r = await api('POST', '/api/print/request-gallery', {
|
||
photo_id: currentPrintPhotoId,
|
||
copies: currentCopies,
|
||
});
|
||
// Mettre à jour le quota local
|
||
printCfg.remaining_quota = r.remaining_quota;
|
||
printCfg.session_print_count = (printCfg.session_print_count || 0) + 1;
|
||
updateQuotaBar();
|
||
|
||
const modeMsg = r.mode === 'direct'
|
||
? 'L\'impression va démarrer.'
|
||
: 'Votre demande a bien été transmise à l\'admin.';
|
||
showToast(`✅ Demande envoyée ! ${modeMsg}`, 'success', 5000);
|
||
closePrintModal();
|
||
document.getElementById('lightbox').classList.remove('open');
|
||
// Recharger les photos pour mettre à jour le badge imprimé
|
||
setTimeout(() => loadPhotos(currentPage), 1500);
|
||
} catch(e) {
|
||
const msg = e.message || 'Erreur';
|
||
if (msg.includes('429')) {
|
||
showToast('🚫 Quota atteint — vous ne pouvez plus imprimer pour cette session.', 'error', 6000);
|
||
printCfg.remaining_quota = 0;
|
||
updateQuotaBar();
|
||
} else if (msg.includes('403')) {
|
||
showToast('L\'impression est désactivée.', 'error');
|
||
} else {
|
||
showToast('❌ Erreur : ' + msg, 'error');
|
||
}
|
||
closePrintModal();
|
||
} finally {
|
||
btn.disabled = false;
|
||
btn.textContent = '🖨 Confirmer';
|
||
}
|
||
}
|
||
|
||
// ── WebSocket ─────────────────────────────────────────────────────────────────
|
||
function onWsMessage(msg) {
|
||
if (msg.type === 'photobooth_event' && msg.event === 'finished') {
|
||
setTimeout(() => loadPhotos(1), 2000);
|
||
}
|
||
}
|
||
|
||
// ── Init ──────────────────────────────────────────────────────────────────────
|
||
(async () => {
|
||
await loadPrintStatus();
|
||
loadPhotos(1);
|
||
})();
|
||
|
||
setInterval(() => loadPhotos(currentPage), 30000);
|
||
</script>
|
||
{% endblock %}
|