49 KiB
Executable File
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