netgescon-day0/docs/90-UI-interfaccia-unica/07-JAVASCRIPT.md

49 KiB
Executable File

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

/**
 * 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 = `
            <strong>Browser non supportato</strong> 
            Alcune funzionalità potrebbero non funzionare correttamente. 
            Ti consigliamo di aggiornare il browser.
            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
        `;
        
        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

/**
 * 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

/**
 * 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

/**
 * 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