Spaces:
Running
Running
| /** | |
| * 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 = '<span class="spinner"></span> 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 = ` | |
| <div class="loading-content"> | |
| <div class="spinner-large"></div> | |
| <p id="loadingMessage">${message}</p> | |
| </div> | |
| `; | |
| document.body.appendChild(overlay); | |
| } else { | |
| document.getElementById('loadingMessage').textContent = message; | |
| overlay.style.display = 'flex'; | |
| } | |
| } | |
| /** | |
| * Hide loading overlay | |
| */ | |
| function hideLoading() { | |
| const overlay = document.getElementById('loadingOverlay'); | |
| if (overlay) { | |
| overlay.style.display = 'none'; | |
| } | |
| } | |
| // ============================================================================ | |
| // CLIPBOARD | |
| // ============================================================================ | |
| /** | |
| * Copy text to clipboard | |
| */ | |
| async function copyToClipboard(text) { | |
| try { | |
| await navigator.clipboard.writeText(text); | |
| showToast('Copiato negli appunti', 'success'); | |
| } catch (err) { | |
| console.error('Failed to copy:', err); | |
| showToast('Errore nella copia', 'error'); | |
| } | |
| } | |
| // ============================================================================ | |
| // LOCAL STORAGE | |
| // ============================================================================ | |
| /** | |
| * Save to local storage | |
| */ | |
| function saveToStorage(key, data) { | |
| try { | |
| localStorage.setItem(key, JSON.stringify(data)); | |
| } catch (e) { | |
| console.error('Storage error:', e); | |
| } | |
| } | |
| /** | |
| * Load from local storage | |
| */ | |
| function loadFromStorage(key, defaultValue = null) { | |
| try { | |
| const item = localStorage.getItem(key); | |
| return item ? JSON.parse(item) : defaultValue; | |
| } catch (e) { | |
| console.error('Storage error:', e); | |
| return defaultValue; | |
| } | |
| } | |
| // ============================================================================ | |
| // INITIALIZATION | |
| // ============================================================================ | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Initialize any live search fields | |
| initLiveSearch('searchInput', 'dataTable'); | |
| // Auto-hide alerts after 5 seconds | |
| const alerts = document.querySelectorAll('.alert:not(.alert-persistent)'); | |
| alerts.forEach(alert => { | |
| setTimeout(() => { | |
| alert.style.opacity = '0'; | |
| setTimeout(() => alert.remove(), 300); | |
| }, 5000); | |
| }); | |
| console.log('Rooting Future Strategy Engine v5.4 initialized'); | |
| }); | |
| // ============================================================================ | |
| // EXPORT | |
| // ============================================================================ | |
| window.RootingFuture = { | |
| apiRequest, | |
| showToast, | |
| formatDate, | |
| openModal, | |
| closeModal, | |
| setButtonLoading, | |
| showLoading, | |
| hideLoading, | |
| copyToClipboard, | |
| saveToStorage, | |
| loadFromStorage, | |
| }; | |