/** * NOTYTEX UNIFIED - Point d'entrée unifié * Charge et initialise tous les composants modernes * Remplace l'ancien notytex.js */ import NotytexCore from './notytex-core.js'; import NotificationSystem from './components/notification-system.js'; import ModalManager from './components/modal-manager.js'; import FilterManager from './components/filter-manager.js'; /** * Gestionnaire d'initialisation unifié */ class NotytexApp { constructor() { this.core = null; this.initialized = false; this.loadingPromise = null; } /** * Initialisation asynchrone */ async init() { if (this.initialized) return this.core; if (this.loadingPromise) return this.loadingPromise; this.loadingPromise = this.performInit(); return this.loadingPromise; } async performInit() { try { // 1. Initialiser le core this.core = NotytexCore.getInstance(); // 2. Enregistrer les composants essentiels await this.registerCoreComponents(); // 3. Initialiser les modules page-spécifiques await this.initializePageModules(); // 4. Configuration des intégrations legacy this.setupLegacyIntegration(); // 5. Optimisations performance this.setupPerformanceOptimizations(); this.initialized = true; // Émission de l'événement ready this.core.emit('notytex:ready'); return this.core; } catch (error) { console.error('❌ Failed to initialize Notytex:', error); throw error; } } /** * Enregistrement des composants essentiels */ async registerCoreComponents() { const components = [ ['notifications', NotificationSystem], ['modals', ModalManager], ['filters', FilterManager] ]; for (const [name, ComponentClass] of components) { try { this.core.registerModule(name, ComponentClass); } catch (error) { console.error(`✗ Failed to register ${name}:`, error); } } } /** * Initialisation des modules spécifiques aux pages */ async initializePageModules() { const currentPage = this.detectCurrentPage(); // Modules spécifiques par page const pageModules = { 'assessments': ['ClassDashboard'], 'classes': ['ClassManagement'], 'grading': ['GradingInterface'], 'council': ['CouncilPreparation'] }; if (pageModules[currentPage]) { for (const moduleName of pageModules[currentPage]) { await this.loadPageModule(moduleName, currentPage); } } // Modules universels (présents sur toutes les pages) await this.initializeUniversalModules(); } /** * Détection de la page actuelle */ detectCurrentPage() { const path = window.location.pathname; const body = document.body; // Détection par URL if (path.includes('/assessments')) return 'assessments'; if (path.includes('/classes')) return 'classes'; if (path.includes('/grading')) return 'grading'; if (path.includes('/council')) return 'council'; if (path.includes('/students')) return 'students'; if (path.includes('/config')) return 'config'; // Détection par data attributes if (body.hasAttribute('data-page')) { return body.getAttribute('data-page'); } // Détection par title ou meta const title = document.title.toLowerCase(); if (title.includes('évaluation')) return 'assessments'; if (title.includes('classe')) return 'classes'; if (title.includes('note')) return 'grading'; return 'general'; } /** * Chargement des modules de page */ async loadPageModule(moduleName, pageName) { try { // Mapping des noms de modules aux fichiers const moduleFiles = { 'ClassDashboard': './ClassDashboard.js', 'CouncilPreparation': './CouncilPreparation.js' }; const filePath = moduleFiles[moduleName]; if (!filePath) { console.warn(`Module file not found for ${moduleName}`); return; } // Vérifier si le fichier existe (fallback) try { await this.core.loadModule(moduleName, filePath); } catch (loadError) { console.warn(`Module ${moduleName} not available, creating placeholder`); this.createModulePlaceholder(moduleName); } } catch (error) { console.error(`Failed to load page module ${moduleName}:`, error); } } /** * Initialisation des modules universels */ async initializeUniversalModules() { // Animation au scroll pour tous les éléments this.initializeScrollAnimations(); // Gestion des filtres sur toutes les pages avec filtres this.initializePageFilters(); // Tooltips et popovers this.initializeTooltips(); // Raccourcis clavier this.initializeKeyboardShortcuts(); } /** * Animations au scroll */ initializeScrollAnimations() { const elements = this.core.utils.queryAll('[data-animate]'); if (elements.length === 0) return; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const animationType = entry.target.dataset.animate || 'fade-in-up'; entry.target.classList.add(animationType); observer.unobserve(entry.target); } }); }, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }); elements.forEach(el => observer.observe(el)); } /** * Filtres de page */ initializePageFilters() { const filterElements = this.core.utils.queryAll('[data-filter], .filter-btn'); if (filterElements.length === 0) return; const filterManager = this.core.getModule('filters'); if (!filterManager) return; // Configuration spécifique pour les pages de liste filterManager.onApply((filters) => { this.handleFilterApplication(filters); }); } /** * Application des filtres avec feedback visuel */ handleFilterApplication(filters) { // Afficher un indicateur de chargement this.showFilterLoading(); // Mettre à jour le compteur si présent this.updateFilterCounts(filters); // Par défaut, recharger après un court délai pour l'UX setTimeout(() => { window.location.href = window.location.href; }, 150); } /** * Indicateur de chargement pour filtres */ showFilterLoading() { const container = this.core.utils.query('#filtered-count'); if (container) { container.innerHTML = '...'; } } /** * Mise à jour des compteurs de filtres */ updateFilterCounts(filters) { // Cette méthode peut être étendue pour faire des appels AJAX // pour obtenir les compteurs en temps réel } /** * Tooltips simples */ initializeTooltips() { const tooltipElements = this.core.utils.queryAll('[title], [data-tooltip]'); tooltipElements.forEach(element => { const tooltipText = element.getAttribute('data-tooltip') || element.getAttribute('title'); if (!tooltipText) return; // Retirer le title natif pour éviter les doublons element.removeAttribute('title'); let tooltip = null; const showTooltip = (event) => { tooltip = this.core.utils.createElement('div', { className: 'absolute z-50 px-2 py-1 text-xs text-white bg-gray-900 rounded shadow-lg pointer-events-none', style: 'top: -9999px; left: -9999px;' }); tooltip.textContent = tooltipText; document.body.appendChild(tooltip); const rect = element.getBoundingClientRect(); const tooltipRect = tooltip.getBoundingClientRect(); const top = rect.top - tooltipRect.height - 5; const left = rect.left + (rect.width - tooltipRect.width) / 2; tooltip.style.top = `${top}px`; tooltip.style.left = `${left}px`; }; const hideTooltip = () => { if (tooltip) { tooltip.remove(); tooltip = null; } }; element.addEventListener('mouseenter', showTooltip); element.addEventListener('mouseleave', hideTooltip); element.addEventListener('focus', showTooltip); element.addEventListener('blur', hideTooltip); }); } /** * Raccourcis clavier */ initializeKeyboardShortcuts() { document.addEventListener('keydown', (event) => { // Ctrl/Cmd + K pour recherche rapide if ((event.ctrlKey || event.metaKey) && event.key === 'k') { event.preventDefault(); const searchInput = this.core.utils.query('input[type="search"], [data-search-filter]'); if (searchInput) { searchInput.focus(); } } // Échap pour fermer les overlays if (event.key === 'Escape') { // Fermer les dropdowns this.core.utils.queryAll('.dropdown-menu.show').forEach(dropdown => { dropdown.classList.remove('show'); }); } }); } /** * Intégration avec le code legacy */ setupLegacyIntegration() { // Maintenir la compatibilité avec l'ancien namespace window.Notytex = { // API de transition utils: this.core.utils, state: this.core.state, emit: this.core.emit.bind(this.core), on: this.core.on.bind(this.core), // Composants notifications: this.core.getModule('notifications'), modals: this.core.getModule('modals'), filters: this.core.getModule('filters'), // Méthodes legacy showToast: (message, type = 'info') => { this.core.getModule('notifications')?.show({ message, type }); }, showModal: (modalId) => { this.core.getModule('modals')?.open(modalId); }, closeModal: (modalId) => { this.core.getModule('modals')?.close(modalId); } }; // Maintenir les anciennes fonctions globales si utilisées if (!window.showToast) { window.showToast = window.Notytex.showToast; } } /** * Optimisations de performance */ setupPerformanceOptimizations() { // Lazy loading des images this.setupLazyLoading(); // Preload des liens importants this.setupLinkPreloading(); // Monitoring des performances this.setupPerformanceMonitoring(); } /** * Lazy loading des images */ setupLazyLoading() { if ('IntersectionObserver' in window) { const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; const src = img.getAttribute('data-src'); if (src) { img.src = src; img.removeAttribute('data-src'); imageObserver.unobserve(img); } } }); }); this.core.utils.queryAll('img[data-src]').forEach(img => { imageObserver.observe(img); }); } } /** * Preload des liens */ setupLinkPreloading() { // Preload au hover pour les liens internes const links = this.core.utils.queryAll('a[href^="/"], a[href^="./"]'); const preloadedUrls = new Set(); links.forEach(link => { link.addEventListener('mouseenter', () => { const href = link.href; if (!preloadedUrls.has(href)) { const preloadLink = document.createElement('link'); preloadLink.rel = 'prefetch'; preloadLink.href = href; document.head.appendChild(preloadLink); preloadedUrls.add(href); } }, { once: true }); }); } /** * Monitoring des performances */ setupPerformanceMonitoring() { if ('PerformanceObserver' in window) { // Observer les métriques de performance const perfObserver = new PerformanceObserver((list) => { list.getEntries().forEach(entry => { if (entry.entryType === 'measure') { } }); }); perfObserver.observe({ entryTypes: ['measure', 'navigation'] }); } } /** * Créer un placeholder pour un module manquant */ createModulePlaceholder(moduleName) { const placeholder = { name: moduleName, placeholder: true, init: () => {}, destroy: () => {} }; this.core.modules.set(moduleName.toLowerCase(), placeholder); } } // Instance globale const notytexApp = new NotytexApp(); // Auto-initialisation if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => notytexApp.init()); } else { notytexApp.init(); } // Export pour utilisation export default notytexApp;