/* ── 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);