/* ── Olimpic Nastri — App JS v2 ─────────────────────────────────────────── */ // ── CSRF helper ── function getCsrf() { return document.cookie.split('; ') .find(r => r.startsWith('csrftoken=')) ?.split('=')[1] ?? ''; } // ── Toast notifications ── function showToast(message, type = 'success') { const container = document.getElementById('toast-container'); if (!container) return; const icons = { success: 'bi-check-circle-fill', danger: 'bi-exclamation-triangle-fill', info: 'bi-info-circle-fill', }; const colors = { success: '#15803d', danger: '#dc2626', info: '#4361ee', }; const id = 'toast-' + Date.now(); const html = ` `; container.insertAdjacentHTML('beforeend', html); const toastEl = document.getElementById(id); const bsToast = new bootstrap.Toast(toastEl, { delay: 3000 }); bsToast.show(); toastEl.addEventListener('hidden.bs.toast', () => toastEl.remove()); } // ── Convert Django messages to toasts on page load ── function convertMessagesToToasts() { document.querySelectorAll('[data-toast-message]').forEach(el => { showToast(el.dataset.toastMessage, el.dataset.toastType || 'success'); el.remove(); }); } // ── Progress slider AJAX ── function initSliders() { document.querySelectorAll('.progress-slider').forEach(slider => { if (slider._sliderInit) return; slider._sliderInit = true; const id = slider.dataset.id; const lbl = document.getElementById('lbl-' + id); const saving = document.getElementById('saving-' + id); let timer = null; slider.addEventListener('input', () => { const v = slider.value; if (lbl) lbl.textContent = v + '%'; slider.style.setProperty('--val', v + '%'); }); slider.addEventListener('change', () => { clearTimeout(timer); if (saving) { saving.classList.remove('d-none'); saving.textContent = 'salvataggio…'; } timer = setTimeout(() => { fetch(slider.dataset.url, { method: 'POST', headers: { 'X-CSRFToken': getCsrf(), 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'avanzamento=' + slider.value, }) .then(r => r.json()) .then(d => { if (saving) { saving.textContent = d.ok ? '✓ salvato' : '⚠ errore'; setTimeout(() => saving.classList.add('d-none'), 1500); } }) .catch(() => { if (saving) { saving.textContent = '⚠ errore'; setTimeout(() => saving.classList.add('d-none'), 1500); } }); }, 400); }); }); } // ── Live search dropdown ── function initLiveSearch() { const input = document.getElementById('search-input'); const dropdown = document.getElementById('search-dropdown'); if (!input || !dropdown) return; let timer = null; input.addEventListener('input', () => { const q = input.value.trim(); clearTimeout(timer); if (q.length < 2) { dropdown.classList.remove('show'); dropdown.innerHTML = ''; return; } timer = setTimeout(() => { fetch('/ricerca/?q=' + encodeURIComponent(q), { headers: { 'HX-Request': 'true' } }) .then(r => r.text()) .then(html => { dropdown.innerHTML = html; dropdown.classList.add('show'); }); }, 300); }); // Close dropdown on click outside document.addEventListener('click', (e) => { if (!input.contains(e.target) && !dropdown.contains(e.target)) { dropdown.classList.remove('show'); } }); // Navigate to full search on Enter input.closest('form')?.addEventListener('submit', (e) => { dropdown.classList.remove('show'); }); } // ── Init everything ── function initApp() { convertMessagesToToasts(); initSliders(); initLiveSearch(); } // Run on initial page load document.addEventListener('DOMContentLoaded', initApp); // Re-init after HTMX swaps document.addEventListener('htmx:afterSettle', initApp);