// UX Manager - Gestionnaire d'expérience utilisateur modulaire class UXManager { constructor() { this.navigation = new NavigationManager(); this.contextPanel = new ContextPanelManager(); this.notifications = new NotificationManager(); this.accessibility = new AccessibilityManager(); this.performance = new PerformanceManager(); this.language = new LanguageManager(); this.init(); } init() { console.log('[UX Manager] Initialisation...'); this.navigation.init(); this.contextPanel.init(); this.notifications.init(); this.accessibility.init(); this.performance.init(); this.language.init(); // Vérifier la session this.checkSession(); // Initialiser le Service Worker this.initServiceWorker(); } toggleContextPanel() { this.contextPanel.toggle(); } toggleSidebar() { const sidebar = document.getElementById('sidebar'); sidebar.classList.toggle('show'); } checkSession() { const dureeSession = parseInt(document.getElementById('dureeSession').value) || 30; const dureeMinutes = dureeSession * 60 * 1000; setInterval(() => { const derniereAction = sessionStorage.getItem('derniere_action'); const maintenant = Date.now(); if (derniereAction && (maintenant - derniereAction > dureeMinutes)) { this.showSessionWarning(); } }, 60000); // Vérifier toutes les minutes } showSessionWarning() { const isAnglophone = window.appConfig.isAnglophone; const message = isAnglophone ? 'Your session will expire soon. Do you want to extend it?' : 'Votre session va bientôt expirer. Souhaitez-vous la prolonger?'; Swal.fire({ title: isAnglophone ? 'Session Warning' : 'Avertissement de session', text: message, icon: 'warning', showCancelButton: true, confirmButtonText: isAnglophone ? 'Extend' : 'Prolonger', cancelButtonText: isAnglophone ? 'Logout' : 'Déconnexion' }).then((result) => { if (result.isConfirmed) { sessionStorage.setItem('derniere_action', Date.now()); } else { window.location.href = window.appConfig.racineWeb + 'Connexion/deconnecter'; } }); } initServiceWorker() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => { console.log('[UX Manager] Service Worker prêt:', registration.scope); }); } } } // Navigation Manager - Gestion des menus (VERSION CORRIGÉE) class NavigationManager { constructor() { this.currentOpenMenu = null; this.activeMenuId = null; // Menu actif basé sur la page courante } init() { this.setupActiveMenu(); this.setupMenuBehavior(); this.setupKeyboardNavigation(); } setupActiveMenu() { // Déterminer le menu actif basé sur la page courante const activeParentId = window.appConfig?.activeParentId; const activeLink = window.appConfig?.activeLink; console.log('[Navigation] Page active:', activeLink, 'Parent ID:', activeParentId); if (activeParentId !== null && activeParentId !== '') { this.activeMenuId = `submenu${activeParentId}`; console.log('[Navigation] Menu actif détecté:', this.activeMenuId); // Ouvrir seulement le menu actif setTimeout(() => { this.openMenu(this.activeMenuId); this.currentOpenMenu = this.activeMenuId; }, 100); } else { console.log('[Navigation] Aucun menu actif détecté'); // Fermer tous les menus par défaut this.closeAllMenus(); } } setupMenuBehavior() { // Pour chaque lien de menu avec sous-menu document.querySelectorAll('.nav-link[data-bs-toggle="collapse"]').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const targetId = link.getAttribute('href').substring(1); console.log('[Navigation] Clic sur menu:', targetId); // Si on clique sur le menu déjà ouvert, on le ferme if (this.currentOpenMenu === targetId) { this.closeMenu(targetId); this.currentOpenMenu = null; } else { // Fermer le menu précédent si différent if (this.currentOpenMenu && this.currentOpenMenu !== targetId) { this.closeMenu(this.currentOpenMenu); } // Ouvrir le nouveau menu this.openMenu(targetId); this.currentOpenMenu = targetId; } }); }); // Fermer les menus en cliquant ailleurs document.addEventListener('click', (e) => { if (!e.target.closest('.app-sidebar')) { // Ne pas fermer le menu actif s'il correspond à la page courante if (this.currentOpenMenu !== this.activeMenuId) { this.closeAllMenus(); } } }); // Empêcher la fermeture du menu actif document.querySelectorAll('.nav-submenu').forEach(menu => { menu.addEventListener('click', (e) => { e.stopPropagation(); }); }); } openMenu(menuId) { const menu = document.getElementById(menuId); const link = document.querySelector(`[href="#${menuId}"]`); if (menu && link) { // Retirer 'show' de tous les autres menus document.querySelectorAll('.nav-submenu.show').forEach(otherMenu => { if (otherMenu.id !== menuId) { otherMenu.classList.remove('show'); const otherLink = document.querySelector(`[href="#${otherMenu.id}"]`); if (otherLink) { otherLink.setAttribute('aria-expanded', 'false'); otherLink.classList.remove('active'); const arrow = otherLink.querySelector('.nav-arrow'); if (arrow) arrow.style.transform = 'rotate(0deg)'; } } }); // Ouvrir le menu sélectionné menu.classList.add('show'); link.setAttribute('aria-expanded', 'true'); link.classList.add('active'); const arrow = link.querySelector('.nav-arrow'); if (arrow) arrow.style.transform = 'rotate(90deg)'; console.log('[Navigation] Menu ouvert:', menuId); } } closeMenu(menuId) { const menu = document.getElementById(menuId); const link = document.querySelector(`[href="#${menuId}"]`); if (menu && link) { // Ne pas fermer le menu actif si c'est celui de la page courante if (menuId === this.activeMenuId) { console.log('[Navigation] Menu actif, ne pas fermer:', menuId); return; } menu.classList.remove('show'); link.setAttribute('aria-expanded', 'false'); link.classList.remove('active'); const arrow = link.querySelector('.nav-arrow'); if (arrow) arrow.style.transform = 'rotate(0deg)'; console.log('[Navigation] Menu fermé:', menuId); } } closeAllMenus() { // Ne fermer que les menus qui ne sont pas actifs document.querySelectorAll('.nav-submenu.show').forEach(menu => { if (menu.id !== this.activeMenuId) { this.closeMenu(menu.id); } }); // Si aucun menu actif, réinitialiser if (!this.activeMenuId) { this.currentOpenMenu = null; } } setupKeyboardNavigation() { document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { this.closeAllMenus(); } // Navigation par flèches if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { const focused = document.activeElement; if (focused.classList.contains('nav-link')) { e.preventDefault(); this.navigateMenu(focused, e.key); } } }); } navigateMenu(currentElement, direction) { const allLinks = Array.from(document.querySelectorAll('.nav-link')); const currentIndex = allLinks.indexOf(currentElement); if (direction === 'ArrowDown' && currentIndex < allLinks.length - 1) { allLinks[currentIndex + 1].focus(); } else if (direction === 'ArrowUp' && currentIndex > 0) { allLinks[currentIndex - 1].focus(); } } // Méthode publique pour forcer l'ouverture d'un menu openMenuById(menuId) { this.openMenu(menuId); this.currentOpenMenu = menuId; } // Méthode publique pour forcer la fermeture closeAllExceptActive() { this.closeAllMenus(); } } // Context Panel Manager - Gestion du panneau de contexte class ContextPanelManager { constructor() { this.panel = document.getElementById('contextPanel'); this.toggleButton = document.querySelector('.context-toggle'); this.proximityArea = document.querySelector('.proximity-hover-area'); this.isOpen = false; } init() { this.setupProximityDetection(); this.setupKeyboardControls(); } setupProximityDetection() { if (!this.proximityArea) return; this.proximityArea.addEventListener('mouseenter', () => { this.toggleButton.style.opacity = '1'; this.toggleButton.style.transform = 'scale(1.1)'; }); this.proximityArea.addEventListener('mouseleave', () => { if (!this.isOpen) { this.toggleButton.style.opacity = '0.2'; this.toggleButton.style.transform = 'scale(1)'; } }); // Initial opacity this.toggleButton.style.opacity = '0.2'; } setupKeyboardControls() { document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.shiftKey && e.key === 'C') { e.preventDefault(); this.toggle(); } if (e.key === 'Escape' && this.isOpen) { this.close(); } }); } toggle() { if (this.isOpen) { this.close(); } else { this.open(); } } open() { this.panel.classList.add('open'); this.toggleButton.setAttribute('aria-expanded', 'true'); this.toggleButton.style.opacity = '1'; this.isOpen = true; // Focus sur le bouton de fermeture setTimeout(() => { this.panel.querySelector('.context-close').focus(); }, 300); } close() { this.panel.classList.remove('open'); this.toggleButton.setAttribute('aria-expanded', 'false'); this.toggleButton.style.opacity = '0.2'; this.isOpen = false; // Retourner le focus au bouton toggle this.toggleButton.focus(); } } // Notification Manager class NotificationManager { constructor() { this.countElement = document.getElementById('notificationCount'); this.unreadCount = 0; } init() { this.loadNotifications(); this.setupPolling(); } loadNotifications() { // Simuler le chargement des notifications this.updateCount(3); // Exemple: 3 notifications non lues } updateCount(count) { this.unreadCount = count; if (this.countElement) { this.countElement.textContent = count; this.countElement.style.display = count > 0 ? 'flex' : 'none'; // Mettre à jour le titre de la page const baseTitle = document.title.replace(/^\(\d+\)\s*/, ''); document.title = count > 0 ? `(${count}) ${baseTitle}` : baseTitle; } } setupPolling() { // Vérifier les nouvelles notifications toutes les 30 secondes setInterval(() => { // À implémenter: appel AJAX pour vérifier les nouvelles notifications // this.checkNewNotifications(); }, 30000); } showMessagesModal() { const modal = new bootstrap.Modal(document.getElementById('messagesModal')); modal.show(); // Charger les messages this.loadMessages(); } loadMessages() { const container = document.getElementById('div_messagerie'); if (!container) return; // Simuler le chargement des messages container.innerHTML = `
Une nouvelle prescription a été créée pour le bénéficiaire.
Votre demande de dérogation a été approuvée.