first commit
🚀 Deploy — JH Photomaton / 🔍 Vérification (push) Has been cancelled
🚀 Deploy — JH Photomaton / 🍓 Deploy sur le Pi (push) Has been cancelled

This commit is contained in:
2026-07-16 00:55:29 +02:00
commit 209f81aa3d
100 changed files with 17682 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
{% extends "base.html" %}
{% block title %}Galerie admin — JH Photomaton{% endblock %}
{% block nav_links %}
<a href="/admin">Dashboard</a>
<a href="/admin/gallery" class="active">Galerie admin</a>
<a href="/admin/print">Impression</a>
<a href="/admin/actions">Actions</a>
<a href="/gallery">Galerie publique</a>
<a href="/admin/logout" style="margin-left:auto;color:var(--text-dim)">Déconnexion</a>
{% endblock %}
{% block content %}
<div class="container">
<div class="flex items-center justify-between mb-2">
<h1 class="page-title" style="margin:0">Galerie — Administration</h1>
<div class="flex gap-1 items-center">
<span class="text-sm text-muted" id="photo-count">Chargement…</span>
<div class="flex gap-1">
<input type="number" id="copies-input" min="1" max="3" value="1" class="form-control" style="width:70px" title="Copies">
<button class="btn btn-ghost btn-sm" onclick="refreshPhotos()">↻ Actualiser</button>
</div>
</div>
</div>
<!-- Filtres / 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 admin -->
<div class="lightbox" id="lightbox" onclick="closeLightbox(event)">
<button class="lightbox-close" onclick="closeLightbox()">&times;</button>
<img id="lightbox-img" src="" alt="">
<div class="lightbox-actions">
<button class="btn btn-success" id="lb-print-btn">🖨 Imprimer</button>
<a class="btn btn-ghost" id="lb-download-btn" download>⬇ Télécharger</a>
<button class="btn btn-danger" id="lb-delete-btn">🗑 Supprimer</button>
</div>
<div class="text-sm text-muted" id="lb-filename"></div>
</div>
{% endblock %}
{% block scripts %}
<script>
let currentPage = 1;
let totalPages = 1;
let currentPhotoId = null;
async function loadPhotos(page = 1) {
try {
const data = await api('GET', `/admin/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.length) {
grid.innerHTML = '<div class="empty-state"><div class="icon">📷</div>Aucune photo</div>';
return;
}
grid.innerHTML = photos.map(p => {
const pid = p.id || p.filename || p.uid || '';
return `
<div class="photo-card" onclick="openLightbox('${pid}', '${p.full_url}', '${p.thumb_url}')">
<img src="${p.thumb_url}" loading="lazy" alt="">
<div class="photo-card-actions">
<button class="btn btn-success btn-sm" onclick="event.stopPropagation();printPhoto('${pid}')">🖨</button>
<button class="btn btn-danger btn-sm" onclick="event.stopPropagation();deletePhoto('${pid}')">🗑</button>
<a class="btn btn-ghost btn-sm" href="${p.full_url}" download onclick="event.stopPropagation()">⬇</a>
</div>
</div>
`;
}).join('');
} catch(e) {
document.getElementById('photo-grid').innerHTML = '<div class="empty-state"><div class="icon">❌</div>Erreur de chargement</div>';
}
}
function changePage(delta) {
loadPhotos(currentPage + delta);
}
function refreshPhotos() { loadPhotos(currentPage); }
// ── Lightbox ──────────────────────────────────────────────────────────────────
function openLightbox(pid, fullUrl, thumbUrl) {
currentPhotoId = pid;
document.getElementById('lightbox-img').src = fullUrl || thumbUrl;
document.getElementById('lb-download-btn').href = fullUrl;
document.getElementById('lb-filename').textContent = pid;
document.getElementById('lb-print-btn').onclick = () => printPhoto(pid);
document.getElementById('lb-delete-btn').onclick = () => deletePhoto(pid);
document.getElementById('lightbox').classList.add('open');
}
function closeLightbox(e) {
if (e && e.target !== document.getElementById('lightbox') && e.type !== 'click') return;
document.getElementById('lightbox').classList.remove('open');
currentPhotoId = null;
}
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeLightbox(); });
// ── Actions ───────────────────────────────────────────────────────────────────
async function printPhoto(pid) {
const copies = parseInt(document.getElementById('copies-input').value) || 1;
if (!confirm(`Imprimer ${copies} copie(s) ?`)) return;
try {
const r = await api('POST', `/admin/api/gallery/print/${pid}?copies=${copies}`);
if (r.success) showToast('✅ Impression lancée sur ' + r.printer, 'success');
else if (r.ok) showToast('📋 Demande ajoutée à la file', 'info');
else showToast('❌ ' + (r.error || 'Erreur'), 'error');
} catch(e) { showToast('Erreur impression', 'error'); }
}
async function deletePhoto(pid) {
if (!confirm('Supprimer cette photo définitivement ?')) return;
try {
const r = await api('DELETE', `/admin/api/gallery/${pid}`);
showToast(r.ok ? '🗑 Photo supprimée' : '❌ Erreur suppression', r.ok ? 'success' : 'error');
if (r.ok) {
document.getElementById('lightbox').classList.remove('open');
loadPhotos(currentPage);
}
} catch(e) { showToast('Erreur suppression', 'error'); }
}
function onWsMessage(msg) {
if (msg.type === 'photo_deleted') loadPhotos(currentPage);
if (msg.type === 'print_result') showToast(msg.result.success ? '✅ Impression OK' : '❌ ' + msg.result.error, msg.result.success ? 'success' : 'error');
}
loadPhotos(1);
</script>
{% endblock %}