112 lines
4.5 KiB
TypeScript
112 lines
4.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useRef, useEffect } from 'react'
|
|
import { Bell, X } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface Notification {
|
|
id: string
|
|
icon: string
|
|
title: string
|
|
desc: string
|
|
time: string
|
|
read: boolean
|
|
}
|
|
|
|
const INITIAL_NOTIFICATIONS: Notification[] = [
|
|
{ id: '1', icon: '✅', title: 'Session finalisée', desc: 'Rapports disponibles pour JS Fundamentals.', time: 'il y a 2h', read: false },
|
|
{ id: '2', icon: '👥', title: 'Nouvelles inscriptions', desc: '12 étudiants ont rejoint "React Basics Quiz".', time: 'il y a 4h', read: false },
|
|
{ id: '3', icon: '🔧', title: 'Maintenance prévue', desc: 'Samedi à 22h. Pensez à sauvegarder votre travail.', time: 'il y a 1j', read: true },
|
|
]
|
|
|
|
export default function NotificationBell() {
|
|
const [open, setOpen] = useState(false)
|
|
const [notifications, setNotifications] = useState<Notification[]>(INITIAL_NOTIFICATIONS)
|
|
const ref = useRef<HTMLDivElement>(null)
|
|
|
|
const unreadCount = notifications.filter((n) => !n.read).length
|
|
|
|
useEffect(() => {
|
|
const handleClick = (e: MouseEvent) => {
|
|
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
|
|
}
|
|
document.addEventListener('mousedown', handleClick)
|
|
return () => document.removeEventListener('mousedown', handleClick)
|
|
}, [])
|
|
|
|
const markRead = (id: string) =>
|
|
setNotifications((prev) => prev.map((n) => (n.id === id ? { ...n, read: true } : n)))
|
|
|
|
const markAllRead = () => setNotifications((prev) => prev.map((n) => ({ ...n, read: true })))
|
|
|
|
return (
|
|
<div ref={ref} className="relative">
|
|
<button
|
|
onClick={() => setOpen((v) => !v)}
|
|
className="relative p-2.5 bg-background-card border border-border rounded-lg hover:border-border-light transition-colors"
|
|
>
|
|
<Bell size={18} className="text-text-secondary" />
|
|
{unreadCount > 0 && (
|
|
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-primary rounded-full animate-pulse" />
|
|
)}
|
|
</button>
|
|
|
|
{open && (
|
|
<div className="absolute right-0 top-12 w-80 bg-background-card border border-border rounded-xl shadow-2xl shadow-black/50 z-50 overflow-hidden animate-in fade-in slide-in-from-top-2 duration-150">
|
|
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="font-semibold text-text-primary text-sm">Notifications</h3>
|
|
{unreadCount > 0 && (
|
|
<span className="bg-primary/20 text-primary text-xs px-2 py-0.5 rounded-full font-medium">
|
|
{unreadCount}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{unreadCount > 0 && (
|
|
<button
|
|
onClick={markAllRead}
|
|
className="text-xs text-primary hover:text-primary-light transition-colors"
|
|
>
|
|
Tout lire
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={() => setOpen(false)}
|
|
className="p-1 hover:bg-background-elevated rounded transition-colors"
|
|
>
|
|
<X size={14} className="text-text-muted" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="max-h-80 overflow-y-auto">
|
|
{notifications.map((notif) => (
|
|
<div
|
|
key={notif.id}
|
|
onClick={() => markRead(notif.id)}
|
|
className={cn(
|
|
'flex items-start gap-3 px-4 py-3 hover:bg-background-elevated/50 transition-colors cursor-pointer border-b border-border/50 last:border-0',
|
|
!notif.read && 'bg-primary/5'
|
|
)}
|
|
>
|
|
<div className="w-8 h-8 bg-background-elevated rounded-lg flex items-center justify-center text-sm flex-shrink-0">
|
|
{notif.icon}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center justify-between gap-2 mb-0.5">
|
|
<p className="text-sm font-medium text-text-primary truncate">{notif.title}</p>
|
|
{!notif.read && <div className="w-1.5 h-1.5 bg-primary rounded-full flex-shrink-0" />}
|
|
</div>
|
|
<p className="text-xs text-text-muted leading-relaxed">{notif.desc}</p>
|
|
<p className="text-xs text-text-muted/50 mt-1">{notif.time}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|