This commit is contained in:
2026-06-18 21:04:54 +02:00
parent e94401dce8
commit fea1d787ca
+71 -22
View File
@@ -510,6 +510,7 @@ export default function Communication() {
const { user, isAdmin } = useAuth();
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const [notifMenuPos, setNotifMenuPos] = useState(null); // { x, y }
const [tab, setTab] = useState('support'); // 'support' | 'notifications'
const [showBroadcastForm, setShowBroadcastForm] = useState(false);
@@ -718,6 +719,16 @@ export default function Communication() {
setBcSending(false);
};
// ── Supprimer toutes les notifications ────────────────────────────────────
const deleteAllNotifs = async () => {
try {
await Promise.all(notifs.map(n => api.del(`/notifications/${n.id}`)));
setNotifs([]);
setSelectedNotif(null);
window.dispatchEvent(new CustomEvent('notif:refresh'));
} catch { /* silencieux */ }
};
// ── Supprimer une notification ────────────────────────────────────────────
const deleteNotif = async (id) => {
try {
@@ -787,26 +798,26 @@ export default function Communication() {
</div>
{/* Colonne 2 — Dossier + filtres */}
<div className="comm-topbar-2">
<span className="comm-topbar-folder">{tab === 'support' ? 'Support' : 'Notifications'}</span>
<div className="comm-topbar-filters">
<span className="comm-topbar-folder" style={{ fontSize: 15, fontWeight: 700 }}>{tab === 'support' ? 'Support' : 'Notifications'}</span>
{tab === 'support' && (
<>
<div className="comm-topbar-filters">
{[['all','Tous'],['open','Ouverts'],['resolved','Résolus']].map(([v,l]) => (
<button key={v} className={`comm-topbar-filter-btn${ticketFilter === v ? ' active' : ''}`} onClick={() => setTicketFilter(v)}>{l}</button>
))}
</>
</div>
)}
{tab === 'notifications' && (
<>
{[['all','Tous'],['unread','Non lus']].map(([v,l]) => (
<button key={v} className={`comm-topbar-filter-btn${notifFilter === v ? ' active' : ''}`} onClick={() => { setNotifFilter(v); setNotifPage(0); }}>{l}</button>
))}
{unreadCount > 0 && (
<button className="comm-topbar-action-btn" onClick={markAllRead}>Tout marquer lu</button>
<button
className="btn-icon-sm"
style={{ marginLeft: 'auto', flexShrink: 0 }}
onClick={e => {
const r = e.currentTarget.getBoundingClientRect();
setNotifMenuPos({ x: r.right, y: r.bottom + 4 });
}}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="5" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="12" cy="19" r="1"/></svg>
</button>
)}
</>
)}
</div>
</div>
{/* Colonne 3 — Toolbar contextuelle */}
<div className="comm-topbar-3">
@@ -822,20 +833,13 @@ export default function Communication() {
</div>
</>
)}
{tab === 'notifications' && selectedNotif && (
{tab === 'notifications' && selectedNotif && !selectedNotif.read && (
<div style={{ display: 'flex', gap: 6, marginLeft: 'auto' }}>
{!selectedNotif.read && (
<button className="btn btn-sm btn-ghost" onClick={async () => {
await api.patch(`/notifications/${selectedNotif.id}/read`);
setNotifs(prev => prev.map(n => n.id === selectedNotif.id ? { ...n, read: 1 } : n));
setSelectedNotif(prev => ({ ...prev, read: 1 }));
}}>Marquer lu</button>
)}
{selectedNotif.link && (
<button className="btn btn-sm btn-primary" onClick={() => navigate(selectedNotif.link)}>
Voir le détail
</button>
)}
</div>
)}
</div>
@@ -972,7 +976,14 @@ export default function Communication() {
return (
<>
<div className="comm-list-header">
<span style={{ fontWeight: 600, fontSize: 14 }}>Notifications</span>
<div className="comm-topbar-filters">
{[['all','Tous'],['unread','Non lus']].map(([v,l]) => (
<button key={v} className={`comm-topbar-filter-btn${notifFilter === v ? ' active' : ''}`} onClick={() => { setNotifFilter(v); setNotifPage(0); }}>{l}</button>
))}
{unreadCount > 0 && (
<button className="comm-topbar-action-btn" style={{ marginLeft: 'auto' }} onClick={markAllRead}>Tout marquer lu</button>
)}
</div>
</div>
<div className="comm-list-scroll">
{notifs.length === 0 && (
@@ -991,6 +1002,8 @@ export default function Communication() {
<div className="comm-list-row-body">
<div className="comm-list-row-top">
<span className="comm-list-row-name">{n.title}</span>
</div>
<div>
<span style={{
fontSize: 11, padding: '2px 7px', borderRadius: 99, fontWeight: 600,
background: (TYPE_META[n.type] ?? TYPE_META.info).bg,
@@ -1322,6 +1335,42 @@ export default function Communication() {
</div>{/* end comm-wrap */}
</div>{/* end comm-page */}
{/* ── Menu ⋮ notifications ── */}
{notifMenuPos && (
<>
<div style={{ position: 'fixed', inset: 0, zIndex: 299 }} onClick={() => setNotifMenuPos(null)} />
<div style={{
position: 'fixed', left: notifMenuPos.x, top: notifMenuPos.y,
transform: 'translateX(-100%) translateY(4px)',
zIndex: 300,
background: 'var(--surface)', border: '1px solid var(--border)',
borderRadius: 8, boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
padding: '4px 0', minWidth: 180,
}}>
{unreadCount > 0 && (
<button
style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--text)', textAlign: 'left' }}
onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
onMouseLeave={e => e.currentTarget.style.background = 'none'}
onClick={() => { markAllRead(); setNotifMenuPos(null); }}
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="20 6 9 17 4 12"/></svg>
Tout marquer lu
</button>
)}
<button
style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--danger)', textAlign: 'left' }}
onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
onMouseLeave={e => e.currentTarget.style.background = 'none'}
onClick={() => { deleteAllNotifs(); setNotifMenuPos(null); }}
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
Tout supprimer
</button>
</div>
</>
)}
{/* ── Lightbox image ── */}
{lightbox && (
<div className="comm-lightbox" onClick={() => setLightbox(null)}>