MIGRATION VERS GITEA
This commit is contained in:
commit
5f9d991c15
333 files changed
+108514
No files matched your search
@@ -0,0 +1,488 @@
|
||||
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: 'Diffusion',
|
||||
items: [
|
||||
{ label: "Gestion d'informations", href: '/informations', match: ['/informations'] },
|
||||
{ label: 'Annonces sonores', href: '/annonces-sonores', match: ['/annonces-sonores'] }
|
||||
]
|
||||
},
|
||||
{
|
||||
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);
|
||||
|
||||
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';
|
||||
|
||||
return hasAccessTo(user, permSection);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (filteredItems.length === 0) return null;
|
||||
|
||||
return { ...group, items: filteredItems };
|
||||
}).filter(Boolean);
|
||||
|
||||
return (
|
||||
<header className="sncf-groupe app-header-shell">
|
||||
<div className="app-header-main">
|
||||
<a href={isLoggedIn ? '/dashboard' : '/'} className="app-header-brand" aria-label="Accueil gestion des exploitations">
|
||||
<img alt="SNCF" src={SNCF_LOGO_DATA_URI} />
|
||||
<div className="app-header-title">
|
||||
<h1>Gestion des exploitations</h1>
|
||||
<span>Panel d'administration regional</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{isLoggedIn && (
|
||||
<button
|
||||
type="button"
|
||||
className="app-header-menu-toggle"
|
||||
aria-label={isMenuOpen ? 'Fermer le menu' : 'Ouvrir le menu'}
|
||||
aria-expanded={isMenuOpen}
|
||||
onClick={handleMenuToggle}
|
||||
>
|
||||
<wcs-mat-icon icon={isMenuOpen ? 'close' : 'menu'}></wcs-mat-icon>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{isLoggedIn && (
|
||||
<nav className={`app-top-menu ${isMenuOpen ? 'open' : ''}`} aria-label="Navigation principale">
|
||||
<ul>
|
||||
{filteredMenuGroups.map((group) => {
|
||||
const subItems = group.items || [];
|
||||
const groupActive = group.match
|
||||
? isPathActive(currentPath, group.match)
|
||||
: subItems.some((item) => isPathActive(currentPath, item.match || [item.href]));
|
||||
|
||||
if (!subItems.length) {
|
||||
return (
|
||||
<li key={group.label} className="app-menu-group">
|
||||
<a
|
||||
className={groupActive ? 'active' : ''}
|
||||
href={group.href}
|
||||
aria-current={groupActive ? 'page' : undefined}
|
||||
onClick={closeMobileNavAfterClick}
|
||||
>
|
||||
{group.label}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={group.label} className={`app-menu-group has-submenu ${expandedGroup === group.label ? 'open' : ''}`}>
|
||||
<button
|
||||
type="button"
|
||||
className={groupActive ? 'active' : ''}
|
||||
aria-expanded={expandedGroup === group.label}
|
||||
onClick={() => handleSubmenuToggle(group.label)}
|
||||
>
|
||||
<span>{group.label}</span>
|
||||
<wcs-mat-icon icon="expand_more"></wcs-mat-icon>
|
||||
</button>
|
||||
<div className="app-submenu" role="menu" aria-label={group.label}>
|
||||
{subItems.map((item) => {
|
||||
const itemActive = isPathActive(currentPath, item.match || [item.href]);
|
||||
return (
|
||||
<a
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
role="menuitem"
|
||||
className={itemActive ? 'active' : ''}
|
||||
aria-current={itemActive ? 'page' : undefined}
|
||||
onClick={closeMobileNavAfterClick}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
)}
|
||||
|
||||
<div className="app-header-actions">
|
||||
{isLoggedIn && (
|
||||
<button
|
||||
type="button"
|
||||
className="app-header-notification-btn"
|
||||
aria-label="Ouvrir le centre de notifications"
|
||||
onClick={() => setIsNotificationsOpen(true)}
|
||||
>
|
||||
<wcs-mat-icon icon="notifications"></wcs-mat-icon>
|
||||
{notificationCount > 0 && <span className="app-header-notification-badge">{notificationCount > 99 ? '99+' : notificationCount}</span>}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{isLoggedIn && (
|
||||
<div className={`app-header-user-menu ${isUserMenuOpen ? 'open' : ''}`}>
|
||||
<button
|
||||
type="button"
|
||||
className="app-header-user-trigger"
|
||||
aria-expanded={isUserMenuOpen}
|
||||
aria-label="Ouvrir le menu utilisateur"
|
||||
onClick={handleUserMenuToggle}
|
||||
>
|
||||
<div className="app-header-user-pill" title={userName || 'Utilisateur'}>
|
||||
<wcs-mat-icon icon="account_circle"></wcs-mat-icon>
|
||||
<span>{userName || 'Utilisateur connecte'}</span>
|
||||
</div>
|
||||
<wcs-mat-icon icon="expand_more"></wcs-mat-icon>
|
||||
</button>
|
||||
|
||||
<div className="app-header-user-accordion" role="menu" aria-label="Menu utilisateur">
|
||||
<a href={accountPageHref} role="menuitem" onClick={() => setIsUserMenuOpen(false)}>
|
||||
<wcs-mat-icon icon="manage_accounts"></wcs-mat-icon>
|
||||
<span>Gestion du compte</span>
|
||||
</a>
|
||||
<button type="button" role="menuitem" onClick={handleAuthAndClose}>
|
||||
<wcs-mat-icon icon="logout"></wcs-mat-icon>
|
||||
<span>Deconnexion</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLoggedIn && isNotificationsOpen && (
|
||||
<div className="app-notifications-overlay" onClick={() => setIsNotificationsOpen(false)}>
|
||||
<aside className="app-notifications-drawer" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="app-notifications-head">
|
||||
<h3>Notifications</h3>
|
||||
<button type="button" onClick={() => setIsNotificationsOpen(false)} aria-label="Fermer le panneau notifications">
|
||||
<wcs-mat-icon icon="close"></wcs-mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{notifications.length === 0 ? (
|
||||
<p className="app-notifications-empty">Aucune notification pour le moment.</p>
|
||||
) : (
|
||||
<div className="app-notifications-list">
|
||||
{notifications.map((notification) => (
|
||||
<button key={notification.id} type="button" className={`app-notification-item ${notification.read ? 'read' : 'unread'}`} onClick={() => handleNotificationClick(notification)}>
|
||||
<span className="app-notification-type">{notification.type === 'system' ? 'Système' : 'Messagerie'}</span>
|
||||
<strong>{notification.title}</strong>
|
||||
<span>{notification.description}</span>
|
||||
{notification.progress !== undefined && notification.progress !== null && (
|
||||
<div className="app-notification-progress-container">
|
||||
<div className="app-notification-progress-bar" style={{ width: `${notification.progress}%` }}></div>
|
||||
</div>
|
||||
)}
|
||||
<small>{notification.read ? 'Lu' : 'Non lu'}</small>
|
||||
<small>{new Date(notification.date).toLocaleString('fr-FR')}</small>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</aside>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user