fix
This commit is contained in:
@@ -510,6 +510,7 @@ export default function Communication() {
|
|||||||
const { user, isAdmin } = useAuth();
|
const { user, isAdmin } = useAuth();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [notifMenuPos, setNotifMenuPos] = useState(null); // { x, y }
|
||||||
|
|
||||||
const [tab, setTab] = useState('support'); // 'support' | 'notifications'
|
const [tab, setTab] = useState('support'); // 'support' | 'notifications'
|
||||||
const [showBroadcastForm, setShowBroadcastForm] = useState(false);
|
const [showBroadcastForm, setShowBroadcastForm] = useState(false);
|
||||||
@@ -718,6 +719,16 @@ export default function Communication() {
|
|||||||
setBcSending(false);
|
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 ────────────────────────────────────────────
|
// ── Supprimer une notification ────────────────────────────────────────────
|
||||||
const deleteNotif = async (id) => {
|
const deleteNotif = async (id) => {
|
||||||
try {
|
try {
|
||||||
@@ -787,26 +798,26 @@ export default function Communication() {
|
|||||||
</div>
|
</div>
|
||||||
{/* Colonne 2 — Dossier + filtres */}
|
{/* Colonne 2 — Dossier + filtres */}
|
||||||
<div className="comm-topbar-2">
|
<div className="comm-topbar-2">
|
||||||
<span className="comm-topbar-folder">{tab === 'support' ? 'Support' : 'Notifications'}</span>
|
<span className="comm-topbar-folder" style={{ fontSize: 15, fontWeight: 700 }}>{tab === 'support' ? 'Support' : 'Notifications'}</span>
|
||||||
<div className="comm-topbar-filters">
|
{tab === 'support' && (
|
||||||
{tab === 'support' && (
|
<div className="comm-topbar-filters">
|
||||||
<>
|
{[['all','Tous'],['open','Ouverts'],['resolved','Résolus']].map(([v,l]) => (
|
||||||
{[['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>
|
||||||
<button key={v} className={`comm-topbar-filter-btn${ticketFilter === v ? ' active' : ''}`} onClick={() => setTicketFilter(v)}>{l}</button>
|
))}
|
||||||
))}
|
</div>
|
||||||
</>
|
)}
|
||||||
)}
|
{tab === 'notifications' && (
|
||||||
{tab === 'notifications' && (
|
<button
|
||||||
<>
|
className="btn-icon-sm"
|
||||||
{[['all','Tous'],['unread','Non lus']].map(([v,l]) => (
|
style={{ marginLeft: 'auto', flexShrink: 0 }}
|
||||||
<button key={v} className={`comm-topbar-filter-btn${notifFilter === v ? ' active' : ''}`} onClick={() => { setNotifFilter(v); setNotifPage(0); }}>{l}</button>
|
onClick={e => {
|
||||||
))}
|
const r = e.currentTarget.getBoundingClientRect();
|
||||||
{unreadCount > 0 && (
|
setNotifMenuPos({ x: r.right, y: r.bottom + 4 });
|
||||||
<button className="comm-topbar-action-btn" onClick={markAllRead}>Tout marquer lu</button>
|
}}
|
||||||
)}
|
>
|
||||||
</>
|
<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>
|
</div>
|
||||||
{/* Colonne 3 — Toolbar contextuelle */}
|
{/* Colonne 3 — Toolbar contextuelle */}
|
||||||
<div className="comm-topbar-3">
|
<div className="comm-topbar-3">
|
||||||
@@ -822,20 +833,13 @@ export default function Communication() {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{tab === 'notifications' && selectedNotif && (
|
{tab === 'notifications' && selectedNotif && !selectedNotif.read && (
|
||||||
<div style={{ display: 'flex', gap: 6, marginLeft: 'auto' }}>
|
<div style={{ display: 'flex', gap: 6, marginLeft: 'auto' }}>
|
||||||
{!selectedNotif.read && (
|
<button className="btn btn-sm btn-ghost" onClick={async () => {
|
||||||
<button className="btn btn-sm btn-ghost" onClick={async () => {
|
await api.patch(`/notifications/${selectedNotif.id}/read`);
|
||||||
await api.patch(`/notifications/${selectedNotif.id}/read`);
|
setNotifs(prev => prev.map(n => n.id === selectedNotif.id ? { ...n, read: 1 } : n));
|
||||||
setNotifs(prev => prev.map(n => n.id === selectedNotif.id ? { ...n, read: 1 } : n));
|
setSelectedNotif(prev => ({ ...prev, read: 1 }));
|
||||||
setSelectedNotif(prev => ({ ...prev, read: 1 }));
|
}}>Marquer lu</button>
|
||||||
}}>Marquer lu</button>
|
|
||||||
)}
|
|
||||||
{selectedNotif.link && (
|
|
||||||
<button className="btn btn-sm btn-primary" onClick={() => navigate(selectedNotif.link)}>
|
|
||||||
Voir le détail →
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -972,7 +976,14 @@ export default function Communication() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="comm-list-header">
|
<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>
|
||||||
<div className="comm-list-scroll">
|
<div className="comm-list-scroll">
|
||||||
{notifs.length === 0 && (
|
{notifs.length === 0 && (
|
||||||
@@ -991,6 +1002,8 @@ export default function Communication() {
|
|||||||
<div className="comm-list-row-body">
|
<div className="comm-list-row-body">
|
||||||
<div className="comm-list-row-top">
|
<div className="comm-list-row-top">
|
||||||
<span className="comm-list-row-name">{n.title}</span>
|
<span className="comm-list-row-name">{n.title}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: 11, padding: '2px 7px', borderRadius: 99, fontWeight: 600,
|
fontSize: 11, padding: '2px 7px', borderRadius: 99, fontWeight: 600,
|
||||||
background: (TYPE_META[n.type] ?? TYPE_META.info).bg,
|
background: (TYPE_META[n.type] ?? TYPE_META.info).bg,
|
||||||
@@ -1322,6 +1335,42 @@ export default function Communication() {
|
|||||||
</div>{/* end comm-wrap */}
|
</div>{/* end comm-wrap */}
|
||||||
</div>{/* end comm-page */}
|
</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 image ── */}
|
||||||
{lightbox && (
|
{lightbox && (
|
||||||
<div className="comm-lightbox" onClick={() => setLightbox(null)}>
|
<div className="comm-lightbox" onClick={() => setLightbox(null)}>
|
||||||
|
|||||||
Reference in New Issue
Block a user