```javascript /** * UTILITY SYSTEM * Professional Helper Functions and Classes */ // Storage Manager class StorageManager { constructor(prefix = 'patrol_') { this.prefix = prefix; } set(key, value, expiry = null) { const item = { value: value, expiry: expiry ? Date.now() + expiry : null }; try { localStorage.setItem(this.prefix + key, JSON.stringify(item)); return true; } catch (error) { console.error('Storage set failed:', error); return false; } } get(key) { try { const itemStr = localStorage.getItem(this.prefix + key); if (!itemStr) return null; const item = JSON.parse(itemStr); // Check expiry if (item.expiry && Date.now() > item.expiry) { this.remove(key); return null; } return item.value; } catch (error) { console.error('Storage get failed:', error); return null; } } remove(key) { try { localStorage.removeItem(this.prefix + key); return true; } catch (error) { console.error('Storage remove failed:', error); return false; } } clear() { try { const keys = Object.keys(localStorage); keys.forEach(key => { if (key.startsWith(this.prefix)) { localStorage.removeItem(key); } }); return true; } catch (error) { console.error('Storage clear failed:', error); return false; } } getAll() { const items = {}; try { const keys = Object.keys(localStorage); keys.forEach(key => { if (key.startsWith(this.prefix)) { const cleanKey = key.replace(this.prefix, ''); items[cleanKey] = this.get(cleanKey); } }); } catch (error) { console.error('Storage getAll failed:', error); } return items; } } // Event Bus class EventBus { constructor() { this.events = new Map(); } on(event, callback) { if (!this.events.has(event)) { this.events.set(event, new Set()); } this.events.get(event).add(callback); // Return unsubscribe function return () => this.off(event, callback); } off(event, callback) { const callbacks = this.events.get(event); if (callbacks) { callbacks.delete(callback); if (callbacks.size === 0) { this.events.delete(event); } } } emit(event, data = {}) { const callbacks = this.events.get(event); if (callbacks) { callbacks.forEach(callback => { try { callback(data); } catch (error) { console.error(`Event callback error for "${event}":`, error); } }); } } once(event, callback) { const onceCallback = (data) => { callback(data); this.off(event, onceCallback); }; return this.on(event, onceCallback); } clear() { this.events.clear(); } } // Notification Manager class NotificationManager { constructor() { this.container = null; this.notifications = new Map(); this.init(); } init() { this.container = document.createElement('div'); this.container.className = 'notification-container'; this.container.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 9999; pointer-events: none; `; document.body.appendChild(this.container); } show(message, type = 'info', options = {}) { const { duration = 5000, closable = true, actions = [] } = options; const id = Date.now() + Math.random(); const notification = this.createNotification(id, message, type, { duration, closable, actions }); this.notifications.set(id, notification); this.container.appendChild(notification); // Auto dismiss if (duration > 0) { setTimeout(() => this.dismiss(id), duration); } return id; } createNotification(id, message, type, options) { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.style.cssText = ` background: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); margin-bottom: 12px; padding: 16px; min-width: 300px; max-width: 500px; transform: translateX(100%); transition: transform 0.3s ease; pointer-events: auto; border-left: 4px solid ${this.getTypeColor(type)}; `; const icon = this.getTypeIcon(type); const content = `
${icon}
${this.getTypeTitle(type)}
${message}
${options.actions.length > 0 ? this.createActions(options.actions, id) : ''}
${options.closable ? ` ` : ''}
`; notification.innerHTML = content; // Animate in setTimeout(() => { notification.style.transform = 'translateX(0)'; }, 100); return notification; } createActions(actions, notificationId) { const buttons = actions.map(action => ` `).join(''); return `
${buttons}
`; } dismiss(id) { const notification = this.notifications.get(id); if (notification) { notification.style.transform = 'translateX(100%)'; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } this.notifications.delete(id); }, 300); } } success(message, options = {}) { return this.show(message, 'success', options); } error(message, options = {}) { return this.show(message, 'error', { ...options, duration: 8000 }); } warning(message, options = {}) { return this.show(message, 'warning', { ...options, duration: 6000 }); } info(message, options = {}) { return this.show(message, 'info', options); } getTypeColor(type) { const colors = { success: '#10b981', error: '#ef4444', warning: '#f59e0b', info: '#3b82f6' }; return colors[type] || colors.info; } getTypeIcon(type) { const icons = { success: '', error: '', warning: '', info: '' }; return icons[type] || icons.info; } getTypeTitle(type) { const titles = { success: 'Succès', error: 'Erreur', warning: 'Attention', info: 'Information' }; return titles[type] || titles.info; } } // Theme Manager class ThemeManager { constructor() { this.currentTheme = 'light'; this.storage = new StorageManager(); this.init(); } init() { const saved = this.storage.get('theme'); if (saved) { this.currentTheme = saved; } this.apply(); } toggle() { this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light'; this.apply(); this.storage.set('theme', this.currentTheme); } apply() { const body = document.body; body.className = body.className.replace(/theme-\w+/g, ''); body.classList.add(`theme-${this.currentTheme}`); } } // Form Validator class FormValidator { constructor() { this.rules = { required: (value) => value !== null && value !== undefined && value.toString().trim() !== '', email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), min: (value, min) => value.length >= min, max: (value, max) => value.length <= max, pattern: (value, pattern) => new RegExp(pattern).test(value), phone: (value) => /^\+?[\d\s\-\(\)]+$/.test(value) }; } validate(formData, rules) { const errors = {}; for (const field in rules) { const fieldRules = rules[field]; const value = formData[field]; for (const rule of fieldRules) { const { type, value: ruleValue, message } = rule; const validator = this.rules[type]; if (validator && !validator(value, ruleValue)) { if (!errors[field]) { errors[field] = []; } errors[field].push(message || `Champ invalide: ${type}`); } } } return { isValid: Object.keys(errors).length === 0, errors }; } validateField(value, rules) { for (const rule of rules) { const { type, value: ruleValue, message } = rule; const validator = this.rules[type]; if (validator && !validator(value, ruleValue)) { return { isValid: false, message: message || `Champ invalide: ${type}` }; } } return { isValid: true }; } } // Modal Manager class ModalManager { constructor() { this.modals = new Map(); this.init(); } init() { this.container = document.createElement('div'); this.container.className = 'modal-container'; this.container.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 10000; opacity: 0; visibility: hidden; transition: all 0.3s ease; `; document.body.appendChild(this.container); } open(id, options = {}) { const modal = this.createModal(id, options); this.modals.set(id, modal); this.container.appendChild(modal); setTimeout(() => { modal.classList.add('active'); this.container.classList.add('active'); }, 10); // Focus first input if exists const firstInput = modal.querySelector('input, textarea, select'); if (firstInput) { firstInput.focus(); } } close(id) { const modal = this.modals.get(id); if (modal) { modal.classList.remove('active'); this.container.classList.remove('active'); setTimeout(() => { if (modal.parentNode) { modal.parentNode.removeChild(modal); } this.modals.delete(id); }, 300); } } createModal(id, options) { const modal = document.createElement('div'); modal.className = 'modal'; modal.style.cssText = ` background: white; border-radius: 8px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); max-width: ${options.maxWidth || '500px'}; width: 90vw; max-height: 90vh; overflow: auto; transform: translateY(-20px); transition: transform 0.3s ease; `; const header = options.title ? ` ` : ''; const content = ` `; const footer = options.actions ? ` ` : ''; modal.innerHTML = header + content + footer; return modal; } } // Utility Functions const Utils = { // Date formatting formatDate(date, format = 'fr-FR') { return new Intl.DateTimeFormat(format, { year: 'numeric', month: 'long', day: 'numeric' }).format(new Date(date)); }, formatDateTime(date, format = 'fr-FR') { return new Intl.DateTimeFormat(format, { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(date)); }, // Number formatting formatNumber(number, decimals = 0) { return new Intl.NumberFormat('fr-FR', { minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(number); }, // File utilities formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }, // String utilities truncate(str, length = 50) { return str.length > length ? str.substring(0, length) + '...' : str; }, slugify(str) { return str.toLowerCase() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); }, // Array utilities chunk(array, size) { const chunks = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; }, // Object utilities deepClone(obj) { return JSON.parse(JSON.stringify(obj)); }, isEmpty(obj) { return Object.keys(obj).length === ___METADATA_START___ {"repoId":"secutorpro/patrol-control-system","isNew":false,"userName":"secutorpro"} ___METADATA_END___