1420 lines
48 KiB
JavaScript
1420 lines
48 KiB
JavaScript
/**
|
|
* UX Manager - Gestionnaire d'expérience utilisateur modulaire
|
|
* Version: 3.0.0 - Corrigée et Optimisée
|
|
* Description: Gère tous les aspects de l'UX : navigation, panels contextuels, notifications, accessibilité
|
|
* Auteur: Portail RH Inter Santé
|
|
* Date: 2025.12.22 - Version finale
|
|
*/
|
|
|
|
// ============================================================================
|
|
// CLASSE PRINCIPALE - UXManager
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Classe principale qui orchestre tous les gestionnaires d'expérience utilisateur
|
|
* Elle initialise et coordonne les différents sous-managers
|
|
*/
|
|
class UXManager {
|
|
/**
|
|
* Constructeur principal - Initialise tous les sous-managers
|
|
*/
|
|
constructor() {
|
|
// Initialisation des sous-managers
|
|
this.navigation = new NavigationManager();
|
|
this.contextPanel = new ContextPanelManager();
|
|
this.notifications = new NotificationManager();
|
|
this.accessibility = new AccessibilityManager();
|
|
this.performance = new PerformanceManager();
|
|
this.language = new LanguageManager();
|
|
|
|
// Lancement de l'initialisation
|
|
this.init();
|
|
}
|
|
|
|
/**
|
|
* Méthode d'initialisation principale
|
|
* Appelée automatiquement par le constructeur
|
|
*/
|
|
init() {
|
|
//console.log('[UX Manager] Initialisation du système d\'expérience utilisateur...');
|
|
|
|
// Initialisation séquentielle des sous-managers
|
|
this.navigation.init();
|
|
this.contextPanel.init();
|
|
this.notifications.init();
|
|
this.accessibility.init();
|
|
this.performance.init();
|
|
this.language.init();
|
|
|
|
// Gestion de session utilisateur
|
|
this.checkSession();
|
|
|
|
// Initialisation du Service Worker
|
|
this.initServiceWorker();
|
|
|
|
//console.log('[UX Manager] Initialisation terminée avec succès');
|
|
}
|
|
|
|
/**
|
|
* Basculer l'état du panneau contextuel
|
|
* @public - Méthode accessible globalement
|
|
*/
|
|
toggleContextPanel() {
|
|
this.contextPanel.toggle();
|
|
}
|
|
|
|
|
|
/**
|
|
* Vérifier et gérer l'expiration de session utilisateur
|
|
* @private - Méthode interne de gestion de session
|
|
*/
|
|
checkSession() {
|
|
// Récupérer la durée de session depuis le DOM
|
|
const dureeSession = parseInt(document.getElementById('dureeSession')?.value) || 30;
|
|
const dureeMinutes = dureeSession * 60 * 1000; // Conversion en millisecondes
|
|
|
|
// Vérifier régulièrement l'activité de l'utilisateur
|
|
setInterval(() => {
|
|
const derniereAction = sessionStorage.getItem('derniere_action');
|
|
const maintenant = Date.now();
|
|
|
|
// Si inactivité dépassant la durée autorisée
|
|
if (derniereAction && (maintenant - derniereAction > dureeMinutes)) {
|
|
this.showSessionWarning();
|
|
}
|
|
}, 60000); // Vérification toutes les minutes
|
|
}
|
|
|
|
/**
|
|
* Afficher un avertissement d'expiration de session
|
|
* @private - Méthode d'affichage de notification
|
|
*/
|
|
showSessionWarning() {
|
|
const isAnglophone = window.appConfig?.isAnglophone || false;
|
|
const messages = {
|
|
title: isAnglophone ? 'Session Warning' : 'Avertissement de session',
|
|
text: isAnglophone
|
|
? 'Your session will expire soon. Do you want to extend it?'
|
|
: 'Votre session va bientôt expirer. Souhaitez-vous la prolonger?',
|
|
confirm: isAnglophone ? 'Extend' : 'Prolonger',
|
|
cancel: isAnglophone ? 'Logout' : 'Déconnexion'
|
|
};
|
|
|
|
// Utiliser SweetAlert pour une notification élégante
|
|
Swal.fire({
|
|
title: messages.title,
|
|
text: messages.text,
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonText: messages.confirm,
|
|
cancelButtonText: messages.cancel,
|
|
allowOutsideClick: false,
|
|
allowEscapeKey: false
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
// Prolonger la session
|
|
sessionStorage.setItem('derniere_action', Date.now());
|
|
this.accessibility.announce(
|
|
isAnglophone ? 'Session extended' : 'Session prolongée'
|
|
);
|
|
} else {
|
|
// Déconnexion
|
|
window.location.href = window.appConfig?.racineWeb + 'Connexion/deconnecter';
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialiser le Service Worker pour le mode hors-ligne
|
|
* @private - Méthode d'initialisation PWA
|
|
*/
|
|
initServiceWorker() {
|
|
if ('serviceWorker' in navigator) {
|
|
navigator.serviceWorker.ready.then(registration => {
|
|
//console.log('[UX Manager] Service Worker actif:', registration.scope);
|
|
}).catch(error => {
|
|
//console.warn('[UX Manager] Service Worker non disponible:', error);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// SOUS-CLASSE - NavigationManager (GESTION DES MENUS)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Gestionnaire spécialisé pour la navigation et les menus
|
|
* Contrôle l'ouverture/fermeture des menus dépliables
|
|
*/
|
|
class NavigationManager {
|
|
/**
|
|
* Constructeur - Initialise l'état de navigation
|
|
*/
|
|
constructor() {
|
|
this.currentOpenMenu = null; // Menu actuellement ouvert
|
|
this.activeMenuId = null; // Menu actif basé sur la page courante
|
|
this.isInitialized = false; // État d'initialisation
|
|
}
|
|
|
|
/**
|
|
* Initialiser le système de navigation
|
|
* @public - Méthode appelée par UXManager
|
|
*/
|
|
init() {
|
|
if (this.isInitialized) return;
|
|
|
|
// console.log('[Navigation] Initialisation du système de menus...');
|
|
|
|
// Appliquer le correctif d'urgence pour les menus multiples ouverts
|
|
this.applyEmergencyFix();
|
|
|
|
// Configuration des comportements
|
|
this.setupActiveMenu();
|
|
this.setupMenuBehavior();
|
|
this.setupKeyboardNavigation();
|
|
|
|
// Vérification finale après initialisation
|
|
setTimeout(() => {
|
|
this.validateMenuConsistency();
|
|
}, 300);
|
|
|
|
this.isInitialized = true;
|
|
//console.log('[Navigation] Système de menus initialisé');
|
|
}
|
|
|
|
/**
|
|
* Correctif d'urgence - Force un seul menu ouvert au chargement
|
|
* @private - Méthode de correction initiale
|
|
*/
|
|
applyEmergencyFix() {
|
|
// Détecter le menu actif depuis la configuration
|
|
const activeParentId = window.appConfig?.activeParentId;
|
|
|
|
if (activeParentId !== null && activeParentId !== '') {
|
|
this.activeMenuId = `submenu${activeParentId}`;
|
|
//console.log('[Navigation] Correctif: Menu actif détecté:', this.activeMenuId);
|
|
|
|
// Forcer la fermeture de tous les autres menus
|
|
this.forceSingleMenu();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forcer l'ouverture d'un seul menu (celui actif)
|
|
* @private - Méthode de correction forcée
|
|
*/
|
|
forceSingleMenu() {
|
|
if (!this.activeMenuId) return;
|
|
|
|
//console.log('[Navigation] Forçage: Un seul menu ouvert ->', this.activeMenuId);
|
|
|
|
// Étape 1: Fermer TOUS les menus
|
|
document.querySelectorAll('.nav-submenu').forEach(menu => {
|
|
if (menu.id !== this.activeMenuId) {
|
|
this.closeMenu(menu.id, true); // true = force close
|
|
}
|
|
});
|
|
|
|
// Étape 2: Ouvrir le menu actif
|
|
setTimeout(() => {
|
|
this.openMenu(this.activeMenuId);
|
|
this.currentOpenMenu = this.activeMenuId;
|
|
}, 50);
|
|
}
|
|
|
|
/**
|
|
* Configurer le menu actif basé sur la page courante
|
|
* @private - Méthode de configuration initiale
|
|
*/
|
|
setupActiveMenu() {
|
|
const activeParentId = window.appConfig?.activeParentId;
|
|
const activeLink = window.appConfig?.activeLink;
|
|
|
|
//console.log('[Navigation] Configuration:', { activeParentId, activeLink });
|
|
|
|
// CORRECTION : Si "Accueil" mais activeParentId != 0, corriger
|
|
if (activeLink === 'Accueil' && activeParentId !== '0') {
|
|
//console.warn('[Navigation] Correction: Accueil devrait être menu 0, pas', activeParentId);
|
|
this.activeMenuId = 'submenu0';
|
|
|
|
// Mettre à jour la config
|
|
if (window.appConfig) {
|
|
window.appConfig.activeParentId = '0';
|
|
}
|
|
}
|
|
// Cas normal
|
|
else if (activeParentId !== null && activeParentId !== '') {
|
|
this.activeMenuId = `submenu${activeParentId}`;
|
|
} else {
|
|
this.activeMenuId = null;
|
|
}
|
|
|
|
//console.log('[Navigation] Menu actif final:', this.activeMenuId);
|
|
|
|
// Appliquer
|
|
if (this.activeMenuId) {
|
|
setTimeout(() => {
|
|
this.openMenu(this.activeMenuId);
|
|
this.currentOpenMenu = this.activeMenuId;
|
|
}, 100);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configurer les comportements interactifs des menus
|
|
* @private - Méthode d'attachement des événements
|
|
*/
|
|
setupMenuBehavior() {
|
|
// Pour chaque lien de menu avec sous-menu (dépliable)
|
|
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); // Enlever le #
|
|
//console.log('[Navigation] Clic détecté sur menu:', targetId);
|
|
|
|
// Logique de basculement intelligent
|
|
if (this.currentOpenMenu === targetId) {
|
|
// Si on clique sur le menu déjà ouvert, on le ferme
|
|
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 en dehors de la sidebar
|
|
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 accidentelle en cliquant dans le sous-menu
|
|
document.querySelectorAll('.nav-submenu').forEach(menu => {
|
|
menu.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ouvrir un menu spécifique
|
|
* @param {string} menuId - ID du menu à ouvrir
|
|
* @private - Méthode d'ouverture de menu
|
|
*/
|
|
openMenu(menuId) {
|
|
const menu = document.getElementById(menuId);
|
|
const link = document.querySelector(`[href="#${menuId}"]`);
|
|
|
|
if (menu && link) {
|
|
// Vérifier si le menu est déjà ouvert
|
|
if (menu.classList.contains('show')) {
|
|
//console.log('[Navigation] Menu déjà ouvert:', menuId);
|
|
return;
|
|
}
|
|
|
|
//console.log('[Navigation] Ouverture du menu:', menuId);
|
|
|
|
// 1. Fermer 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)';
|
|
}
|
|
}
|
|
});
|
|
|
|
// 2. Ouvrir le menu sélectionné avec animations
|
|
menu.classList.add('show');
|
|
link.setAttribute('aria-expanded', 'true');
|
|
link.classList.add('active');
|
|
|
|
// 3. Animer la flèche
|
|
const arrow = link.querySelector('.nav-arrow');
|
|
if (arrow) arrow.style.transform = 'rotate(90deg)';
|
|
|
|
// 4. Annoncer pour l'accessibilité
|
|
const menuName = link.querySelector('.nav-text')?.textContent || 'Menu';
|
|
window.appUX?.accessibility.announce(`${menuName} ouvert`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fermer un menu spécifique
|
|
* @param {string} menuId - ID du menu à fermer
|
|
* @param {boolean} force - Forcer la fermeture même pour le menu actif
|
|
* @private - Méthode de fermeture de menu
|
|
*/
|
|
closeMenu(menuId, force = false) {
|
|
const menu = document.getElementById(menuId);
|
|
const link = document.querySelector(`[href="#${menuId}"]`);
|
|
|
|
if (menu && link) {
|
|
// Ne pas fermer le menu actif s'il correspond à la page courante (sauf si forcé)
|
|
if (!force && menuId === this.activeMenuId) {
|
|
//console.log('[Navigation] Menu actif, fermeture bloquée:', menuId);
|
|
return;
|
|
}
|
|
|
|
//console.log('[Navigation] Fermeture du menu:', menuId);
|
|
|
|
// Appliquer les changements visuels
|
|
menu.classList.remove('show');
|
|
link.setAttribute('aria-expanded', 'false');
|
|
link.classList.remove('active');
|
|
|
|
// Réinitialiser la flèche
|
|
const arrow = link.querySelector('.nav-arrow');
|
|
if (arrow) arrow.style.transform = 'rotate(0deg)';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fermer tous les menus (sauf le menu actif)
|
|
* @public - Méthode accessible globalement
|
|
*/
|
|
closeAllMenus() {
|
|
//console.log('[Navigation] Fermeture de tous les menus (sauf actif)');
|
|
|
|
document.querySelectorAll('.nav-submenu.show').forEach(menu => {
|
|
if (menu.id !== this.activeMenuId) {
|
|
this.closeMenu(menu.id);
|
|
}
|
|
});
|
|
|
|
// Réinitialiser l'état si aucun menu actif
|
|
if (!this.activeMenuId) {
|
|
this.currentOpenMenu = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fermer tous les menus sauf celui actif
|
|
* @public - Alias pour closeAllMenus avec nom plus explicite
|
|
*/
|
|
closeAllExceptActive() {
|
|
this.closeAllMenus();
|
|
}
|
|
|
|
/**
|
|
* Configurer la navigation au clavier
|
|
* @private - Méthode d'accessibilité clavier
|
|
*/
|
|
setupKeyboardNavigation() {
|
|
document.addEventListener('keydown', (e) => {
|
|
// Échap: fermer tous les menus
|
|
if (e.key === 'Escape') {
|
|
this.closeAllMenus();
|
|
}
|
|
|
|
// Flèches: navigation dans les menus
|
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
const focused = document.activeElement;
|
|
if (focused.classList.contains('nav-link')) {
|
|
e.preventDefault();
|
|
this.navigateMenu(focused, e.key);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Naviguer dans les menus avec les flèches
|
|
* @param {HTMLElement} currentElement - Élément actuellement focus
|
|
* @param {string} direction - Direction de navigation ('ArrowDown' ou 'ArrowUp')
|
|
* @private - Méthode de navigation clavier
|
|
*/
|
|
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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Vérifier la cohérence des menus
|
|
* @private - Méthode de validation
|
|
*/
|
|
validateMenuConsistency() {
|
|
//console.log('[Navigation] Vérification de cohérence...');
|
|
|
|
// 1. Vérifier que le menu actif est ouvert
|
|
if (this.activeMenuId) {
|
|
const activeMenu = document.getElementById(this.activeMenuId);
|
|
if (activeMenu && !activeMenu.classList.contains('show')) {
|
|
//console.warn('[Navigation] Menu actif non ouvert, correction...');
|
|
this.openMenu(this.activeMenuId);
|
|
}
|
|
}
|
|
|
|
// 2. Vérifier qu'un seul menu est ouvert
|
|
const openMenus = document.querySelectorAll('.nav-submenu.show');
|
|
if (openMenus.length > 1) {
|
|
//console.warn(`[Navigation] ${openMenus.length} menus ouverts, correction...`);
|
|
this.forceSingleMenu();
|
|
}
|
|
|
|
// 3. Vérifier la correspondance page/menu
|
|
this.validatePageMenuMatch();
|
|
}
|
|
|
|
/**
|
|
* Valider la correspondance entre la page et le menu ouvert
|
|
* @private - Méthode de validation avancée
|
|
*/
|
|
validatePageMenuMatch() {
|
|
const activeLink = window.appConfig?.activeLink;
|
|
if (!activeLink) return;
|
|
|
|
// Chercher si la page active est dans le menu ouvert
|
|
const openMenu = document.querySelector('.nav-submenu.show');
|
|
if (openMenu) {
|
|
const hasActiveLink = openMenu.querySelector(`a[href*="${activeLink}"]`);
|
|
if (!hasActiveLink) {
|
|
//console.warn(`[Navigation] Page "${activeLink}" non trouvée dans le menu ouvert ${openMenu.id}`);
|
|
|
|
// Essayer de trouver le bon menu
|
|
this.autoDetectCorrectMenu();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Détecter automatiquement le menu correct
|
|
* @private - Méthode d'auto-détection
|
|
*/
|
|
autoDetectCorrectMenu() {
|
|
const activeLink = window.appConfig?.activeLink;
|
|
if (!activeLink) return;
|
|
|
|
//console.log(`[Navigation] Auto-détection du menu pour "${activeLink}"...`);
|
|
|
|
// Chercher dans tous les menus
|
|
document.querySelectorAll('.nav-submenu').forEach(menu => {
|
|
const link = menu.querySelector(`a[href*="${activeLink}"]`);
|
|
if (link) {
|
|
const correctMenuId = menu.id;
|
|
//console.log(`[Navigation] Trouvé dans ${correctMenuId}`);
|
|
|
|
// Mettre à jour
|
|
this.activeMenuId = correctMenuId;
|
|
if (window.appConfig) {
|
|
const menuNum = correctMenuId.replace('submenu', '');
|
|
window.appConfig.activeParentId = menuNum;
|
|
}
|
|
|
|
// Ouvrir le bon menu
|
|
setTimeout(() => {
|
|
this.openMenu(correctMenuId);
|
|
this.currentOpenMenu = correctMenuId;
|
|
}, 100);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ouvrir un menu par son ID (méthode publique)
|
|
* @param {string} menuId - ID du menu à ouvrir
|
|
* @public - Méthode accessible globalement
|
|
*/
|
|
openMenuById(menuId) {
|
|
this.openMenu(menuId);
|
|
this.currentOpenMenu = menuId;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// SOUS-CLASSE - ContextPanelManager (PANEL CONTEXTUEL)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Gestionnaire du panneau contextuel latéral
|
|
* Contrôle l'affichage et les interactions du panel d'outils
|
|
*/
|
|
class ContextPanelManager {
|
|
/**
|
|
* Constructeur - Initialise les références DOM
|
|
*/
|
|
constructor() {
|
|
this.panel = document.getElementById('contextPanel');
|
|
this.toggleButton = document.querySelector('.context-toggle');
|
|
this.proximityArea = document.querySelector('.proximity-hover-area');
|
|
this.isOpen = false;
|
|
this.isInitialized = false;
|
|
}
|
|
|
|
/**
|
|
* Initialiser le panel contextuel
|
|
* @public - Méthode appelée par UXManager
|
|
*/
|
|
init() {
|
|
if (this.isInitialized || !this.panel) return;
|
|
|
|
//console.log('[Context Panel] Initialisation...');
|
|
|
|
this.setupProximityDetection();
|
|
this.setupKeyboardControls();
|
|
|
|
this.isInitialized = true;
|
|
}
|
|
|
|
/**
|
|
* Configurer la détection de proximité (hover)
|
|
* @private - Méthode d'interaction au survol
|
|
*/
|
|
setupProximityDetection() {
|
|
if (!this.proximityArea || !this.toggleButton) return;
|
|
|
|
// Animation au survol de la zone de proximité
|
|
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)';
|
|
}
|
|
});
|
|
|
|
// Opacité initiale
|
|
this.toggleButton.style.opacity = '0.2';
|
|
this.toggleButton.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
|
}
|
|
|
|
/**
|
|
* Configurer les contrôles clavier
|
|
* @private - Méthode d'accessibilité clavier
|
|
*/
|
|
setupKeyboardControls() {
|
|
document.addEventListener('keydown', (e) => {
|
|
// Ctrl+Shift+C : basculer le panel
|
|
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
|
|
e.preventDefault();
|
|
this.toggle();
|
|
}
|
|
|
|
// Échap : fermer le panel si ouvert
|
|
if (e.key === 'Escape' && this.isOpen) {
|
|
this.close();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Basculer l'état du panel (ouvrir/fermer)
|
|
* @public - Méthode accessible globalement
|
|
*/
|
|
toggle() {
|
|
if (this.isOpen) {
|
|
this.close();
|
|
} else {
|
|
this.open();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ouvrir le panel contextuel
|
|
* @private - Méthode d'ouverture
|
|
*/
|
|
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 pour l'accessibilité
|
|
setTimeout(() => {
|
|
const closeBtn = this.panel.querySelector('.context-close');
|
|
if (closeBtn) closeBtn.focus();
|
|
}, 300);
|
|
|
|
//console.log('[Context Panel] Ouvert');
|
|
window.appUX?.accessibility.announce('Panneau contextuel ouvert');
|
|
}
|
|
|
|
/**
|
|
* Fermer le panel contextuel
|
|
* @private - Méthode de fermeture
|
|
*/
|
|
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();
|
|
|
|
//console.log('[Context Panel] Fermé');
|
|
window.appUX?.accessibility.announce('Panneau contextuel fermé');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// SOUS-CLASSE - NotificationManager (GESTION DES NOTIFICATIONS)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Gestionnaire des notifications et messages système
|
|
*/
|
|
class NotificationManager {
|
|
/**
|
|
* Constructeur - Initialise le compteur de notifications
|
|
*/
|
|
constructor() {
|
|
this.countElement = document.getElementById('notificationCount');
|
|
this.unreadCount = 0;
|
|
this.pollingInterval = null;
|
|
}
|
|
|
|
/**
|
|
* Initialiser le système de notifications
|
|
* @public - Méthode appelée par UXManager
|
|
*/
|
|
init() {
|
|
//console.log('[Notifications] Initialisation...');
|
|
|
|
this.loadNotifications();
|
|
this.setupPolling();
|
|
}
|
|
|
|
/**
|
|
* Charger les notifications depuis le serveur
|
|
* @private - Méthode de chargement initial
|
|
*/
|
|
loadNotifications() {
|
|
// Simulation - À remplacer par un appel API réel
|
|
const mockCount = 3; // Exemple : 3 notifications non lues
|
|
this.updateCount(mockCount);
|
|
|
|
//console.log('[Notifications] Chargement simulé:', mockCount, 'notification(s)');
|
|
}
|
|
|
|
/**
|
|
* Mettre à jour le compteur de notifications
|
|
* @param {number} count - Nouveau nombre de notifications
|
|
* @private - Méthode de mise à jour d'affichage
|
|
*/
|
|
updateCount(count) {
|
|
this.unreadCount = count;
|
|
|
|
if (this.countElement) {
|
|
// Mettre à jour l'élément visuel
|
|
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;
|
|
|
|
// Annoncer pour l'accessibilité si nouveau message
|
|
if (count > 0) {
|
|
const message = window.appConfig?.isAnglophone
|
|
? `${count} new notification${count > 1 ? 's' : ''}`
|
|
: `${count} nouvelle${count > 1 ? 's' : ''} notification${count > 1 ? 's' : ''}`;
|
|
window.appUX?.accessibility.announce(message);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configurer le polling pour les nouvelles notifications
|
|
* @private - Méthode de vérification périodique
|
|
*/
|
|
setupPolling() {
|
|
// Vérifier les nouvelles notifications toutes les 30 secondes
|
|
this.pollingInterval = setInterval(() => {
|
|
this.checkNewNotifications();
|
|
}, 30000);
|
|
}
|
|
|
|
/**
|
|
* Vérifier les nouvelles notifications (méthode à implémenter)
|
|
* @private - Méthode d'appel API
|
|
*/
|
|
checkNewNotifications() {
|
|
// À implémenter : appel AJAX pour vérifier les nouvelles notifications
|
|
// console.log('[Notifications] Vérification des nouvelles notifications...');
|
|
}
|
|
|
|
/**
|
|
* Afficher la modale des messages
|
|
* @public - Méthode accessible globalement
|
|
*/
|
|
showMessagesModal() {
|
|
const modalElement = document.getElementById('messagesModal');
|
|
if (!modalElement) {
|
|
//console.error('[Notifications] Modale des messages non trouvée');
|
|
return;
|
|
}
|
|
|
|
const modal = new bootstrap.Modal(modalElement);
|
|
modal.show();
|
|
|
|
// Charger les messages
|
|
this.loadMessages();
|
|
|
|
//console.log('[Notifications] Affichage de la modale des messages');
|
|
}
|
|
|
|
/**
|
|
* Charger les messages dans la modale
|
|
* @private - Méthode de chargement de contenu
|
|
*/
|
|
loadMessages() {
|
|
const container = document.getElementById('div_messagerie');
|
|
if (!container) return;
|
|
|
|
// Simulation de chargement - À remplacer par un appel API réel
|
|
const messages = window.appConfig?.isAnglophone
|
|
? [
|
|
{ title: 'New prescription', time: '5 minutes ago', content: 'A new prescription has been created for the beneficiary.' },
|
|
{ title: 'Exemption approved', time: '2 hours ago', content: 'Your exemption request has been approved.' }
|
|
]
|
|
: [
|
|
{ title: 'Nouvelle prescription', time: 'Il y a 5 minutes', content: 'Une nouvelle prescription a été créée pour le bénéficiaire.' },
|
|
{ title: 'Dérogation approuvée', time: 'Il y a 2 heures', content: 'Votre demande de dérogation a été approuvée.' }
|
|
];
|
|
|
|
// Générer le HTML des messages
|
|
let messagesHTML = '<div class="list-group">';
|
|
messages.forEach(msg => {
|
|
messagesHTML += `
|
|
<div class="list-group-item">
|
|
<div class="d-flex w-100 justify-content-between">
|
|
<h6 class="mb-1">${msg.title}</h6>
|
|
<small>${msg.time}</small>
|
|
</div>
|
|
<p class="mb-1">${msg.content}</p>
|
|
</div>
|
|
`;
|
|
});
|
|
messagesHTML += '</div>';
|
|
|
|
container.innerHTML = messagesHTML;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// SOUS-CLASSE - AccessibilityManager (ACCESSIBILITÉ)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Gestionnaire d'accessibilité (WCAG, ARIA, etc.)
|
|
*/
|
|
class AccessibilityManager {
|
|
/**
|
|
* Constructeur - Initialise les fonctionnalités d'accessibilité
|
|
*/
|
|
constructor() {
|
|
this.liveRegion = null;
|
|
this.reducedMotion = false;
|
|
}
|
|
|
|
/**
|
|
* Initialiser les fonctionnalités d'accessibilité
|
|
* @public - Méthode appelée par UXManager
|
|
*/
|
|
init() {
|
|
//console.log('[Accessibility] Initialisation des fonctionnalités d\'accessibilités...');
|
|
|
|
this.setupFocusManagement();
|
|
this.setupAriaLiveRegions();
|
|
this.detectReducedMotion();
|
|
this.setupSkipLinks();
|
|
|
|
//console.log('[Accessibility] Fonctionnalités d\'accessibilité activées');
|
|
}
|
|
|
|
/**
|
|
* Configurer la gestion du focus (modales, etc.)
|
|
* @private - Méthode de gestion du focus
|
|
*/
|
|
setupFocusManagement() {
|
|
// Gérer le focus pour les modales Bootstrap
|
|
document.addEventListener('shown.bs.modal', (e) => {
|
|
const modal = e.target;
|
|
const focusable = modal.querySelector(
|
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
);
|
|
if (focusable) focusable.focus();
|
|
});
|
|
|
|
// Piéger le focus dans les modales
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Tab' && document.querySelector('.modal.show')) {
|
|
this.trapFocusInModal(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Piéger le focus dans une modale
|
|
* @param {KeyboardEvent} e - Événement clavier
|
|
* @private - Méthode de piégeage de focus
|
|
*/
|
|
trapFocusInModal(e) {
|
|
const modal = document.querySelector('.modal.show');
|
|
if (!modal) return;
|
|
|
|
const focusable = modal.querySelectorAll(
|
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
);
|
|
const firstFocusable = focusable[0];
|
|
const lastFocusable = focusable[focusable.length - 1];
|
|
|
|
if (e.shiftKey) {
|
|
// Shift + Tab
|
|
if (document.activeElement === firstFocusable) {
|
|
e.preventDefault();
|
|
lastFocusable.focus();
|
|
}
|
|
} else {
|
|
// Tab seul
|
|
if (document.activeElement === lastFocusable) {
|
|
e.preventDefault();
|
|
firstFocusable.focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configurer les régions ARIA live (annonces)
|
|
* @private - Méthode de création de régions ARIA
|
|
*/
|
|
setupAriaLiveRegions() {
|
|
// Créer une région ARIA live pour les annonces
|
|
this.liveRegion = document.createElement('div');
|
|
this.liveRegion.setAttribute('aria-live', 'polite');
|
|
this.liveRegion.setAttribute('aria-atomic', 'true');
|
|
this.liveRegion.className = 'visually-hidden';
|
|
this.liveRegion.setAttribute('role', 'status');
|
|
document.body.appendChild(this.liveRegion);
|
|
|
|
//console.log('[Accessibility] Région ARIA live créée');
|
|
}
|
|
|
|
/**
|
|
* Annoncer un message pour les lecteurs d'écran
|
|
* @param {string} message - Message à annoncer
|
|
* @public - Méthode accessible globalement
|
|
*/
|
|
announce(message) {
|
|
if (!this.liveRegion || !message) return;
|
|
|
|
// Effacer l'ancien message
|
|
this.liveRegion.textContent = '';
|
|
|
|
// Ajouter le nouveau message après un délai
|
|
setTimeout(() => {
|
|
this.liveRegion.textContent = message;
|
|
//console.log('[Accessibility] Annonce:', message);
|
|
}, 100);
|
|
}
|
|
|
|
/**
|
|
* Détecter la préférence de réduction des animations
|
|
* @private - Méthode de détection de préférence utilisateur
|
|
*/
|
|
detectReducedMotion() {
|
|
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
this.reducedMotion = mediaQuery.matches;
|
|
|
|
if (this.reducedMotion) {
|
|
document.documentElement.classList.add('reduced-motion');
|
|
//console.log('[Accessibility] Réduction des mouvements activée');
|
|
}
|
|
|
|
// Écouter les changements de préférence
|
|
mediaQuery.addEventListener('change', (e) => {
|
|
this.reducedMotion = e.matches;
|
|
if (this.reducedMotion) {
|
|
document.documentElement.classList.add('reduced-motion');
|
|
} else {
|
|
document.documentElement.classList.remove('reduced-motion');
|
|
}
|
|
//console.log('[Accessibility] Préférence de mouvement mise à jour:', this.reducedMotion);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Configurer les liens d'évitement (skip links)
|
|
* @private - Méthode d'accessibilité navigation
|
|
*/
|
|
setupSkipLinks() {
|
|
// Vérifier si les skip links existent déjà
|
|
const existingSkipLinks = document.querySelector('.skip-links');
|
|
if (existingSkipLinks) return;
|
|
|
|
// Créer les skip links
|
|
const skipLinks = document.createElement('nav');
|
|
skipLinks.className = 'skip-links';
|
|
skipLinks.setAttribute('aria-label', 'Liens d\'évitement');
|
|
skipLinks.innerHTML = `
|
|
<a href="#main-content" class="skip-link">Aller au contenu principal</a>
|
|
<a href="#navigation" class="skip-link">Aller à la navigation</a>
|
|
<a href="#footer" class="skip-link">Aller au pied de page</a>
|
|
`;
|
|
|
|
// Insérer au début du body
|
|
document.body.insertBefore(skipLinks, document.body.firstChild);
|
|
|
|
//console.log('[Accessibility] Liens d\'évitement créés');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// SOUS-CLASSE - PerformanceManager (PERFORMANCES)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Gestionnaire des performances et optimisation
|
|
*/
|
|
class PerformanceManager {
|
|
/**
|
|
* Constructeur - Initialise le monitoring de performance
|
|
*/
|
|
constructor() {
|
|
this.observers = [];
|
|
this.metrics = {};
|
|
}
|
|
|
|
/**
|
|
* Initialiser le monitoring de performance
|
|
* @public - Méthode appelée par UXManager
|
|
*/
|
|
init() {
|
|
//console.log('[Performance] Initialisation du monitoring...');
|
|
|
|
this.monitorPerformance();
|
|
this.setupLazyLoading();
|
|
this.setupIntersectionObserver();
|
|
this.setupIdleCallback();
|
|
|
|
//console.log('[Performance] Monitoring activé');
|
|
}
|
|
|
|
/**
|
|
* Monitorer les métriques de performance web vitales
|
|
* @private - Méthode de monitoring Core Web Vitals
|
|
*/
|
|
monitorPerformance() {
|
|
// Monitoring LCP (Largest Contentful Paint)
|
|
if ('PerformanceObserver' in window) {
|
|
const observer = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
switch (entry.entryType) {
|
|
case 'largest-contentful-paint':
|
|
this.metrics.lcp = entry.startTime;
|
|
//console.log('[Performance] LCP:', entry.startTime.toFixed(2), 'ms');
|
|
break;
|
|
case 'first-input':
|
|
this.metrics.fid = entry.processingStart - entry.startTime;
|
|
//console.log('[Performance] FID:', this.metrics.fid.toFixed(2), 'ms');
|
|
break;
|
|
case 'layout-shift':
|
|
if (!entry.hadRecentInput) {
|
|
this.metrics.cls = (this.metrics.cls || 0) + entry.value;
|
|
//console.log('[Performance] CLS cumulé:', this.metrics.cls.toFixed(4));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
observer.observe({
|
|
entryTypes: ['largest-contentful-paint', 'first-input', 'layout-shift']
|
|
});
|
|
|
|
this.observers.push(observer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configurer le lazy loading pour les images
|
|
* @private - Méthode d'optimisation de chargement
|
|
*/
|
|
setupLazyLoading() {
|
|
// Utiliser le lazy loading natif si disponible
|
|
if ('loading' in HTMLImageElement.prototype) {
|
|
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
|
|
img.addEventListener('load', () => {
|
|
img.classList.add('loaded');
|
|
//console.log('[Performance] Image chargée:', img.src);
|
|
});
|
|
|
|
img.addEventListener('error', () => {
|
|
//console.warn('[Performance] Erreur de chargement image:', img.src);
|
|
});
|
|
});
|
|
} else {
|
|
// Fallback pour les navigateurs plus anciens
|
|
this.setupIntersectionObserverForImages();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configurer l'Intersection Observer pour les images (fallback)
|
|
* @private - Méthode de lazy loading alternatif
|
|
*/
|
|
setupIntersectionObserverForImages() {
|
|
const lazyImages = document.querySelectorAll('img[data-src]');
|
|
|
|
const imageObserver = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const img = entry.target;
|
|
img.src = img.dataset.src;
|
|
img.classList.add('loaded');
|
|
imageObserver.unobserve(img);
|
|
}
|
|
});
|
|
});
|
|
|
|
lazyImages.forEach(img => imageObserver.observe(img));
|
|
this.observers.push(imageObserver);
|
|
}
|
|
|
|
/**
|
|
* Configurer l'Intersection Observer pour les éléments
|
|
* @private - Méthode d'animation au défilement
|
|
*/
|
|
setupIntersectionObserver() {
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.classList.add('in-view');
|
|
observer.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, {
|
|
threshold: 0.1,
|
|
rootMargin: '50px'
|
|
});
|
|
|
|
// Observer les cartes de contenu
|
|
document.querySelectorAll('.content-card').forEach(card => {
|
|
observer.observe(card);
|
|
});
|
|
|
|
this.observers.push(observer);
|
|
}
|
|
|
|
/**
|
|
* Configurer les callbacks idle (traitements en arrière-plan)
|
|
* @private - Méthode d'optimisation des ressources
|
|
*/
|
|
setupIdleCallback() {
|
|
if ('requestIdleCallback' in window) {
|
|
requestIdleCallback(() => {
|
|
// Tâches de basse priorité à exécuter pendant les périodes d'inactivité
|
|
this.cleanupOldData();
|
|
this.prefetchImportantLinks();
|
|
}, { timeout: 2000 });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Nettoyer les données anciennes
|
|
* @private - Méthode de nettoyage
|
|
*/
|
|
cleanupOldData() {
|
|
// Exemple : nettoyer le cache local si trop volumineux
|
|
// À implémenter selon les besoins
|
|
}
|
|
|
|
/**
|
|
* Précharger les liens importants
|
|
* @private - Méthode de préchargement
|
|
*/
|
|
prefetchImportantLinks() {
|
|
// Exemple : précharger les pages fréquemment visitées
|
|
// À implémenter selon l'analyse d'usage
|
|
}
|
|
|
|
/**
|
|
* Nettoyer les observers (pour la mémoire)
|
|
* @public - Méthode de nettoyage
|
|
*/
|
|
cleanup() {
|
|
this.observers.forEach(observer => {
|
|
if (observer && typeof observer.disconnect === 'function') {
|
|
observer.disconnect();
|
|
}
|
|
});
|
|
this.observers = [];
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// SOUS-CLASSE - LanguageManager (GESTION DES LANGUES)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Gestionnaire du changement de langue
|
|
*/
|
|
class LanguageManager {
|
|
/**
|
|
* Constructeur - Initialise les textes selon la langue
|
|
*/
|
|
constructor() {
|
|
this.currentLang = null;
|
|
}
|
|
|
|
/**
|
|
* Initialiser la gestion des langues
|
|
* @public - Méthode appelée par UXManager
|
|
*/
|
|
init() {
|
|
this.detectCurrentLanguage();
|
|
this.updateLanguageTexts();
|
|
|
|
//console.log('[Language] Gestionnaire de langue initialisé:', this.currentLang);
|
|
}
|
|
|
|
/**
|
|
* Détecter la langue actuelle depuis la configuration
|
|
* @private - Méthode de détection
|
|
*/
|
|
detectCurrentLanguage() {
|
|
this.currentLang = window.appConfig?.isAnglophone ? 'en_US' : 'fr_FR';
|
|
}
|
|
|
|
/**
|
|
* Changer la langue de l'interface
|
|
* @public - Méthode accessible globalement
|
|
*/
|
|
changeLanguage() {
|
|
const newLang = this.currentLang === 'en_US' ? 'fr_FR' : 'en_US';
|
|
const messages = window.appConfig?.isAnglophone
|
|
? {
|
|
title: 'Change Language',
|
|
text: 'Switch to French?',
|
|
confirm: 'Switch',
|
|
cancel: 'Cancel'
|
|
}
|
|
: {
|
|
title: 'Changer de langue',
|
|
text: 'Passer en Anglais?',
|
|
confirm: 'Changer',
|
|
cancel: 'Annuler'
|
|
};
|
|
|
|
// Demander confirmation à l'utilisateur
|
|
Swal.fire({
|
|
title: messages.title,
|
|
text: messages.text,
|
|
icon: 'question',
|
|
showCancelButton: true,
|
|
confirmButtonText: messages.confirm,
|
|
cancelButtonText: messages.cancel,
|
|
allowOutsideClick: true,
|
|
allowEscapeKey: true
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
// Rediriger vers la page de connexion avec le nouveau paramètre de langue
|
|
const redirectUrl = window.appConfig?.racineWeb +
|
|
'Connexion/index?langue=' + newLang;
|
|
//console.log('[Language] Changement de langue vers:', newLang);
|
|
window.location.href = redirectUrl;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mettre à jour les textes dynamiques selon la langue
|
|
* @private - Méthode de mise à jour des textes
|
|
*/
|
|
updateLanguageTexts() {
|
|
// À implémenter: mise à jour des textes dynamiques non gérés par le serveur
|
|
// Cette méthode peut être étendue pour gérer les textes côté client
|
|
|
|
// Exemple: Mettre à jour le titre de la page en fonction de la langue
|
|
const pageTitle = document.title;
|
|
if (this.currentLang === 'en_US' && !pageTitle.includes('RH Portal')) {
|
|
// Traduire vers l'anglais si nécessaire
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtenir la langue courante
|
|
* @returns {string} Code de langue (fr_FR ou en_US)
|
|
* @public - Méthode d'accès à la langue
|
|
*/
|
|
getCurrentLanguage() {
|
|
return this.currentLang;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// INITIALISATION ET EXPOSITION GLOBALE
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Initialiser l'application après le chargement du DOM
|
|
* Point d'entrée principal de l'application
|
|
*/
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
//console.log('[App] DOM chargé, initialisation de l\'application...');
|
|
|
|
// Vérifier que la configuration est disponible
|
|
if (!window.appConfig) {
|
|
//console.warn('[App] Configuration non trouvée, création par défaut...');
|
|
window.appConfig = {
|
|
activeParentId: '',
|
|
activeChildId: '',
|
|
activeLink: '',
|
|
racineWeb: '/',
|
|
baseUrl: '/',
|
|
isAnglophone: false,
|
|
debugMode: false
|
|
};
|
|
}
|
|
|
|
// Créer l'instance principale
|
|
window.appUX = new UXManager();
|
|
|
|
// Marquer le chargement comme terminé
|
|
setTimeout(() => {
|
|
document.body.classList.add('loaded');
|
|
//console.log('[App] Portail RH Inter Santé initialisé avec succès');
|
|
|
|
// Émettre un événement personnalisé
|
|
document.dispatchEvent(new CustomEvent('app:initialized'));
|
|
}, 100);
|
|
});
|
|
|
|
// ============================================================================
|
|
// FONCTIONS GLOBALES D'ACCÈS RAPIDE
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Obtenir le gestionnaire de notifications
|
|
* @returns {NotificationManager} Instance du gestionnaire de notifications
|
|
*/
|
|
function appNotifications() {
|
|
return window.appUX?.notifications;
|
|
}
|
|
|
|
/**
|
|
* Obtenir le gestionnaire de navigation
|
|
* @returns {NavigationManager} Instance du gestionnaire de navigation
|
|
*/
|
|
function appNavigation() {
|
|
return window.appUX?.navigation;
|
|
}
|
|
|
|
/**
|
|
* Obtenir le gestionnaire de langues
|
|
* @returns {LanguageManager} Instance du gestionnaire de langues
|
|
*/
|
|
function appLanguage() {
|
|
return window.appUX?.language;
|
|
}
|
|
|
|
/**
|
|
* Obtenir le gestionnaire d'accessibilité
|
|
* @returns {AccessibilityManager} Instance du gestionnaire d'accessibilité
|
|
*/
|
|
function appAccessibility() {
|
|
return window.appUX?.accessibility;
|
|
}
|
|
|
|
// ============================================================================
|
|
// API PUBLIQUE GLOBALE
|
|
// ============================================================================
|
|
|
|
/**
|
|
* API publique globale pour l'interaction avec le UX Manager
|
|
* Permet un accès simple aux fonctionnalités principales
|
|
*/
|
|
window.uxManager = {
|
|
/**
|
|
* Basculer le panneau contextuel
|
|
*/
|
|
toggleContextPanel: () => window.appUX?.toggleContextPanel(),
|
|
|
|
/**
|
|
* Afficher les notifications
|
|
*/
|
|
showNotifications: () => window.appUX?.notifications?.showMessagesModal(),
|
|
|
|
/**
|
|
* Changer la langue
|
|
*/
|
|
changeLanguage: () => window.appUX?.language?.changeLanguage(),
|
|
|
|
/**
|
|
* Annoncer un message pour l'accessibilité
|
|
* @param {string} message - Message à annoncer
|
|
*/
|
|
announce: (message) => window.appUX?.accessibility?.announce(message),
|
|
|
|
/**
|
|
* Ouvrir un menu spécifique
|
|
* @param {string} menuId - ID du menu à ouvrir
|
|
*/
|
|
openMenu: (menuId) => window.appUX?.navigation?.openMenuById(menuId),
|
|
|
|
/**
|
|
* Fermer tous les menus
|
|
*/
|
|
closeAllMenus: () => window.appUX?.navigation?.closeAllMenus(),
|
|
|
|
/**
|
|
* Tester le système (mode debug)
|
|
*/
|
|
testSystem: () => {
|
|
if (window.appConfig?.debugMode) {
|
|
/*console.group('[UX Manager] Test du système');
|
|
console.log('App UX:', window.appUX);
|
|
console.log('Navigation:', window.appUX?.navigation);
|
|
console.log('Config:', window.appConfig);
|
|
console.groupEnd();*/
|
|
}
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// EXPORT POUR LES MODULES ES6 (si utilisé avec bundler)
|
|
// ============================================================================
|
|
|
|
// Note: Décommentez si vous utilisez un bundler moderne
|
|
/*
|
|
export {
|
|
UXManager,
|
|
NavigationManager,
|
|
ContextPanelManager,
|
|
NotificationManager,
|
|
AccessibilityManager,
|
|
PerformanceManager,
|
|
LanguageManager
|
|
};
|
|
*/
|
|
|
|
//console.log('[UX Manager] Module chargé et prêt à l\'emploi');
|