# โšก JAVASCRIPT - Documentazione Completa NetGescon > **Sistema JavaScript dell'interfaccia universale** > **๐Ÿ“… Creato:** 21/07/2025 > **๐Ÿ”„ Ultimo aggiornamento:** 21/07/2025 > **๐ŸŽฏ Prioritร :** โšก ALTO --- ## ๐Ÿ“‹ **OVERVIEW JAVASCRIPT SYSTEM** Il Sistema JavaScript NetGescon fornisce **INTERATTIVITร€ AVANZATA** per: - ๐ŸŽญ **Core Animations & Transitions** - ๐Ÿ”„ **State Management** - ๐Ÿ“ก **AJAX & API Calls** - ๐Ÿ“ฑ **Responsive Interactions** - ๐ŸŽฏ **Form Enhancements** - ๐Ÿ“Š **Dashboard Dynamics** - ๐Ÿ”ง **Utility Functions** ### ๐ŸŽฏ **JavaScript Philosophy** - **Performance First** - Codice ottimizzato e lazy loading - **Progressive Enhancement** - Funziona senza JS, migliora con JS - **Modular Architecture** - Componenti riutilizzabili e indipendenti - **Modern Standards** - ES6+, Vanilla JS, No jQuery dependency --- ## ๐Ÿ—๏ธ **ARCHITECTURE OVERVIEW** ### ๐Ÿ“ **File Structure** ``` /public/js/ โ”œโ”€โ”€ core/ โ”‚ โ”œโ”€โ”€ app.js # Core application logic โ”‚ โ”œโ”€โ”€ config.js # Configuration constants โ”‚ โ”œโ”€โ”€ utils.js # Utility functions โ”‚ โ””โ”€โ”€ events.js # Event management system โ”œโ”€โ”€ components/ โ”‚ โ”œโ”€โ”€ sidebar.js # Sidebar functionality โ”‚ โ”œโ”€โ”€ header.js # Header interactions โ”‚ โ”œโ”€โ”€ cards.js # Cards enhancements โ”‚ โ”œโ”€โ”€ forms.js # Form improvements โ”‚ โ”œโ”€โ”€ tables.js # Table features โ”‚ โ””โ”€โ”€ modals.js # Modal management โ”œโ”€โ”€ modules/ โ”‚ โ”œโ”€โ”€ dashboard.js # Dashboard specific โ”‚ โ”œโ”€โ”€ stabili.js # Stabili management โ”‚ โ”œโ”€โ”€ condomini.js # Condomini features โ”‚ โ””โ”€โ”€ reports.js # Reports functionality โ”œโ”€โ”€ libs/ โ”‚ โ”œโ”€โ”€ chart.min.js # Chart.js library โ”‚ โ”œโ”€โ”€ datatables.min.js # DataTables library โ”‚ โ””โ”€โ”€ alpine.min.js # Alpine.js (if used) โ””โ”€โ”€ netgescon-admin.js # Main compiled file ``` --- ## ๐ŸŽญ **CORE APPLICATION** ### ๐Ÿ“ **File:** `/public/js/core/app.js` ```javascript /** * NetGescon Admin - Core Application * @version 2.1.0 * @description Main application initialization and coordination */ class NetGesconApp { constructor() { this.version = '2.1.0'; this.debug = window.location.hostname === 'localhost'; this.components = new Map(); this.modules = new Map(); this.initialized = false; // Bind context this.init = this.init.bind(this); this.log = this.log.bind(this); this.error = this.error.bind(this); } /** * Initialize the application */ async init() { if (this.initialized) { this.log('App already initialized'); return; } try { this.log('Initializing NetGescon Admin...', this.version); // 1. Initialize core systems await this.initializeCore(); // 2. Initialize components await this.initializeComponents(); // 3. Initialize modules based on page await this.initializeModules(); // 4. Setup global event handlers this.setupGlobalHandlers(); // 5. Perform system checks this.performSystemChecks(); this.initialized = true; this.log('NetGescon Admin initialized successfully'); // Dispatch custom event this.dispatch('netgescon:ready', { version: this.version }); } catch (error) { this.error('Failed to initialize NetGescon Admin:', error); } } /** * Initialize core systems */ async initializeCore() { // Initialize configuration await NetGesconConfig.init(); // Initialize utilities NetGesconUtils.init(); // Initialize event system NetGesconEvents.init(); // Setup CSRF token this.setupCSRF(); // Setup global error handling this.setupErrorHandling(); } /** * Initialize UI components */ async initializeComponents() { const componentLoaders = [ { name: 'sidebar', module: () => import('./components/sidebar.js') }, { name: 'header', module: () => import('./components/header.js') }, { name: 'cards', module: () => import('./components/cards.js') }, { name: 'forms', module: () => import('./components/forms.js') }, { name: 'tables', module: () => import('./components/tables.js') }, { name: 'modals', module: () => import('./components/modals.js') } ]; for (const loader of componentLoaders) { try { const module = await loader.module(); const Component = module.default || module[loader.name]; if (Component && typeof Component.init === 'function') { await Component.init(); this.components.set(loader.name, Component); this.log(`Component '${loader.name}' initialized`); } } catch (error) { this.error(`Failed to load component '${loader.name}':`, error); } } } /** * Initialize page-specific modules */ async initializeModules() { const page = this.getCurrentPage(); const moduleMap = { 'dashboard': () => import('./modules/dashboard.js'), 'stabili': () => import('./modules/stabili.js'), 'condomini': () => import('./modules/condomini.js'), 'reports': () => import('./modules/reports.js') }; if (moduleMap[page]) { try { const module = await moduleMap[page](); const Module = module.default || module[page]; if (Module && typeof Module.init === 'function') { await Module.init(); this.modules.set(page, Module); this.log(`Module '${page}' initialized`); } } catch (error) { this.error(`Failed to load module '${page}':`, error); } } } /** * Setup global event handlers */ setupGlobalHandlers() { // Window resize handler window.addEventListener('resize', NetGesconUtils.throttle(() => { this.dispatch('netgescon:resize', { width: window.innerWidth, height: window.innerHeight }); }, 250)); // Visibility change handler document.addEventListener('visibilitychange', () => { this.dispatch('netgescon:visibility', { hidden: document.hidden }); }); // Before unload handler window.addEventListener('beforeunload', (e) => { const hasUnsavedChanges = this.checkUnsavedChanges(); if (hasUnsavedChanges) { e.preventDefault(); e.returnValue = 'Ci sono modifiche non salvate. Sei sicuro di voler uscire?'; return e.returnValue; } }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { this.handleKeyboardShortcuts(e); }); // Handle loading states this.setupLoadingStates(); } /** * Setup CSRF token for AJAX requests */ setupCSRF() { const token = document.querySelector('meta[name="csrf-token"]'); if (token) { // Set default header for fetch requests window.fetch = new Proxy(window.fetch, { apply: function(target, thisArg, argumentsList) { const [url, options = {}] = argumentsList; if (options.method && options.method.toUpperCase() !== 'GET') { options.headers = { 'X-CSRF-TOKEN': token.getAttribute('content'), 'X-Requested-With': 'XMLHttpRequest', ...options.headers }; } return target.apply(thisArg, [url, options]); } }); } } /** * Setup global error handling */ setupErrorHandling() { window.addEventListener('error', (e) => { this.error('JavaScript Error:', e.error); }); window.addEventListener('unhandledrejection', (e) => { this.error('Unhandled Promise Rejection:', e.reason); }); } /** * Setup loading states management */ setupLoadingStates() { // Intercept form submissions document.addEventListener('submit', (e) => { const form = e.target; if (form.classList.contains('netgescon-form')) { this.showFormLoading(form); } }); // Intercept AJAX requests const originalFetch = window.fetch; window.fetch = function(...args) { const request = originalFetch.apply(this, args); // Show global loading indicator for slow requests const timeout = setTimeout(() => { NetGesconApp.instance.showGlobalLoading(); }, 500); request.finally(() => { clearTimeout(timeout); NetGesconApp.instance.hideGlobalLoading(); }); return request; }; } /** * Handle keyboard shortcuts */ handleKeyboardShortcuts(e) { // Ctrl/Cmd + K = Search if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); this.openSearch(); } // Esc = Close modals/dropdowns if (e.key === 'Escape') { this.closeAllOverlays(); } // Ctrl/Cmd + S = Save if ((e.ctrlKey || e.metaKey) && e.key === 's') { const activeForm = document.querySelector('form:focus-within'); if (activeForm) { e.preventDefault(); activeForm.dispatchEvent(new Event('submit')); } } } /** * Get current page identifier */ getCurrentPage() { const path = window.location.pathname; const segments = path.split('/').filter(Boolean); if (segments.includes('admin')) { const adminIndex = segments.indexOf('admin'); return segments[adminIndex + 1] || 'dashboard'; } return 'dashboard'; } /** * Check for unsaved changes */ checkUnsavedChanges() { const forms = document.querySelectorAll('.netgescon-form[data-track-changes]'); return Array.from(forms).some(form => form.dataset.hasChanges === 'true'); } /** * Perform system checks */ performSystemChecks() { // Check browser compatibility this.checkBrowserSupport(); // Check required dependencies this.checkDependencies(); // Check API connectivity this.checkAPIConnection(); } /** * Check browser support */ checkBrowserSupport() { const requiredFeatures = [ 'fetch', 'Promise', 'Map', 'Set', 'IntersectionObserver' ]; const unsupported = requiredFeatures.filter(feature => !window[feature]); if (unsupported.length > 0) { this.error('Browser missing required features:', unsupported); this.showBrowserWarning(); } } /** * Check required dependencies */ checkDependencies() { const dependencies = ['Chart', 'bootstrap']; const missing = dependencies.filter(dep => !window[dep]); if (missing.length > 0) { this.error('Missing dependencies:', missing); } } /** * Check API connectivity */ async checkAPIConnection() { try { const response = await fetch('/api/health'); if (!response.ok) { throw new Error(`API returned ${response.status}`); } this.log('API connection successful'); } catch (error) { this.error('API connection failed:', error); } } /** * Show form loading state */ showFormLoading(form) { const submitBtn = form.querySelector('[type="submit"]'); if (submitBtn) { submitBtn.disabled = true; submitBtn.classList.add('loading'); const originalText = submitBtn.textContent; submitBtn.textContent = 'Caricamento...'; submitBtn.dataset.originalText = originalText; } } /** * Show global loading indicator */ showGlobalLoading() { const loader = document.getElementById('global-loader'); if (loader) { loader.classList.remove('hidden'); } } /** * Hide global loading indicator */ hideGlobalLoading() { const loader = document.getElementById('global-loader'); if (loader) { loader.classList.add('hidden'); } } /** * Open search functionality */ openSearch() { const searchModal = document.getElementById('search-modal'); if (searchModal) { // Open search modal const modal = new bootstrap.Modal(searchModal); modal.show(); // Focus search input const input = searchModal.querySelector('input[type="search"]'); if (input) { setTimeout(() => input.focus(), 100); } } } /** * Close all overlays */ closeAllOverlays() { // Close modals document.querySelectorAll('.modal.show').forEach(modal => { const modalInstance = bootstrap.Modal.getInstance(modal); if (modalInstance) { modalInstance.hide(); } }); // Close dropdowns document.querySelectorAll('.dropdown-menu.show').forEach(dropdown => { dropdown.classList.remove('show'); }); // Close sidebar on mobile if (window.innerWidth < 768) { const sidebar = document.getElementById('sidebar'); if (sidebar && sidebar.classList.contains('show')) { sidebar.classList.remove('show'); } } } /** * Show browser warning */ showBrowserWarning() { const warning = document.createElement('div'); warning.className = 'alert alert-warning alert-dismissible fixed-top'; warning.style.zIndex = '9999'; warning.innerHTML = ` Browser non supportato Alcune funzionalitร  potrebbero non funzionare correttamente. Ti consigliamo di aggiornare il browser. `; document.body.insertBefore(warning, document.body.firstChild); } /** * Dispatch custom event */ dispatch(eventName, detail = {}) { const event = new CustomEvent(eventName, { detail }); document.dispatchEvent(event); this.log(`Event dispatched: ${eventName}`, detail); } /** * Get component instance */ getComponent(name) { return this.components.get(name); } /** * Get module instance */ getModule(name) { return this.modules.get(name); } /** * Debug logging */ log(...args) { if (this.debug) { console.log('[NetGescon]', ...args); } } /** * Error logging */ error(...args) { console.error('[NetGescon]', ...args); // Send error to monitoring service if available if (window.Sentry) { window.Sentry.captureException(new Error(args.join(' '))); } } } // Create singleton instance NetGesconApp.instance = new NetGesconApp(); // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', NetGesconApp.instance.init); } else { NetGesconApp.instance.init(); } // Export for use in other modules window.NetGesconApp = NetGesconApp; ``` --- ## โš™๏ธ **CONFIGURATION SYSTEM** ### ๐Ÿ“ **File:** `/public/js/core/config.js` ```javascript /** * NetGescon Configuration System * @description Centralized configuration management */ class NetGesconConfig { constructor() { this.data = new Map(); this.initialized = false; } /** * Initialize configuration */ async init() { if (this.initialized) return; // Load from meta tags this.loadFromMeta(); // Load from API if needed await this.loadFromAPI(); // Set defaults this.setDefaults(); this.initialized = true; } /** * Load configuration from meta tags */ loadFromMeta() { const metaTags = document.querySelectorAll('meta[name^="netgescon-"]'); metaTags.forEach(meta => { const key = meta.name.replace('netgescon-', ''); const value = this.parseValue(meta.content); this.set(key, value); }); } /** * Load configuration from API */ async loadFromAPI() { try { const response = await fetch('/api/config'); if (response.ok) { const config = await response.json(); Object.entries(config).forEach(([key, value]) => { this.set(key, value); }); } } catch (error) { console.warn('Failed to load config from API:', error); } } /** * Set default configuration values */ setDefaults() { const defaults = { // App settings 'app.name': 'NetGescon', 'app.version': '2.1.0', 'app.debug': false, 'app.environment': 'production', // UI settings 'ui.theme': 'default', 'ui.sidebar.collapsed': false, 'ui.animations.enabled': true, 'ui.animations.duration': 300, // Performance settings 'performance.lazy_load': true, 'performance.cache_ttl': 300000, // 5 minutes 'performance.throttle_delay': 250, // API settings 'api.base_url': '/api', 'api.timeout': 30000, 'api.retry_attempts': 3, // Features 'features.real_time_updates': true, 'features.offline_support': false, 'features.analytics': true, // Localization 'locale.language': 'it', 'locale.timezone': 'Europe/Rome', 'locale.currency': 'EUR', 'locale.date_format': 'DD/MM/YYYY', 'locale.time_format': 'HH:mm', // Tables 'tables.default_page_size': 25, 'tables.page_size_options': [10, 25, 50, 100], 'tables.search_delay': 500, // Forms 'forms.auto_save': true, 'forms.auto_save_delay': 2000, 'forms.validation_delay': 300, // Notifications 'notifications.position': 'top-right', 'notifications.auto_dismiss': true, 'notifications.dismiss_delay': 5000 }; Object.entries(defaults).forEach(([key, value]) => { if (!this.has(key)) { this.set(key, value); } }); } /** * Parse string value to appropriate type */ parseValue(value) { if (value === 'true') return true; if (value === 'false') return false; if (value === 'null') return null; if (value === 'undefined') return undefined; // Try to parse as number const numValue = Number(value); if (!isNaN(numValue) && isFinite(numValue)) { return numValue; } // Try to parse as JSON try { return JSON.parse(value); } catch { return value; } } /** * Get configuration value */ get(key, defaultValue = null) { return this.data.get(key) ?? defaultValue; } /** * Set configuration value */ set(key, value) { this.data.set(key, value); // Dispatch change event document.dispatchEvent(new CustomEvent('netgescon:config:change', { detail: { key, value, config: this } })); } /** * Check if configuration key exists */ has(key) { return this.data.has(key); } /** * Remove configuration key */ remove(key) { const existed = this.data.delete(key); if (existed) { document.dispatchEvent(new CustomEvent('netgescon:config:remove', { detail: { key, config: this } })); } return existed; } /** * Get all configuration data */ all() { return Object.fromEntries(this.data); } /** * Get configuration subset by prefix */ getByPrefix(prefix) { const result = {}; for (const [key, value] of this.data) { if (key.startsWith(prefix)) { const shortKey = key.substring(prefix.length); result[shortKey] = value; } } return result; } /** * Merge configuration object */ merge(config) { Object.entries(config).forEach(([key, value]) => { this.set(key, value); }); } /** * Save configuration to localStorage */ save() { try { const config = this.all(); localStorage.setItem('netgescon_config', JSON.stringify(config)); } catch (error) { console.warn('Failed to save config to localStorage:', error); } } /** * Load configuration from localStorage */ load() { try { const saved = localStorage.getItem('netgescon_config'); if (saved) { const config = JSON.parse(saved); this.merge(config); } } catch (error) { console.warn('Failed to load config from localStorage:', error); } } } // Create singleton instance const NetGesconConfig = new NetGesconConfig(); // Export for use in other modules window.NetGesconConfig = NetGesconConfig; ``` --- ## ๐Ÿ› ๏ธ **UTILITY FUNCTIONS** ### ๐Ÿ“ **File:** `/public/js/core/utils.js` ```javascript /** * NetGescon Utility Functions * @description Common utility functions used throughout the application */ class NetGesconUtils { constructor() { this.cache = new Map(); this.timers = new Map(); } /** * Initialize utilities */ init() { // Setup global utility functions this.setupGlobalHelpers(); // Setup performance monitoring this.setupPerformanceMonitoring(); } /** * Setup global helper functions */ setupGlobalHelpers() { // Add utility functions to window for global access window.$ = this.querySelector.bind(this); window.$$ = this.querySelectorAll.bind(this); window.debounce = this.debounce.bind(this); window.throttle = this.throttle.bind(this); } /** * Setup performance monitoring */ setupPerformanceMonitoring() { // Monitor DOM mutations if (window.MutationObserver) { const observer = new MutationObserver((mutations) => { const addedNodes = mutations.reduce((acc, mutation) => { return acc + mutation.addedNodes.length; }, 0); if (addedNodes > 50) { console.warn('Large DOM mutation detected:', addedNodes, 'nodes'); } }); observer.observe(document.body, { childList: true, subtree: true }); } } // ========================= // DOM UTILITIES // ========================= /** * Enhanced querySelector */ querySelector(selector, context = document) { return context.querySelector(selector); } /** * Enhanced querySelectorAll */ querySelectorAll(selector, context = document) { return Array.from(context.querySelectorAll(selector)); } /** * Create element with attributes and content */ createElement(tag, attributes = {}, content = '') { const element = document.createElement(tag); Object.entries(attributes).forEach(([key, value]) => { if (key === 'className') { element.className = value; } else if (key === 'innerHTML') { element.innerHTML = value; } else if (key === 'textContent') { element.textContent = value; } else { element.setAttribute(key, value); } }); if (content) { element.innerHTML = content; } return element; } /** * Check if element is visible in viewport */ isInViewport(element, threshold = 0) { const rect = element.getBoundingClientRect(); const windowHeight = window.innerHeight || document.documentElement.clientHeight; const windowWidth = window.innerWidth || document.documentElement.clientWidth; return ( rect.top >= -threshold && rect.left >= -threshold && rect.bottom <= windowHeight + threshold && rect.right <= windowWidth + threshold ); } /** * Smooth scroll to element */ scrollToElement(element, options = {}) { const defaults = { behavior: 'smooth', block: 'start', inline: 'nearest' }; const config = { ...defaults, ...options }; if (typeof element === 'string') { element = document.querySelector(element); } if (element) { element.scrollIntoView(config); } } /** * Get element dimensions and position */ getElementInfo(element) { const rect = element.getBoundingClientRect(); const style = window.getComputedStyle(element); return { width: rect.width, height: rect.height, top: rect.top + window.scrollY, left: rect.left + window.scrollX, bottom: rect.bottom + window.scrollY, right: rect.right + window.scrollX, visible: this.isInViewport(element), zIndex: parseInt(style.zIndex) || 0, opacity: parseFloat(style.opacity) }; } // ========================= // PERFORMANCE UTILITIES // ========================= /** * Debounce function execution */ debounce(func, wait, immediate = false) { let timeout; return function executedFunction(...args) { const later = () => { timeout = null; if (!immediate) func(...args); }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func(...args); }; } /** * Throttle function execution */ throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } /** * Request animation frame with fallback */ requestAnimationFrame(callback) { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || ((cb) => setTimeout(cb, 16)); } /** * Lazy load with Intersection Observer */ lazyLoad(elements, callback, options = {}) { const defaults = { root: null, rootMargin: '50px', threshold: 0.1 }; const config = { ...defaults, ...options }; if (!window.IntersectionObserver) { // Fallback for browsers without Intersection Observer elements.forEach(callback); return; } const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { callback(entry.target); observer.unobserve(entry.target); } }); }, config); elements.forEach(element => observer.observe(element)); return observer; } // ========================= // DATA UTILITIES // ========================= /** * Deep clone object */ deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } if (obj instanceof Date) { return new Date(obj.getTime()); } if (obj instanceof Array) { return obj.map(item => this.deepClone(item)); } if (typeof obj === 'object') { const cloned = {}; Object.keys(obj).forEach(key => { cloned[key] = this.deepClone(obj[key]); }); return cloned; } } /** * Merge objects deeply */ deepMerge(target, source) { const result = this.deepClone(target); Object.keys(source).forEach(key => { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this.deepMerge(result[key] || {}, source[key]); } else { result[key] = source[key]; } }); return result; } /** * Get nested object property safely */ get(obj, path, defaultValue = undefined) { const keys = path.split('.'); let result = obj; for (const key of keys) { if (result == null || typeof result !== 'object') { return defaultValue; } result = result[key]; } return result !== undefined ? result : defaultValue; } /** * Set nested object property */ set(obj, path, value) { const keys = path.split('.'); const lastKey = keys.pop(); let current = obj; for (const key of keys) { if (!(key in current) || typeof current[key] !== 'object') { current[key] = {}; } current = current[key]; } current[lastKey] = value; return obj; } // ========================= // VALIDATION UTILITIES // ========================= /** * Validate email address */ isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * Validate Italian phone number */ isValidPhone(phone) { const phoneRegex = /^(\+39\s?)?((3[0-9]{2}\s?\d{6,7})|(0[0-9]{1,3}\s?\d{6,8}))$/; return phoneRegex.test(phone.replace(/\s/g, '')); } /** * Validate Italian fiscal code */ isValidFiscalCode(code) { const fiscalCodeRegex = /^[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]$/; return fiscalCodeRegex.test(code.toUpperCase()); } /** * Validate Italian VAT number */ isValidVAT(vat) { const vatRegex = /^[0-9]{11}$/; return vatRegex.test(vat); } // ========================= // FORMAT UTILITIES // ========================= /** * Format currency */ formatCurrency(amount, currency = 'EUR', locale = 'it-IT') { return new Intl.NumberFormat(locale, { style: 'currency', currency: currency }).format(amount); } /** * Format date */ formatDate(date, format = 'DD/MM/YYYY', locale = 'it-IT') { if (!date) return ''; const d = new Date(date); if (isNaN(d.getTime())) return ''; const options = {}; if (format.includes('DD')) options.day = '2-digit'; if (format.includes('MM')) options.month = '2-digit'; if (format.includes('YYYY')) options.year = 'numeric'; if (format.includes('HH')) options.hour = '2-digit'; if (format.includes('mm')) options.minute = '2-digit'; return d.toLocaleDateString(locale, options); } /** * Format file size */ formatFileSize(bytes, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } /** * Slugify string */ slugify(text) { return text .toLowerCase() .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .replace(/[^a-z0-9\s-]/g, '') .trim() .replace(/[\s-]+/g, '-'); } // ========================= // CACHE UTILITIES // ========================= /** * Cache with TTL */ cache(key, value, ttl = 300000) { // 5 minutes default if (value === undefined) { // Get from cache const cached = this.cache.get(key); if (cached && Date.now() < cached.expires) { return cached.value; } this.cache.delete(key); return null; } else { // Set cache this.cache.set(key, { value: value, expires: Date.now() + ttl }); } } /** * Clear expired cache entries */ cleanCache() { const now = Date.now(); for (const [key, cached] of this.cache) { if (now >= cached.expires) { this.cache.delete(key); } } } // ========================= // ASYNC UTILITIES // ========================= /** * Sleep function */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Retry function with backoff */ async retry(fn, maxAttempts = 3, delay = 1000) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (error) { if (attempt === maxAttempts) { throw error; } await this.sleep(delay * attempt); } } } /** * Timeout promise */ timeout(promise, ms) { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms) ) ]); } } // Create singleton instance const NetGesconUtils = new NetGesconUtils(); // Export for use in other modules window.NetGesconUtils = NetGesconUtils; ``` --- ## ๐Ÿ“ก **API MANAGEMENT** ### ๐Ÿ“ **File:** `/public/js/core/api.js` ```javascript /** * NetGescon API Management * @description Centralized API communication layer */ class NetGesconAPI { constructor() { this.baseURL = NetGesconConfig.get('api.base_url', '/api'); this.timeout = NetGesconConfig.get('api.timeout', 30000); this.retryAttempts = NetGesconConfig.get('api.retry_attempts', 3); this.cache = new Map(); this.pendingRequests = new Map(); } /** * Make HTTP request */ async request(url, options = {}) { const fullURL = url.startsWith('http') ? url : `${this.baseURL}${url}`; const requestKey = `${options.method || 'GET'}:${fullURL}`; // Deduplicate identical requests if (this.pendingRequests.has(requestKey)) { return this.pendingRequests.get(requestKey); } const defaults = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' }; const config = this.mergeConfig(defaults, options); // Add CSRF token for mutations if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(config.method)) { const token = document.querySelector('meta[name="csrf-token"]'); if (token) { config.headers['X-CSRF-TOKEN'] = token.getAttribute('content'); } } const requestPromise = this.executeRequest(fullURL, config); this.pendingRequests.set(requestKey, requestPromise); try { const result = await requestPromise; return result; } finally { this.pendingRequests.delete(requestKey); } } /** * Execute request with timeout and retry */ async executeRequest(url, config) { for (let attempt = 1; attempt <= this.retryAttempts; attempt++) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout); const response = await fetch(url, { ...config, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new HTTPError(response.status, response.statusText, response); } const data = await this.parseResponse(response); return { data, response }; } catch (error) { if (attempt === this.retryAttempts || !this.shouldRetry(error)) { throw error; } await NetGesconUtils.sleep(1000 * attempt); } } } /** * Parse response based on content type */ async parseResponse(response) { const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return await response.json(); } if (contentType && contentType.includes('text/')) { return await response.text(); } return await response.blob(); } /** * Check if error should trigger retry */ shouldRetry(error) { if (error.name === 'AbortError') return false; if (error instanceof HTTPError) { return error.status >= 500 || error.status === 429; } return true; } /** * Merge request configurations */ mergeConfig(defaults, options) { return { ...defaults, ...options, headers: { ...defaults.headers, ...options.headers } }; } // ========================= // HTTP METHODS // ========================= /** * GET request */ async get(url, params = {}, options = {}) { const searchParams = new URLSearchParams(params); const fullURL = searchParams.toString() ? `${url}?${searchParams}` : url; return this.request(fullURL, { method: 'GET', ...options }); } /** * POST request */ async post(url, data = {}, options = {}) { return this.request(url, { method: 'POST', body: JSON.stringify(data), ...options }); } /** * PUT request */ async put(url, data = {}, options = {}) { return this.request(url, { method: 'PUT', body: JSON.stringify(data), ...options }); } /** * PATCH request */ async patch(url, data = {}, options = {}) { return this.request(url, { method: 'PATCH', body: JSON.stringify(data), ...options }); } /** * DELETE request */ async delete(url, options = {}) { return this.request(url, { method: 'DELETE', ...options }); } // ========================= // SPECIALIZED METHODS // ========================= /** * Upload file */ async upload(url, file, onProgress = null) { const formData = new FormData(); formData.append('file', file); return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable && onProgress) { const percentComplete = (e.loaded / e.total) * 100; onProgress(percentComplete); } }); xhr.addEventListener('load', () => { if (xhr.status >= 200 && xhr.status < 300) { try { const response = JSON.parse(xhr.responseText); resolve(response); } catch (error) { resolve(xhr.responseText); } } else { reject(new HTTPError(xhr.status, xhr.statusText)); } }); xhr.addEventListener('error', () => { reject(new Error('Upload failed')); }); xhr.open('POST', url.startsWith('http') ? url : `${this.baseURL}${url}`); // Add CSRF token const token = document.querySelector('meta[name="csrf-token"]'); if (token) { xhr.setRequestHeader('X-CSRF-TOKEN', token.getAttribute('content')); } xhr.send(formData); }); } /** * Download file */ async download(url, filename = null) { try { const response = await fetch(url.startsWith('http') ? url : `${this.baseURL}${url}`); if (!response.ok) { throw new HTTPError(response.status, response.statusText); } const blob = await response.blob(); const downloadURL = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = downloadURL; link.download = filename || this.getFilenameFromResponse(response); document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(downloadURL); } catch (error) { console.error('Download failed:', error); throw error; } } /** * Extract filename from response headers */ getFilenameFromResponse(response) { const disposition = response.headers.get('content-disposition'); if (disposition) { const matches = disposition.match(/filename="([^"]+)"/); if (matches) return matches[1]; } return 'download'; } // ========================= // CACHING // ========================= /** * Get with cache */ async getCached(url, params = {}, ttl = 300000) { const cacheKey = `${url}:${JSON.stringify(params)}`; const cached = this.cache.get(cacheKey); if (cached && Date.now() < cached.expires) { return cached.data; } const result = await this.get(url, params); this.cache.set(cacheKey, { data: result, expires: Date.now() + ttl }); return result; } /** * Clear cache */ clearCache(pattern = null) { if (pattern) { for (const key of this.cache.keys()) { if (key.includes(pattern)) { this.cache.delete(key); } } } else { this.cache.clear(); } } // ========================= // ERROR HANDLING // ========================= /** * Handle API errors globally */ handleError(error) { if (error instanceof HTTPError) { switch (error.status) { case 401: this.handleUnauthorized(); break; case 403: this.handleForbidden(); break; case 422: this.handleValidationError(error); break; case 500: this.handleServerError(error); break; default: this.handleGenericError(error); } } else { this.handleGenericError(error); } } handleUnauthorized() { // Redirect to login window.location.href = '/login'; } handleForbidden() { NetGesconNotifications.error('Non hai i permessi per eseguire questa azione'); } handleValidationError(error) { // Handle form validation errors NetGesconNotifications.error('Controlla i dati inseriti'); } handleServerError(error) { NetGesconNotifications.error('Errore del server. Riprova piรน tardi.'); } handleGenericError(error) { NetGesconNotifications.error('Si รจ verificato un errore imprevisto'); } } /** * HTTP Error class */ class HTTPError extends Error { constructor(status, statusText, response = null) { super(`HTTP ${status}: ${statusText}`); this.name = 'HTTPError'; this.status = status; this.statusText = statusText; this.response = response; } } // Create singleton instance const NetGesconAPI = new NetGesconAPI(); // Export for use in other modules window.NetGesconAPI = NetGesconAPI; window.HTTPError = HTTPError; ``` --- ## ๐Ÿ“š **FILES CORRELATI** ### โšก **Core JavaScript Files** - `/public/js/core/app.js` - Main application logic - `/public/js/core/config.js` - Configuration management - `/public/js/core/utils.js` - Utility functions - `/public/js/core/api.js` - API communication layer - `/public/js/core/events.js` - Event management system ### ๐Ÿงฉ **Component Files** - `/public/js/components/sidebar.js` - Sidebar functionality - `/public/js/components/header.js` - Header interactions - `/public/js/components/cards.js` - Cards enhancements - `/public/js/components/forms.js` - Form improvements - `/public/js/components/tables.js` - Table features - `/public/js/components/modals.js` - Modal management ### ๐Ÿ“ฆ **Module Files** - `/public/js/modules/dashboard.js` - Dashboard specific - `/public/js/modules/stabili.js` - Stabili management - `/public/js/modules/condomini.js` - Condomini features - `/public/js/modules/reports.js` - Reports functionality --- ## ๐Ÿงช **TESTING CHECKLIST** ### โœ… **Core Functionality** - [ ] App initialization successful - [ ] Configuration loading working - [ ] Utility functions available - [ ] API communication functional - [ ] Error handling proper ### โœ… **Performance Tests** - [ ] Initial load time < 2s - [ ] Memory usage stable - [ ] No memory leaks - [ ] Smooth animations - [ ] Efficient DOM updates ### โœ… **Browser Compatibility** - [ ] Chrome 90+ - [ ] Firefox 88+ - [ ] Safari 14+ - [ ] Edge 90+ - [ ] Mobile browsers --- ## ๐ŸŽฏ **NEXT STEPS** ### ๐Ÿ”ฎ **JavaScript Evolution** - [ ] **Service Worker** - Offline support e caching - [ ] **Web Workers** - Heavy computations in background - [ ] **WebAssembly** - Performance-critical operations - [ ] **Progressive Web App** - App-like experience - [ ] **Real-time Updates** - WebSocket integration --- *๐Ÿ“ Documento mantenuto da: GitHub Copilot AI Assistant* *๐Ÿ”— Componente: JavaScript System NetGescon Interface v2.1.0*