/** * Rooting Future Strategy Engine v5.4 * Main JavaScript */ // ============================================================================ // UTILITIES // ============================================================================ /** * Make API request with JSON */ async function apiRequest(url, method = 'GET', data = null) { const options = { method, headers: { 'Content-Type': 'application/json', }, }; if (data) { options.body = JSON.stringify(data); } try { const response = await fetch(url, options); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Request failed'); } return result; } catch (error) { console.error('API Error:', error); throw error; } } /** * Show notification toast */ function showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.textContent = message; document.body.appendChild(toast); // Animate in setTimeout(() => toast.classList.add('show'), 10); // Remove after 3 seconds setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }, 3000); } /** * Format date */ function formatDate(isoString) { if (!isoString) return 'N/A'; const date = new Date(isoString); return date.toLocaleDateString('it-IT', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', }); } /** * Debounce function */ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // ============================================================================ // FORM HANDLING // ============================================================================ /** * Serialize form to object */ function serializeForm(form) { const formData = new FormData(form); const data = {}; for (const [key, value] of formData.entries()) { if (data[key]) { if (!Array.isArray(data[key])) { data[key] = [data[key]]; } data[key].push(value); } else { data[key] = value; } } return data; } /** * Validate form */ function validateForm(form) { const requiredFields = form.querySelectorAll('[required]'); let isValid = true; requiredFields.forEach(field => { if (!field.value.trim()) { isValid = false; field.classList.add('error'); } else { field.classList.remove('error'); } }); return isValid; } // ============================================================================ // MODAL HANDLING // ============================================================================ /** * Open modal */ function openModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; } } /** * Close modal */ function closeModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.style.display = 'none'; document.body.style.overflow = ''; } } // Close modal on backdrop click document.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { e.target.style.display = 'none'; document.body.style.overflow = ''; } }); // Close modal on Escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { const openModals = document.querySelectorAll('.modal[style*="flex"]'); openModals.forEach(modal => { modal.style.display = 'none'; }); document.body.style.overflow = ''; } }); // ============================================================================ // SEARCH & FILTER // ============================================================================ /** * Initialize live search */ function initLiveSearch(inputId, tableId) { const input = document.getElementById(inputId); const table = document.getElementById(tableId); if (!input || !table) return; const search = debounce(() => { const query = input.value.toLowerCase(); const rows = table.querySelectorAll('tbody tr'); rows.forEach(row => { const text = row.textContent.toLowerCase(); row.style.display = text.includes(query) ? '' : 'none'; }); }, 300); input.addEventListener('input', search); } // ============================================================================ // LOADING STATES // ============================================================================ /** * Show loading spinner on button */ function setButtonLoading(button, loading = true) { if (loading) { button.disabled = true; button.dataset.originalText = button.innerHTML; button.innerHTML = ' Caricamento...'; } else { button.disabled = false; button.innerHTML = button.dataset.originalText || button.innerHTML; } } /** * Show loading overlay */ function showLoading(message = 'Caricamento...') { let overlay = document.getElementById('loadingOverlay'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'loadingOverlay'; overlay.className = 'loading-overlay'; overlay.innerHTML = `
${message}