import React, { useEffect, useState } from 'react'; import { getCurrentUser, hasAccessTo } from '../../utils/userUtils'; import { getThreadsForUser } from '../../api/messagerie'; import { getAllUtilisateurs } from '../../api/utilisateurs'; import './AppHeader.css'; const NOTIFICATIONS_READ_PREFIX = 'app_notifications_read_v1'; const SNCF_LOGO_DATA_URI = "data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'%20standalone='no'?%3e%3csvg%20version='1.0'%20viewBox='0%200%2070%2036.799999'%20xml:space='preserve'%20id='svg17'%20width='70'%20height='36.799999'%20xmlns='http://www.w3.org/2000/svg'%20xmlns:svg='http://www.w3.org/2000/svg'%3e%3cdefs%20id='defs21'%20/%3e%3clinearGradient%20id='SVG_GRADIENT'%20gradientUnits='userSpaceOnUse'%20x1='0'%20y1='16'%20x2='31.959299'%20y2='16'%20gradientTransform='matrix(2.1849396,0,0,2.1904762,-3.0604997e-7,-16.64762)'%3e%3cstop%20offset='0'%20style='stop-color:%23832F70'%20id='stop2'%20/%3e%3cstop%20offset='0.2472'%20style='stop-color:%23AC287C'%20id='stop4'%20/%3e%3cstop%20offset='0.8146'%20style='stop-color:%23DF2530'%20id='stop6'%20/%3e%3cstop%20offset='1'%20style='stop-color:%23DF2530'%20id='stop8'%20/%3e%3c/linearGradient%3e%3cpath%20fill='url(%23SVG_GRADIENT)'%20d='M%201.3109633,0.21904772%20C%200.43698745,0.21904772%200,0.43809528%200,1.3142858%20V%2035.704762%20C%200,36.361904%200.43698745,36.8%201.0924696,36.8%20H%2062.270776%20c%200.655482,0%201.09247,0%201.310963,-0.876191%200,0%206.117832,-19.495238%206.336326,-19.714286%200.218494,-0.657143%200,-1.095237%20-0.655483,-1.533333%20C%2049.379634,1.9714287%2019.445962,0%206.1178299,0%204.1513848,0%202.403433,0%201.3109633,0.21904772%20Z'%20id='path11'%20style='fill:url(%23SVG_GRADIENT);stroke-width:2.18771'%20/%3e%3cpath%20fill='%23ffffff'%20d='m%2013.983612,22.12381%20c%20-2.184939,-1.095239%20-3.714396,-1.752382%20-3.495902,-2.84762%200.436987,-1.314285%202.840421,-1.752381%204.806867,-1.752381%201.092469,0%201.747951,0.219047%202.621926,0.438096%20l%200.655482,-2.409525%20c%20-1.09247,-0.219047%20-1.747951,-0.219047%20-3.058915,-0.219047%20-4.151384,0%20-7.8657819,1.095238%20-8.7397579,3.504761%20-1.0924696,2.847619%201.966446,4.380953%204.8068679,5.695239%202.184939,1.095238%203.93289,1.971428%203.277408,3.285715%20-0.436987,1.095237%20-1.966446,1.533332%20-4.369878,1.533332%20-1.9664456,0%20-4.3698801,-0.657142%20-6.3363252,-1.533332%20l%20-1.0924697,2.409523%20c%201.3109634,0.657143%204.3698793,1.533333%206.7733121,1.533333%204.5883738,0%207.6472878,-1.095237%208.9582518,-3.723809%201.529458,-2.847619%20-1.966445,-4.600001%20-4.806867,-5.914285%20z'%20id='path13'%20style='stroke-width:2.18771'%20/%3e%3cpath%20fill='%23ffffff'%20d='m%2020.319937,26.285714%20c%200,0.219047%200,0.219047%200,0%200.218494,0%200.218494,0%200.218494,0%200,0%202.184939,-7.009524%202.184939,-7.009524%202.403434,3.285714%204.588373,7.666667%205.899337,12.266667%20h%203.714398%20l%201.966445,-5.914286%20c%200,0%200,0%200.218494,0%200,0%200,0%200.218493,0%200.655483,3.723809%204.151386,6.133333%209.395241,6.133333%203.277409,0%206.336325,-0.87619%206.991806,-1.314286%20l%201.966446,-6.133333%20h%207.428794%20l%200.655483,-2.409523%20h%20-7.428796%20l%201.310964,-3.942857%20h%208.958253%20L%2064.892704,15.55238%20H%2052.438549%20l%20-4.151387,13.361905%20c%20-0.873975,0.219048%20-1.966445,0.438095%20-3.277409,0.438095%20-2.403432,0%20-4.588373,-0.657142%20-5.680843,-1.971428%20-0.873975,-1.095238%20-1.092469,-2.190476%20-0.873975,-3.72381%200.436988,-3.504761%203.932891,-5.695237%208.302771,-5.695237%200.873975,0%201.966445,0%202.840421,0.219047%20l%200.873976,-2.628572%20c%20-1.092469,-0.219047%20-2.403435,-0.219047%20-3.714397,-0.219047%20-3.932892,0%20-7.428796,1.314286%20-9.613735,3.723809%200,0%20-0.218493,0%20-0.218493,0%200,0%20-0.218494,0%200,0%20l%201.09247,-3.504762%20H%2034.740537%20L%2030.589152,26.72381%20C%2029.496683,23.438094%2027.09325,19.057142%2024.252828,15.55238%20h%20-3.495904%20l%20-2.621926,8.104762%20c%201.310964,0.876191%201.747952,1.752381%202.184939,2.628572%20z'%20id='path15'%20style='stroke-width:2.18771'%20/%3e%3c/svg%3e"; const MENU_GROUPS = [ { label: 'Dashboard', href: '/dashboard', match: ['/dashboard', '/'] }, { label: 'Perturbations', items: [ { label: 'Gestion des travaux', href: '/travaux', match: ['/travaux'] }, { label: 'Gestion des perturbations', href: '/perturbations', match: ['/perturbations'] } ] }, { label: 'Exploitation', items: [ { label: 'Gestion des gares', href: '/gares', match: ['/gares'] }, { label: 'Gestion des materiels roulants', href: '/materiel-roulant', match: ['/materiel-roulant'] }, { label: 'Gestion des horaires', href: '/horaires', match: ['/horaires'] }, { label: 'Gestion des quais', href: '/quais', match: ['/quais'] }, { label: 'Attribution Materiel-Roulant', href: '/attribution-materiel-roulant', match: ['/attribution-materiel-roulant'] }, { label: 'Gestion des tarifications', href: '/tarifs', match: ['/tarifs'] } ] }, { label: 'Configuration', items: [ { label: 'Gestion des parametres', href: '/parametres', match: ['/parametres'] }, { label: 'Gestion des menus regionaux', href: '/menu', match: ['/menu'] }, { label: 'Mise à jour systeme', href: '/systeme-maj', match: ['/systeme-maj'] } ] }, { label: 'Diffusion', items: [ { label: "Gestion d'informations", href: '/informations', match: ['/informations'] }, { label: 'Annonces sonores', href: '/annonces-sonores', match: ['/annonces-sonores'] }, { label: 'Fiches horaires', href: '/fiches-horaires', match: ['/fiches-horaires'] } ] }, { label: 'Messagerie', href: '/messagerie', match: ['/messagerie'] } ]; function isPathActive(currentPath, matchList) { return matchList.some((path) => { if (path === '/') return currentPath === '/' || currentPath.startsWith('/dashboard'); return currentPath.startsWith(path); }); } export function addSystemNotification(userId, notif) { if (!userId) return null; try { const existing = JSON.parse(window.localStorage.getItem(`app_system_notifications_${userId}`)) || []; const id = Date.now(); existing.push({ id, date: new Date().toISOString(), ...notif }); window.localStorage.setItem(`app_system_notifications_${userId}`, JSON.stringify(existing)); window.dispatchEvent(new Event('app:notificationsChanged')); return id; } catch(_) { return null; } } export function updateSystemNotification(userId, notifId, updatedFields) { if (!userId || !notifId) return; try { const existing = JSON.parse(window.localStorage.getItem(`app_system_notifications_${userId}`)) || []; const index = existing.findIndex(n => n.id === notifId); if (index !== -1) { existing[index] = { ...existing[index], ...updatedFields }; window.localStorage.setItem(`app_system_notifications_${userId}`, JSON.stringify(existing)); window.dispatchEvent(new Event('app:notificationsChanged')); } } catch(_) {} } function getAccountPageHref() { return '/mon-compte'; } function getReadNotificationsForUser(userId) { if (!userId) return []; try { const raw = window.localStorage.getItem(`${NOTIFICATIONS_READ_PREFIX}_${userId}`); const parsed = raw ? JSON.parse(raw) : []; return Array.isArray(parsed) ? parsed : []; } catch (_) { return []; } } function saveReadNotificationsForUser(userId, notificationIds) { if (!userId) return; try { window.localStorage.setItem(`${NOTIFICATIONS_READ_PREFIX}_${userId}`, JSON.stringify(notificationIds || [])); } catch (_) { // ignore localStorage failures } } export default function AppHeader({ isLoggedIn, userName }) { const currentPath = window.location.pathname; const [isMenuOpen, setIsMenuOpen] = useState(false); const [expandedGroup, setExpandedGroup] = useState(null); const [isUserMenuOpen, setIsUserMenuOpen] = useState(false); const [isNotificationsOpen, setIsNotificationsOpen] = useState(false); const [notifications, setNotifications] = useState([]); const [notificationCount, setNotificationCount] = useState(0); const [isDarkMode, setIsDarkMode] = useState(() => localStorage.getItem('theme') === 'dark'); const toggleDarkMode = () => { setIsDarkMode(prev => { const next = !prev; localStorage.setItem('theme', next ? 'dark' : 'light'); if (next) { document.body.classList.add('dark-mode'); document.documentElement.setAttribute('data-theme', 'dark'); } else { document.body.classList.remove('dark-mode'); document.documentElement.setAttribute('data-theme', 'light'); } return next; }); }; useEffect(() => { const handleResize = () => { if (window.innerWidth > 980) { setIsMenuOpen(false); setExpandedGroup(null); } }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); useEffect(() => { const handleDocumentClick = (event) => { const target = event.target; if (!(target instanceof Element)) return; if (!target.closest('.app-header-user-menu')) { setIsUserMenuOpen(false); } if (!target.closest('.has-submenu')) { setExpandedGroup(null); } }; if (isUserMenuOpen || expandedGroup) { document.addEventListener('click', handleDocumentClick); } return () => document.removeEventListener('click', handleDocumentClick); }, [isUserMenuOpen, expandedGroup]); useEffect(() => { if (!isLoggedIn) { setNotifications([]); setNotificationCount(0); return; } let active = true; const loadNotifications = async () => { const currentUser = getCurrentUser(); if (!currentUser?.id) return; const [{ data: threads }, { data: users }] = await Promise.all([ getThreadsForUser(currentUser.id), getAllUtilisateurs() ]); if (!active) return; const usersById = {}; (users || []).forEach((u) => { usersById[u.id] = u; }); const readIds = new Set(getReadNotificationsForUser(currentUser.id)); const items = (threads || []) .filter((thread) => thread?.last_message && String(thread.last_message.sender_id) !== String(currentUser.id)) .map((thread) => { const sender = usersById[thread.last_message.sender_id] || {}; const senderName = `${sender.prenom || ''} ${sender.nom || ''}`.trim() || sender.email || 'Utilisateur'; return { id: `msg-${thread.last_message.id}`, type: 'messagerie', title: 'Nouveau message', description: `${senderName}: ${thread.last_message.contenu || ''}`, date: thread.last_message.created_at, threadId: thread.id, read: readIds.has(`msg-${thread.last_message.id}`) }; }) .sort((a, b) => String(b.date).localeCompare(String(a.date))); let systemItems = []; try { const sysRaw = JSON.parse(window.localStorage.getItem(`app_system_notifications_${currentUser.id}`)) || []; systemItems = sysRaw.map(n => ({ id: `sys-${n.id}`, type: 'system', title: n.title, description: n.description, date: n.date, link: n.link, progress: n.progress, read: readIds.has(`sys-${n.id}`) })); } catch(_) {} const allItems = [...items, ...systemItems].sort((a, b) => String(b.date).localeCompare(String(a.date))); setNotifications(allItems); setNotificationCount(allItems.filter((item) => !item.read).length); }; loadNotifications(); const onNotificationsChanged = () => loadNotifications(); window.addEventListener('app:notificationsChanged', onNotificationsChanged); window.addEventListener('app:authChanged', onNotificationsChanged); return () => { active = false; window.removeEventListener('app:notificationsChanged', onNotificationsChanged); window.removeEventListener('app:authChanged', onNotificationsChanged); }; }, [isLoggedIn]); const handleAuthClick = () => { if (isLoggedIn) { window.location.href = '/logout'; } }; const handleMenuToggle = () => { setIsMenuOpen((prev) => !prev); if (isMenuOpen) setExpandedGroup(null); }; const handleSubmenuToggle = (groupLabel) => { setExpandedGroup((prev) => (prev === groupLabel ? null : groupLabel)); }; const closeMobileNavAfterClick = () => { if (window.innerWidth <= 980) { setIsMenuOpen(false); setExpandedGroup(null); } }; const handleUserMenuToggle = (event) => { event.stopPropagation(); setIsUserMenuOpen((prev) => !prev); }; const handleAuthAndClose = () => { setIsUserMenuOpen(false); handleAuthClick(); }; const handleNotificationClick = (notification) => { const currentUser = getCurrentUser(); const alreadyRead = !!notification?.read; if (currentUser?.id && notification?.id && !alreadyRead) { const existing = getReadNotificationsForUser(currentUser.id); const next = [...new Set([...existing, notification.id])]; saveReadNotificationsForUser(currentUser.id, next); const nextNotifications = notifications.map((n) => (n.id === notification.id ? { ...n, read: true } : n)); setNotifications(nextNotifications); setNotificationCount(nextNotifications.filter((n) => !n.read).length); } if (notification?.threadId) { try { window.localStorage.setItem('messagerie_active_thread', notification.threadId); } catch (_) {} window.location.href = '/messagerie'; return; } if (notification?.link) { window.location.href = notification.link; return; } setIsNotificationsOpen(false); }; const accountPageHref = getAccountPageHref(); const user = getCurrentUser(); const filteredMenuGroups = MENU_GROUPS.map(group => { // Cas où le groupe a ses propres attributs 'href' et 'match' (par exemple: Dashboard, Messagerie) if (!group.items) { const section = group.href.split('/')[1] || group.href.replace('/', '') || 'dashboard'; if (!hasAccessTo(user, section)) return null; return group; } // Cas où le groupe contient des sous-éléments const filteredItems = group.items.filter(item => { if (item.href) { const section = item.href.split('/')[1] || item.href.replace('/', ''); let permSection = section; // Mappage des URLs vers les permissions exactes du système if (section === 'materiel-roulant') permSection = 'materiel_roulant'; else if (section === 'attribution-materiel-roulant') permSection = 'horaires'; else if (section === 'tarifs') permSection = 'tarifs'; else if (section === 'menu') permSection = 'menu'; else if (section === 'travaux') permSection = 'travaux'; else if (section === 'perturbations') permSection = 'perturbations'; else if (section === 'pages') permSection = 'pages'; else if (section === 'informations') permSection = 'informations'; else if (section === 'parametres') permSection = 'parametres'; else if (section === 'gares') permSection = 'gares'; else if (section === 'quais') permSection = 'quais'; else if (section === 'annonces-sonores') permSection = 'annonces_sonores'; else if (section === 'fiches-horaires') permSection = 'fiches_horaires'; else if (section === 'systeme-maj') permSection = 'systeme_maj'; return hasAccessTo(user, permSection) || (permSection === 'fiches_horaires' && hasAccessTo(user, 'horaires')); } return true; }); if (filteredItems.length === 0) return null; return { ...group, items: filteredItems }; }).filter(Boolean); return (
SNCF

Gestion des exploitations

Panel d'administration regional
{isLoggedIn && ( )} {isLoggedIn && ( )}
{isLoggedIn && ( )} {isLoggedIn && (
setIsUserMenuOpen(false)}> Gestion du compte
)}
{isLoggedIn && isNotificationsOpen && (
setIsNotificationsOpen(false)}>
)}
); }