511 lines
16 KiB
JavaScript
511 lines
16 KiB
JavaScript
// 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
|
|
class NavigationManager {
|
|
constructor() {
|
|
this.currentOpenMenu = null;
|
|
}
|
|
|
|
init() {
|
|
this.setupMenuBehavior();
|
|
this.setupKeyboardNavigation();
|
|
this.setupActiveMenu();
|
|
}
|
|
|
|
setupMenuBehavior() {
|
|
// Désactiver le comportement Bootstrap par défaut
|
|
document.querySelectorAll('.nav-link[data-bs-toggle="collapse"]').forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
const targetId = link.getAttribute('href').substring(1);
|
|
this.toggleMenu(targetId, e);
|
|
});
|
|
});
|
|
|
|
// Fermer le menu en cliquant ailleurs
|
|
document.addEventListener('click', (e) => {
|
|
if (!e.target.closest('.app-sidebar')) {
|
|
this.closeAllMenus();
|
|
}
|
|
});
|
|
}
|
|
|
|
toggleMenu(menuId, event) {
|
|
if (event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
|
|
const menu = document.getElementById(menuId);
|
|
const link = document.querySelector(`[href="#${menuId}"]`);
|
|
|
|
if (!menu || !link) return;
|
|
|
|
// Si ce menu est déjà ouvert, le fermer
|
|
if (this.currentOpenMenu === menuId) {
|
|
this.closeMenu(menuId);
|
|
this.currentOpenMenu = null;
|
|
} else {
|
|
// Fermer le menu actuellement ouvert
|
|
if (this.currentOpenMenu) {
|
|
this.closeMenu(this.currentOpenMenu);
|
|
}
|
|
|
|
// Ouvrir le nouveau menu
|
|
this.openMenu(menuId);
|
|
this.currentOpenMenu = menuId;
|
|
}
|
|
}
|
|
|
|
openMenu(menuId) {
|
|
const menu = document.getElementById(menuId);
|
|
const link = document.querySelector(`[href="#${menuId}"]`);
|
|
|
|
if (menu && link) {
|
|
menu.classList.add('show');
|
|
link.classList.add('active');
|
|
link.setAttribute('aria-expanded', 'true');
|
|
link.querySelector('.nav-arrow').style.transform = 'rotate(90deg)';
|
|
}
|
|
}
|
|
|
|
closeMenu(menuId) {
|
|
const menu = document.getElementById(menuId);
|
|
const link = document.querySelector(`[href="#${menuId}"]`);
|
|
|
|
if (menu && link) {
|
|
menu.classList.remove('show');
|
|
link.classList.remove('active');
|
|
link.setAttribute('aria-expanded', 'false');
|
|
link.querySelector('.nav-arrow').style.transform = 'rotate(0deg)';
|
|
}
|
|
}
|
|
|
|
closeAllMenus() {
|
|
document.querySelectorAll('.nav-submenu.show').forEach(menu => {
|
|
const menuId = menu.id;
|
|
this.closeMenu(menuId);
|
|
});
|
|
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();
|
|
}
|
|
}
|
|
|
|
setupActiveMenu() {
|
|
// Ouvrir automatiquement le menu actif au chargement
|
|
const activeParentId = window.appConfig.activeParentId;
|
|
if (activeParentId !== null && activeParentId !== '') {
|
|
const menuId = `submenu${activeParentId}`;
|
|
setTimeout(() => {
|
|
this.openMenu(menuId);
|
|
this.currentOpenMenu = menuId;
|
|
}, 100);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 = `
|
|
<div class="list-group">
|
|
<div class="list-group-item">
|
|
<div class="d-flex w-100 justify-content-between">
|
|
<h6 class="mb-1">Nouvelle prescription</h6>
|
|
<small>Il y a 5 minutes</small>
|
|
</div>
|
|
<p class="mb-1">Une nouvelle prescription a été créée pour le bénéficiaire.</p>
|
|
</div>
|
|
<div class="list-group-item">
|
|
<div class="d-flex w-100 justify-content-between">
|
|
<h6 class="mb-1">Dérogation approuvée</h6>
|
|
<small>Il y a 2 heures</small>
|
|
</div>
|
|
<p class="mb-1">Votre demande de dérogation a été approuvée.</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Accessibility Manager
|
|
class AccessibilityManager {
|
|
init() {
|
|
this.setupFocusManagement();
|
|
this.setupAriaLiveRegions();
|
|
this.detectReducedMotion();
|
|
}
|
|
|
|
setupFocusManagement() {
|
|
// Gérer le focus pour les modales
|
|
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();
|
|
});
|
|
}
|
|
|
|
setupAriaLiveRegions() {
|
|
// Créer une région ARIA live pour les notifications
|
|
const liveRegion = document.createElement('div');
|
|
liveRegion.setAttribute('aria-live', 'polite');
|
|
liveRegion.setAttribute('aria-atomic', 'true');
|
|
liveRegion.className = 'visually-hidden';
|
|
document.body.appendChild(liveRegion);
|
|
}
|
|
|
|
detectReducedMotion() {
|
|
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
if (mediaQuery.matches) {
|
|
document.documentElement.classList.add('reduced-motion');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Performance Manager
|
|
class PerformanceManager {
|
|
init() {
|
|
this.monitorPerformance();
|
|
this.setupLazyLoading();
|
|
this.setupIntersectionObserver();
|
|
}
|
|
|
|
monitorPerformance() {
|
|
if ('PerformanceObserver' in window) {
|
|
const observer = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
if (entry.entryType === 'largest-contentful-paint') {
|
|
console.log('[Performance] LCP:', entry.startTime);
|
|
}
|
|
}
|
|
});
|
|
|
|
observer.observe({ entryTypes: ['largest-contentful-paint', 'first-input'] });
|
|
}
|
|
}
|
|
|
|
setupLazyLoading() {
|
|
// Lazy loading pour les images
|
|
if ('loading' in HTMLImageElement.prototype) {
|
|
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
|
|
img.addEventListener('load', () => {
|
|
img.classList.add('loaded');
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
setupIntersectionObserver() {
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.classList.add('in-view');
|
|
}
|
|
});
|
|
}, {
|
|
threshold: 0.1
|
|
});
|
|
|
|
document.querySelectorAll('.content-card').forEach(card => {
|
|
observer.observe(card);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Language Manager
|
|
class LanguageManager {
|
|
changeLanguage() {
|
|
const currentLang = window.appConfig.isAnglophone ? 'en_US' : 'fr_FR';
|
|
const newLang = currentLang === 'en_US' ? 'fr_FR' : 'en_US';
|
|
|
|
// Demander confirmation
|
|
const message = window.appConfig.isAnglophone
|
|
? 'Switch to French?'
|
|
: 'Passer en Anglais?';
|
|
|
|
Swal.fire({
|
|
title: window.appConfig.isAnglophone ? 'Change Language' : 'Changer de langue',
|
|
text: message,
|
|
icon: 'question',
|
|
showCancelButton: true,
|
|
confirmButtonText: window.appConfig.isAnglophone ? 'Switch' : 'Changer',
|
|
cancelButtonText: window.appConfig.isAnglophone ? 'Cancel' : 'Annuler'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
// Rediriger vers la page de connexion avec le nouveau paramètre de langue
|
|
window.location.href = window.appConfig.racineWeb +
|
|
'Connexion/index?langue=' + newLang;
|
|
}
|
|
});
|
|
}
|
|
|
|
init() {
|
|
// Initialiser les textes selon la langue
|
|
this.updateLanguageTexts();
|
|
}
|
|
|
|
updateLanguageTexts() {
|
|
// À implémenter: mise à jour des textes dynamiques
|
|
}
|
|
}
|
|
|
|
// Initialisation de l'application
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.appUX = new UXManager();
|
|
console.log('[App] Portail RH Inter Santé initialisé');
|
|
|
|
// Déclencher les animations après le chargement
|
|
setTimeout(() => {
|
|
document.body.classList.add('loaded');
|
|
}, 100);
|
|
});
|
|
|
|
// Fonctions globales
|
|
function appNotifications() {
|
|
return window.appUX?.notifications;
|
|
}
|
|
|
|
function appNavigation() {
|
|
return window.appUX?.navigation;
|
|
}
|
|
|
|
function appLanguage() {
|
|
return window.appUX?.language;
|
|
}
|
|
|
|
// Export pour les scripts externes
|
|
window.uxManager = {
|
|
toggleContextPanel: () => window.appUX?.toggleContextPanel(),
|
|
toggleSidebar: () => window.appUX?.toggleSidebar(),
|
|
showNotifications: () => window.appUX?.notifications.showMessagesModal()
|
|
}; |