/* Module administration - Backoffice complet */ // Mapping toggle ID -> cle config fonctionnalites const TOGGLES_MAP = { 'tog-photo-simple': 'photo_simple', 'tog-multi-shot': 'multi_shot', 'tog-filtres': 'filtres', 'tog-overlays': 'overlays', 'tog-chroma-key': 'chroma_key', 'tog-impression': 'impression', 'tog-email': 'email', 'tog-qr-code': 'qr_code', 'tog-galerie': 'galerie', }; async function chargerAdmin() { config = await apiGet('/api/config'); chargerMateriel(); chargerCompteur(); chargerDestinations(); chargerCadres(); chargerCadresImpression(); chargerAnimations(); chargerFonctionnalites(); chargerEmailAdmin(); chargerFondsAdmin(); chargerGalerieAdmin(); chargerBoothAdmin(); chargerEvenementAdmin(); } // === MATERIEL === async function chargerMateriel() { const cam = config.camera || {}; const imp = config.impression || {}; // Camera const statut = await apiGet('/api/camera/statut'); const badge = document.getElementById('admin-camera-statut'); badge.textContent = statut.connectee ? 'Connectee' : 'Deconnectee'; badge.style.background = statut.connectee ? 'rgba(76,175,80,0.2)' : 'rgba(244,67,54,0.2)'; badge.style.color = statut.connectee ? 'var(--succes)' : 'var(--danger)'; // Liste appareils const selectApp = document.getElementById('admin-appareil'); selectApp.innerHTML = ''; for (const a of statut.appareils) { const opt = document.createElement('option'); opt.value = a.source; opt.textContent = `${a.nom} [${a.type}]`; if (a.source === statut.mode || a.source === cam.appareil) opt.selected = true; selectApp.appendChild(opt); } if (cam.appareil) selectApp.value = cam.appareil; setValue('admin-car', cam.compte_a_rebours || 3); document.getElementById('tog-flash-integre').checked = cam.flash_integre !== false; // Imprimantes await rafraichirImprimantes(); rafraichirStatutImprimante(); if (imp.imprimante) document.getElementById('admin-imprimante').value = imp.imprimante; if (imp.format) document.getElementById('admin-format-papier').value = imp.format; const fmt = (imp.format || '15x20').replace('-2up', ''); const orient = (imp.orientations || {})[fmt] || 'portrait'; document.querySelector(`input[name="admin-orientation"][value="${orient}"]`).checked = true; setValue('admin-copies-max', imp.copies_max || 5); document.getElementById('tog-rembobinage-ruban').checked = imp.rembobinage_ruban || false; } async function rafraichirImprimantes() { const imprimantes = await apiGet('/api/imprimantes'); const select = document.getElementById('admin-imprimante'); select.innerHTML = ''; for (const p of imprimantes) { const opt = document.createElement('option'); opt.value = p.nom; opt.textContent = `${p.nom} (${p.statut})`; select.appendChild(opt); } } async function reconnecterCamera() { const source = getValue('admin-appareil') || null; const resultat = await apiPost('/api/camera/reconnecter', { source }); chargerMateriel(); } function _getOrientations() { const fmt = (getValue('admin-format-papier') || '15x20').replace('-2up', ''); const orient = document.querySelector('input[name="admin-orientation"]:checked')?.value || 'portrait'; // On conserve les orientations des autres formats depuis la config courante const existing = (config.impression || {}).orientations || {}; return { ...existing, [fmt]: orient }; } async function couperPapier() { afficherStatut('Envoi commande coupe...', 'succes'); const r = await apiPost('/api/imprimante/couper', {}); afficherStatut(r.message, r.succes ? 'succes' : 'erreur'); } async function resetUsb() { afficherStatut('Réinitialisation USB...', 'succes'); const r = await apiPost('/api/imprimante/reset-usb', {}); afficherStatut(r.message, r.succes ? 'succes' : 'erreur'); } async function evacuerBourrage() { const r = await apiPost('/api/imprimante/evacuer', {}); afficherStatut(r.message || 'Imprimante réactivée', r.succes ? 'succes' : 'erreur'); const el = document.getElementById('imprimante-erreur-msg'); const bloc = document.getElementById('imprimante-statut-detail'); if (r.message && r.message !== 'Imprimante réactivée') { el.textContent = '⚠ ' + r.message; bloc.style.display = 'block'; } else { bloc.style.display = 'none'; } } async function rafraichirStatutImprimante() { const r = await apiGet('/api/imprimante/statut-detail').catch(() => null); if (!r) return; const el = document.getElementById('imprimante-erreur-msg'); const bloc = document.getElementById('imprimante-statut-detail'); if (r.derniere_erreur) { let msg = r.derniere_erreur; if (msg.includes('media does not match')) msg = '⚠ Format papier incorrect — vérifiez que le format configuré correspond à la cassette chargée dans l\'imprimante'; else if (msg.includes('jam')) msg = '⚠ Bourrage papier détecté'; else msg = '⚠ ' + msg.split(']').pop().trim(); el.textContent = msg; bloc.style.display = 'block'; } else { bloc.style.display = 'none'; } } async function sauvegarderMateriel() { const flashIntegre = document.getElementById('tog-flash-integre').checked; await apiPost('/api/config', { camera: { appareil: getValue('admin-appareil'), compte_a_rebours: parseInt(getValue('admin-car')) || 3, flash_integre: flashIntegre, }, impression: { imprimante: getValue('admin-imprimante'), format: getValue('admin-format-papier'), copies_max: parseInt(getValue('admin-copies-max')) || 5, orientations: _getOrientations(), rembobinage_ruban: document.getElementById('tog-rembobinage-ruban').checked, }, }); afficherStatut('Materiel sauvegarde', 'succes'); } // === COMPTEUR === async function chargerCompteur() { const etat = await apiGet('/api/compteur'); document.getElementById('tog-compteur-actif').checked = etat.actif; document.getElementById('admin-compteur-restant').textContent = etat.restantes; document.getElementById('admin-compteur-limite-display').textContent = etat.limite; setValue('admin-compteur-limite', etat.limite); } async function sauvegarderCompteur() { await apiPost('/api/config', { compteur: { actif: document.getElementById('tog-compteur-actif').checked, limite: parseInt(getValue('admin-compteur-limite')) || 400, }, }); chargerCompteur(); afficherStatut('Compteur sauvegarde', 'succes'); } async function resetCompteur() { if (confirm('Remettre le compteur a zero ?')) { await apiPost('/api/compteur/reset'); chargerCompteur(); afficherStatut('Compteur remis a zero', 'succes'); } } // === DESTINATIONS === function chargerDestinations() { const dest = config.destinations || {}; const booth = config.booth || {}; document.getElementById('tog-dest-memoire').checked = dest.memoire_interne !== false; document.getElementById('tog-dest-usb').checked = dest.cle_usb || false; document.getElementById('tog-dest-ftp').checked = dest.ftp || false; document.getElementById('tog-dest-galerie').checked = booth.actif || false; document.getElementById('tog-dest-email-auto').checked = dest.email_auto || false; // Sous-configs visibles si actives toggleSousConfig('tog-dest-usb', 'dest-usb-details'); toggleSousConfig('tog-dest-ftp', 'dest-ftp-details'); toggleSousConfig('tog-dest-galerie', 'dest-galerie-details'); setValue('admin-ftp-host', dest.ftp_host); setValue('admin-ftp-port', dest.ftp_port || 21); setValue('admin-ftp-user', dest.ftp_user); setValue('admin-ftp-pass', dest.ftp_password); setValue('admin-ftp-chemin', dest.ftp_chemin || '/photobooth'); // Booth fields setValue('admin-booth-url', booth.url || 'https://booth.copydev.fr'); setValue('admin-booth-apikey', booth.api_key || 'booth-photobooth-2026'); setValue('admin-booth-eventid', booth.event_id || 'default'); // Radio sauvegarder tout / imprimees if (dest.sauvegarder_tout === false) { document.getElementById('sauv-imprimees').checked = true; } else { document.getElementById('sauv-tout').checked = true; } // Listeners pour afficher/cacher sous-configs ['tog-dest-usb', 'tog-dest-ftp', 'tog-dest-galerie'].forEach(id => { const el = document.getElementById(id); el.onchange = () => { const map = { 'tog-dest-usb': 'dest-usb-details', 'tog-dest-ftp': 'dest-ftp-details', 'tog-dest-galerie': 'dest-galerie-details' }; toggleSousConfig(id, map[id]); }; }); } function toggleSousConfig(toggleId, detailsId) { const checked = document.getElementById(toggleId).checked; const el = document.getElementById(detailsId); if (checked) el.classList.remove('cache'); else el.classList.add('cache'); } async function detecterUSB() { const usbs = await apiGet('/api/usb/detecter'); const select = document.getElementById('admin-chemin-usb'); select.innerHTML = ''; if (usbs.length === 0) { const opt = document.createElement('option'); opt.value = ''; opt.textContent = 'Aucune cle detectee'; select.appendChild(opt); } else { for (const u of usbs) { const opt = document.createElement('option'); opt.value = u; opt.textContent = u; select.appendChild(opt); } } } async function sauvegarderDestinations() { await apiPost('/api/config', { destinations: { memoire_interne: document.getElementById('tog-dest-memoire').checked, cle_usb: document.getElementById('tog-dest-usb').checked, chemin_usb: getValue('admin-chemin-usb') || '/media/usb', ftp: document.getElementById('tog-dest-ftp').checked, ftp_host: getValue('admin-ftp-host'), ftp_port: parseInt(getValue('admin-ftp-port')) || 21, ftp_user: getValue('admin-ftp-user'), ftp_password: getValue('admin-ftp-pass'), ftp_chemin: getValue('admin-ftp-chemin') || '/photobooth', email_auto: document.getElementById('tog-dest-email-auto').checked, sauvegarder_tout: document.getElementById('sauv-tout').checked, }, booth: { actif: document.getElementById('tog-dest-galerie').checked, url: getValue('admin-booth-url'), api_key: getValue('admin-booth-apikey'), event_id: getValue('admin-booth-eventid'), }, }); afficherStatut('Destinations sauvegardees', 'succes'); } // === CADRES IMPRESSION (par format) === const FORMATS_IMPRESSION = ['strip', '10x15', '15x20']; async function chargerCadresImpression() { for (const fmt of FORMATS_IMPRESSION) { await chargerCadresFormat(fmt); } } async function chargerCadresFormat(fmt) { const data = await apiGet(`/api/cadres-impression/${fmt}`); const liste = document.getElementById(`cadres-liste-${fmt}`); liste.innerHTML = ''; // Option "Aucun cadre" const divAucun = _creerItemCadreImpression(fmt, null, data.actif); liste.appendChild(divAucun); if (data.disponibles.length === 0) { const p = document.createElement('p'); p.className = 'texte-secondaire'; p.textContent = 'Aucun cadre importe'; liste.appendChild(p); return; } for (const nom of data.disponibles) { const div = _creerItemCadreImpression(fmt, nom, data.actif); liste.appendChild(div); } } function _creerItemCadreImpression(fmt, nom, actif) { const div = document.createElement('div'); div.className = 'cadre-imp-item'; const rb = document.createElement('input'); rb.type = 'radio'; rb.name = `cadre-imp-${fmt}`; rb.value = nom || ''; rb.checked = (actif === nom); rb.onchange = () => activerCadreFormat(fmt, nom); if (nom) { const img = document.createElement('img'); img.src = `/assets/cadres/${fmt}/${nom}`; img.alt = nom; img.className = 'cadre-preview'; const btnDel = document.createElement('button'); btnDel.className = 'btn-danger btn-icone'; btnDel.title = 'Supprimer'; btnDel.textContent = '✕'; btnDel.onclick = () => supprimerCadreFormat(fmt, nom); const span = document.createElement('span'); span.textContent = nom.replace('.png', ''); div.appendChild(rb); div.appendChild(img); div.appendChild(span); div.appendChild(btnDel); } else { const span = document.createElement('span'); span.textContent = 'Aucun cadre'; div.appendChild(rb); div.appendChild(span); } return div; } async function activerCadreFormat(fmt, nom) { await apiPost(`/api/cadres-impression/${fmt}/actif`, { nom: nom || null }); afficherStatut(`Cadre ${fmt} mis a jour`, 'succes'); } async function supprimerCadreFormat(fmt, nom) { await fetch(`/api/cadres-impression/${fmt}/${encodeURIComponent(nom)}`, { method: 'DELETE' }); await chargerCadresFormat(fmt); afficherStatut('Cadre supprime', 'succes'); } async function uploaderCadreFormat(fmt) { const input = document.getElementById(`input-cadre-${fmt}`); if (!input.files.length) return; const formData = new FormData(); formData.append('fichier', input.files[0]); const r = await fetch(`/api/upload/cadre/${fmt}`, { method: 'POST', body: formData }); if (r.ok) { input.value = ''; await chargerCadresFormat(fmt); afficherStatut(`Cadre ${fmt} importe`, 'succes'); } else { afficherStatut('Erreur import cadre', 'erreur'); } } // === CADRES OVERLAYS PHOTO === async function chargerCadres() { const data = await apiGet('/api/cadres'); const liste = document.getElementById('liste-cadres'); liste.innerHTML = ''; if (data.tous.length === 0) { liste.innerHTML = '
Aucun cadre importe
'; return; } for (const nom of data.tous) { const div = document.createElement('div'); div.className = 'cadre-item'; const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = data.actifs.includes(nom); cb.onchange = () => majCadresActifs(); const img = document.createElement('img'); img.src = `/assets/overlays/${nom}`; img.alt = nom; const span = document.createElement('span'); span.textContent = nom.replace('.png', ''); div.appendChild(cb); div.appendChild(img); div.appendChild(span); liste.appendChild(div); } } async function majCadresActifs() { const items = document.querySelectorAll('.cadre-item input[type="checkbox"]'); const noms = document.querySelectorAll('.cadre-item span'); const actifs = []; items.forEach((cb, i) => { if (cb.checked) actifs.push(noms[i].textContent + '.png'); }); await apiPost('/api/cadres', { actifs }); } async function uploaderCadre() { const input = document.getElementById('input-cadre'); if (!input.files.length) return; const formData = new FormData(); formData.append('fichier', input.files[0]); await fetch('/api/upload/overlay', { method: 'POST', body: formData }); input.value = ''; chargerCadres(); afficherStatut('Cadre importe', 'succes'); } // === FONDS VERTS === async function chargerFondsAdmin() { const fonds = await apiGet('/api/fonds'); const liste = document.getElementById('liste-fonds'); liste.innerHTML = ''; if (fonds.length === 0) { liste.innerHTML = 'Aucun fond importe
'; return; } for (const nom of fonds) { const div = document.createElement('div'); div.className = 'fond-item'; const img = document.createElement('img'); img.src = `/assets/backgrounds/${nom}`; img.alt = nom; const btnSuppr = document.createElement('button'); btnSuppr.className = 'btn-supprimer'; btnSuppr.innerHTML = '✕'; btnSuppr.onclick = () => supprimerFond(nom); div.appendChild(img); div.appendChild(btnSuppr); liste.appendChild(div); } } async function uploaderFond() { const input = document.getElementById('input-fond'); if (!input.files.length) return; const formData = new FormData(); formData.append('fichier', input.files[0]); await fetch('/api/upload/fond', { method: 'POST', body: formData }); input.value = ''; chargerFondsAdmin(); afficherStatut('Fond importe', 'succes'); } async function supprimerFond(nom) { if (!confirm(`Supprimer le fond "${nom}" ?`)) return; await fetch(`/api/fonds/${encodeURIComponent(nom)}`, { method: 'DELETE' }); chargerFondsAdmin(); } // === ANIMATIONS === const EMOJI_MAP = { 3: '🤪', 2: '😱', 1: '🔥' }; async function chargerAnimations() { // Animations CSS internes const anims = await apiGet('/api/animations'); const cssActives = config.camera?.animations_css_actives || ['classique']; const liste = document.getElementById('liste-animations'); liste.innerHTML = ''; for (const [id, nom] of Object.entries(anims)) { const div = document.createElement('div'); div.className = 'anim-item'; const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = cssActives.includes(id); cb.dataset.animId = id; cb.onchange = () => majAnimationsCss(); const label = document.createElement('span'); label.textContent = nom; div.appendChild(cb); div.appendChild(label); liste.appendChild(div); } // Animations custom (video/GIF) await chargerAnimationsCustom(); } async function chargerAnimationsCustom() { const data = await apiGet('/api/animations/custom'); const liste = document.getElementById('liste-animations-custom'); liste.innerHTML = ''; if (data.tous.length === 0) { liste.innerHTML = 'Aucune animation importee
'; return; } for (const nom of data.tous) { const div = document.createElement('div'); div.className = 'anim-item'; const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = data.actives.includes(nom); cb.dataset.animNom = nom; cb.onchange = () => majAnimationsCustom(); // Preview miniature const ext = nom.split('.').pop().toLowerCase(); let preview; if (ext === 'gif') { preview = document.createElement('img'); preview.src = `/assets/animations/${nom}`; preview.className = 'anim-item-preview'; } else { preview = document.createElement('video'); preview.src = `/assets/animations/${nom}`; preview.className = 'anim-item-preview'; preview.muted = true; preview.preload = 'metadata'; } const label = document.createElement('span'); label.textContent = nom; const btnSuppr = document.createElement('button'); btnSuppr.className = 'btn-supprimer'; btnSuppr.innerHTML = '✕'; btnSuppr.onclick = () => supprimerAnimation(nom); div.appendChild(cb); div.appendChild(preview); div.appendChild(label); div.appendChild(btnSuppr); liste.appendChild(div); } } async function majAnimationsCss() { const checkboxes = document.querySelectorAll('#liste-animations .anim-item input[type="checkbox"]'); const actives = []; checkboxes.forEach(cb => { if (cb.checked) actives.push(cb.dataset.animId); }); // Au moins une doit rester active if (actives.length === 0) actives.push('classique'); await apiPost('/api/config', { camera: { animations_css_actives: actives } }); } async function majAnimationsCustom() { const checkboxes = document.querySelectorAll('#liste-animations-custom .anim-item input[type="checkbox"]'); const actives = []; checkboxes.forEach(cb => { if (cb.checked) actives.push(cb.dataset.animNom); }); await apiPost('/api/animations/custom', { actives }); } async function uploaderAnimation() { const input = document.getElementById('input-animation'); if (!input.files.length) return; const formData = new FormData(); formData.append('fichier', input.files[0]); await fetch('/api/upload/animation', { method: 'POST', body: formData }); input.value = ''; chargerAnimationsCustom(); afficherStatut('Animation importee', 'succes'); } async function supprimerAnimation(nom) { if (!confirm(`Supprimer l'animation "${nom}" ?`)) return; await fetch(`/api/animations/custom/${encodeURIComponent(nom)}`, { method: 'DELETE' }); chargerAnimationsCustom(); } function testerAnimationPreview() { const chiffre = document.getElementById('preview-anim-chiffre'); const video = document.getElementById('preview-anim-video'); // Verifier s'il y a des animations custom actives const customActives = []; document.querySelectorAll('#liste-animations-custom .anim-item input[type="checkbox"]:checked').forEach(cb => { customActives.push(cb.dataset.animNom); }); // Verifier les CSS actives const cssActives = []; document.querySelectorAll('#liste-animations .anim-item input[type="checkbox"]:checked').forEach(cb => { cssActives.push(cb.dataset.animId); }); const toutes = [...cssActives.map(a => ({type: 'css', id: a})), ...customActives.map(a => ({type: 'custom', nom: a}))]; if (toutes.length === 0) return; const choix = toutes[Math.floor(Math.random() * toutes.length)]; if (choix.type === 'custom') { // Jouer la video/gif chiffre.classList.add('cache'); video.classList.remove('cache'); video.src = `/assets/animations/${choix.nom}`; video.currentTime = 0; video.play(); video.onended = () => { video.classList.add('cache'); chiffre.classList.remove('cache'); }; // Pour les GIF, masquer apres 3s if (choix.nom.endsWith('.gif')) { setTimeout(() => { video.classList.add('cache'); chiffre.classList.remove('cache'); }, 3000); } } else { // Animation CSS video.classList.add('cache'); chiffre.classList.remove('cache'); let count = 3; function afficher() { if (choix.id === 'emoji') { chiffre.textContent = EMOJI_MAP[count] || count; } else { chiffre.textContent = count; } chiffre.className = 'anim-chiffre'; void chiffre.offsetWidth; chiffre.classList.add('anim-' + choix.id); count--; if (count > 0) setTimeout(afficher, 1000); } afficher(); } } // === FONCTIONNALITES === function chargerFonctionnalites() { const fonc = config.fonctionnalites || {}; for (const [id, cle] of Object.entries(TOGGLES_MAP)) { const el = document.getElementById(id); if (el) el.checked = fonc[cle] !== false; } setupToggleListeners(); } function setupToggleListeners() { for (const [id, cle] of Object.entries(TOGGLES_MAP)) { const el = document.getElementById(id); if (!el) continue; const nouveau = el.cloneNode(true); el.parentNode.replaceChild(nouveau, el); nouveau.addEventListener('change', () => { apiPost('/api/config', { fonctionnalites: { [cle]: nouveau.checked } }); }); } } // === EVENEMENT === let eventEditId = null; async function chargerEvenementAdmin() { await chargerListeEvenements(); const event = config.evenement || {}; if (event.event_id) { eventEditId = event.event_id; afficherDetailEvent(event); } } async function chargerListeEvenements() { const events = await apiGet('/api/evenements'); const liste = document.getElementById('liste-evenements'); liste.innerHTML = ''; const actifId = (config.evenement || {}).event_id; for (const ev of events) { const div = document.createElement('div'); div.className = 'event-item' + (ev.id === actifId ? ' event-actif' : ''); div.innerHTML = ` ${ev.nom} ${ev.id !== actifId ? `` : 'Actif'} `; liste.appendChild(div); } if (events.length === 0) { liste.innerHTML = 'Aucun evenement. Creez-en un ci-dessus.
'; } } async function creerEvenementAdmin() { const nom = document.getElementById('input-nouveau-event').value.trim(); if (!nom) return; const event = await apiPost('/api/evenements', { nom }); document.getElementById('input-nouveau-event').value = ''; await activerEventAdmin(event.id); } async function activerEventAdmin(id) { await apiPost(`/api/evenements/${id}/activer`); config = await apiGet('/api/config'); eventEditId = id; await chargerListeEvenements(); afficherDetailEvent(config.evenement); appliquerConfig(); afficherStatut('Evenement active', 'succes'); } async function editerEventAdmin(id) { const event = await apiGet(`/api/evenements/${id}`); if (!event) return; eventEditId = id; afficherDetailEvent(event); } function afficherDetailEvent(event) { document.getElementById('titre-event-detail').classList.remove('cache'); document.getElementById('detail-evenement').classList.remove('cache'); document.getElementById('admin-theme').value = event.theme || 'base'; setValue('admin-nom-event', event.nom); setValue('admin-couleur-primaire', event.couleur_primaire || '#e91e63'); setValue('admin-couleur-secondaire', event.couleur_secondaire || '#ffffff'); rafraichirMediaAccueil().then(() => { const select = document.getElementById('admin-media-accueil'); if (event.media_accueil) select.value = event.media_accueil; }); const fmts = event.formats_actifs || ['10x15', '15x20', 'strip']; document.getElementById('tog-event-fmt-10x15').checked = fmts.includes('10x15'); document.getElementById('tog-event-fmt-15x20').checked = fmts.includes('15x20'); document.getElementById('tog-event-fmt-strip').checked = fmts.includes('strip'); // Lien galerie en ligne const booth = config.booth || {}; const lienBloc = document.getElementById('lien-galerie-event'); const lienA = document.getElementById('url-galerie-event'); if (booth.actif && booth.url) { const evtId = booth.event_id || eventEditId || 'default'; const url = booth.url.replace(/\/$/, '') + '/' + evtId; lienA.href = url; lienA.textContent = url; lienBloc.style.display = ''; } else { lienBloc.style.display = 'none'; } chargerCadresEvent(); } async function supprimerEventAdmin(id, nom) { if (!confirm(`Supprimer l'evenement "${nom}" et ses cadres ?`)) return; await fetch(`/api/evenements/${id}`, { method: 'DELETE' }); if (eventEditId === id) { eventEditId = null; document.getElementById('titre-event-detail').classList.add('cache'); document.getElementById('detail-evenement').classList.add('cache'); } config = await apiGet('/api/config'); await chargerListeEvenements(); afficherStatut('Evenement supprime', 'succes'); } function changerTheme(theme) { document.documentElement.setAttribute('data-theme', theme); } async function rafraichirMediaAccueil() { const data = await apiGet('/api/animations/custom'); const select = document.getElementById('admin-media-accueil'); const valeur = select.value; select.innerHTML = ''; for (const nom of data.tous) { const opt = document.createElement('option'); opt.value = nom; opt.textContent = nom; select.appendChild(opt); } if (valeur) select.value = valeur; } async function uploaderMediaAccueil() { const input = document.getElementById('input-media-accueil'); if (!input.files.length) return; const formData = new FormData(); formData.append('fichier', input.files[0]); await fetch('/api/upload/animation', { method: 'POST', body: formData }); const nomFichier = input.files[0].name; input.value = ''; await rafraichirMediaAccueil(); document.getElementById('admin-media-accueil').value = nomFichier; // Applique automatiquement la video comme media d'accueil de l'evenement en cours // (sinon il faut la re-selectionner dans le menu ET cliquer sur "Enregistrer" separement) if (eventEditId) { await apiPost('/api/config', { evenement: { media_accueil: nomFichier, event_id: eventEditId } }); await fetch(`/api/evenements/${eventEditId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ media_accueil: nomFichier }), }); appliquerConfig(); } afficherStatut('Media importe et applique', 'succes'); } async function sauvegarderEvenement() { const formats_actifs = []; if (document.getElementById('tog-event-fmt-10x15').checked) formats_actifs.push('10x15'); if (document.getElementById('tog-event-fmt-15x20').checked) formats_actifs.push('15x20'); if (document.getElementById('tog-event-fmt-strip').checked) formats_actifs.push('strip'); const donnees = { theme: document.getElementById('admin-theme').value, nom: getValue('admin-nom-event'), couleur_primaire: getValue('admin-couleur-primaire'), couleur_secondaire: getValue('admin-couleur-secondaire'), media_accueil: getValue('admin-media-accueil') || null, formats_actifs, }; if (eventEditId) { await apiPost('/api/config', { evenement: { ...donnees, event_id: eventEditId } }); await fetch(`/api/evenements/${eventEditId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(donnees), }); } else { await apiPost('/api/config', { evenement: donnees }); } appliquerConfig(); await chargerListeEvenements(); afficherStatut('Evenement sauvegarde', 'succes'); } // === CADRES EVENEMENT === const FORMATS_CADRE_EVENT = ['10x15', '15x20']; async function chargerCadresEvent() { if (!eventEditId) return; const container = document.getElementById('cadres-event-formats'); container.innerHTML = ''; // Charger cadres du premier format pour afficher le mode + liste const data = await apiGet(`/api/evenements/${eventEditId}/cadres/15x20`); const data2 = await apiGet(`/api/evenements/${eventEditId}/cadres/10x15`); const tous = [...new Set([...(data.disponibles || []), ...(data2.disponibles || [])])]; let cadresHtml = ''; for (const nom of tous) { const selected = data.cadre === nom ? ' cadre-selectionne' : ''; const src15 = `/data/evenements/${eventEditId}/cadres/15x20/${nom}`; const src10 = `/data/evenements/${eventEditId}/cadres/10x15/${nom}`; cadresHtml += `Aucun cadre importe
'}Chargement...
'; try { const info = await apiGet('/api/systeme/info'); let html = ''; // Reseau html += 'Impossible de charger les informations systeme.
'; } } 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() { const booth = config.booth || {}; const url = getValue('admin-booth-url').replace(/\/$/, ''); const apiKey = getValue('admin-booth-apikey'); const eventId = getValue('admin-booth-eventid') || 'default'; try { const resp = await fetch(`${url}/api/${eventId}/reset-password`, { method: 'POST', headers: { 'X-Api-Key': apiKey }, }); const data = await resp.json(); if (data.password) { document.getElementById('admin-booth-password').textContent = data.password; afficherStatut('Nouveau code genere : ' + data.password, 'succes'); } } catch (e) { afficherStatut('Erreur connexion au serveur booth', 'erreur'); } } // === WIFI === let _wifiMajActif = false; let _wifiSsidCible = null; function _signalBars(signal, actif) { const seuils = [20, 40, 60, 80]; return seuils.map(s => `` ).join(''); } async function chargerWifi() { const statut = await apiGet('/api/wifi/status'); const icon = document.getElementById('wifi-statut-icon'); const ssid = document.getElementById('wifi-ssid'); const detail = document.getElementById('wifi-detail'); const bars = document.getElementById('wifi-signal-bars'); if (statut.connecte) { icon.textContent = '\u{1F4F6}'; ssid.textContent = statut.ssid; detail.textContent = `IP : ${statut.ip || '—'}`; bars.innerHTML = _signalBars(statut.signal || 0, true); } else { icon.textContent = '\u{1F4F4}'; ssid.textContent = 'Non connecté'; detail.textContent = 'Aucun réseau WiFi'; bars.innerHTML = _signalBars(0, false); } await chargerWifiEnregistres(); } async function scannerWifi() { const liste = document.getElementById('wifi-liste-reseaux'); const loading = document.getElementById('wifi-scan-loading'); liste.innerHTML = ''; loading.classList.remove('cache'); try { const reseaux = await apiGet('/api/wifi/scan'); loading.classList.add('cache'); if (reseaux.length === 0) { liste.innerHTML = 'Aucun réseau détecté
'; return; } for (const r of reseaux) { const div = document.createElement('div'); div.className = 'wifi-reseau' + (r.actif ? ' wifi-actif' : ''); div.onclick = () => onWifiReseauClick(r); div.innerHTML = `Erreur scan WiFi
'; } } function onWifiReseauClick(reseau) { if (reseau.actif) return; if (reseau.enregistre) { connecterWifiDirect(reseau.ssid); } else if (!reseau.securise) { connecterWifiDirect(reseau.ssid); } else { afficherPopupWifiMdp(reseau.ssid); } } async function connecterWifiDirect(ssid) { afficherStatut(`Connexion à ${ssid}...`, 'succes'); const r = await apiPost('/api/wifi/connect', { ssid }); if (r.succes) { afficherStatut(r.message, 'succes'); await chargerWifi(); await scannerWifi(); } else { afficherStatut(r.message || 'Échec connexion', 'erreur'); afficherPopupWifiMdp(ssid); } } function afficherPopupWifiMdp(ssid) { _wifiSsidCible = ssid; document.getElementById('wifi-mdp-titre').textContent = `WiFi : ${ssid}`; document.getElementById('wifi-mdp-input').value = ''; document.getElementById('wifi-mdp-erreur').classList.add('cache'); document.getElementById('popup-wifi-mdp').classList.remove('cache'); apiGet(`/api/wifi/password/${encodeURIComponent(ssid)}`).then(data => { if (data.password) { document.getElementById('wifi-mdp-input').value = data.password; } }).catch(() => {}); } function fermerPopupWifi() { document.getElementById('popup-wifi-mdp').classList.add('cache'); _wifiSsidCible = null; } let _wifiMaj = false; function wifiTape(c) { const input = document.getElementById('wifi-mdp-input'); if (_wifiMaj && c.length === 1 && c.match(/[a-z]/)) c = c.toUpperCase(); input.value += c; if (_wifiMaj) { _wifiMaj = false; _wifiMajUI(); } } function wifiEffacer() { const input = document.getElementById('wifi-mdp-input'); input.value = input.value.slice(0, -1); } function wifiToggleMaj() { _wifiMaj = !_wifiMaj; _wifiMajUI(); } function _wifiMajUI() { const btn = document.getElementById('wifi-btn-maj'); btn.style.background = _wifiMaj ? 'var(--primaire)' : ''; btn.style.color = _wifiMaj ? '#fff' : ''; ['wifi-kb-row1', 'wifi-kb-row2', 'wifi-kb-row3'].forEach(id => { document.getElementById(id).querySelectorAll('button').forEach(b => { const c = b.textContent; if (c.length === 1 && c.match(/[a-zA-Z]/)) { b.textContent = _wifiMaj ? c.toUpperCase() : c.toLowerCase(); } }); }); } async function validerWifiMdp() { const mdp = document.getElementById('wifi-mdp-input').value; if (!mdp) return; const btn = document.getElementById('wifi-btn-connecter'); btn.textContent = 'Connexion...'; btn.disabled = true; const r = await apiPost('/api/wifi/connect', { ssid: _wifiSsidCible, password: mdp }); btn.textContent = 'Connecter'; btn.disabled = false; if (r.succes) { fermerPopupWifi(); afficherStatut(r.message, 'succes'); await chargerWifi(); await scannerWifi(); } else { const err = document.getElementById('wifi-mdp-erreur'); err.textContent = r.message || 'Échec connexion'; err.classList.remove('cache'); setTimeout(() => err.classList.add('cache'), 4000); } } async function chargerWifiEnregistres() { const liste = document.getElementById('wifi-liste-enregistres'); const saved = await apiGet('/api/wifi/saved'); liste.innerHTML = ''; if (saved.length === 0) { liste.innerHTML = 'Aucun réseau enregistré
'; return; } for (const s of saved) { const div = document.createElement('div'); div.className = 'wifi-enregistre-item'; div.innerHTML = ` ${_escHtml(s.ssid)} ${s.mdp_connu ? `👁 mdp` : ''} `; liste.appendChild(div); } } async function afficherMdpWifi(ssid, el) { const data = await apiGet(`/api/wifi/password/${encodeURIComponent(ssid)}`); if (data.password) { el.textContent = data.password; setTimeout(() => { el.innerHTML = '👁 mdp'; }, 5000); } } async function oublierWifi(ssid) { await fetch(`/api/wifi/saved/${encodeURIComponent(ssid)}`, { method: 'DELETE' }); afficherStatut(`${ssid} oublié`, 'succes'); await chargerWifiEnregistres(); } function _escHtml(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } function _escAttr(s) { return s.replace(/'/g, "\\'").replace(/"/g, '"'); } async function chargerDiagCamera() { const el = document.getElementById('diag-camera-contenu'); el.textContent = 'Test en cours...'; try { const data = await apiGet('/api/camera/debug'); let txt = ''; for (const [k, v] of Object.entries(data)) { if (k === 'config_widgets_top') { txt += `${k}: ${Array.isArray(v) ? v.join(', ') : v}\n`; } else { txt += `${k}: ${JSON.stringify(v)}\n`; } } el.textContent = txt || 'Aucune donnee'; } catch (e) { el.textContent = 'ERREUR : ' + e.message; } } // === CONSOMMABLES === async function chargerConsommables() { try { const data = await apiGet('/api/consommables'); document.getElementById('conso-papier-restant').textContent = data.papier_restant; document.getElementById('conso-papier-cap').textContent = data.papier_capacite; document.getElementById('conso-ruban-restant').textContent = data.ruban_restant; document.getElementById('conso-ruban-cap').textContent = data.ruban_capacite; document.getElementById('conso-photos').textContent = data.photos_imprimees; setValue('conso-cap-papier', data.papier_capacite); setValue('conso-cap-ruban', data.ruban_capacite); const pctPapier = data.papier_capacite > 0 ? Math.max(0, (data.papier_restant / data.papier_capacite) * 100) : 0; const pctRuban = data.ruban_capacite > 0 ? Math.max(0, (data.ruban_restant / data.ruban_capacite) * 100) : 0; const barrePapier = document.getElementById('conso-barre-papier'); const barreRuban = document.getElementById('conso-barre-ruban'); barrePapier.style.width = pctPapier + '%'; barreRuban.style.width = pctRuban + '%'; barrePapier.style.background = pctPapier < 10 ? '#f44336' : pctPapier < 25 ? '#fb8c00' : '#43a047'; barreRuban.style.background = pctRuban < 10 ? '#f44336' : pctRuban < 25 ? '#fb8c00' : '#43a047'; const perdues = data.poses_perdues || 0; document.getElementById('conso-perdues').textContent = perdues; const cardPerdues = document.getElementById('conso-card-perdues'); if (perdues > 0) { document.getElementById('conso-perdues').style.color = '#f44336'; cardPerdues.style.borderLeft = '3px solid #f44336'; } else { document.getElementById('conso-perdues').style.color = '#43a047'; cardPerdues.style.borderLeft = '3px solid #43a047'; } const diag = document.getElementById('conso-diagnostic'); const papierUsed = data.papier_utilise; const rubanUsed = data.ruban_utilise; const photos = data.photos_imprimees; let html = 'Diagnostic consommables