File size: 4,638 Bytes
24727a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { Bell, Loader, CheckCheck } from 'lucide-react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { api } from '../../lib/axios'
import { useState, useRef, useEffect } from 'react'

export function NotificationBell({ basePath = '/admin' }) {
  const [open, setOpen] = useState(false)
  const ref = useRef(null)
  const queryClient = useQueryClient()
  const prefix = basePath === '/admin' ? '/admin' : ''

  const { data: countData } = useQuery({
    queryKey: ['notifications-count', prefix],
    queryFn: () => api.get(`${prefix}/notifications/count`).then(r => r.data),
    refetchInterval: 30000,
  })

  const { data: notifData } = useQuery({
    queryKey: ['notifications', prefix],
    queryFn: () => api.get(`${prefix}/notifications`, { params: { limit: 10 } }).then(r => r.data),
    enabled: open,
  })

  const markRead = useMutation({
    mutationFn: (id) => api.patch(`${prefix}/notifications/${id}/read`),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['notifications', prefix] })
      queryClient.invalidateQueries({ queryKey: ['notifications-count', prefix] })
    },
  })

  const markAllRead = useMutation({
    mutationFn: () => api.patch(`${prefix}/notifications/read-all`),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['notifications', prefix] })
      queryClient.invalidateQueries({ queryKey: ['notifications-count', prefix] })
    },
  })

  useEffect(() => {
    const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false) }
    document.addEventListener('mousedown', handler)
    return () => document.removeEventListener('mousedown', handler)
  }, [])

  const unread = countData?.count || 0

  return (
    <div className="relative" ref={ref}>
      <button onClick={() => setOpen(!open)} className="relative p-2 rounded-xl hover:bg-gray-100 transition-colors">
        <Bell className="w-5 h-5 text-gray-600" />
        {unread > 0 && (
          <span className="absolute -top-0.5 -right-0.5 bg-red-500 text-white text-[10px] font-bold rounded-full min-w-[18px] h-[18px] flex items-center justify-center px-1">
            {unread > 99 ? '99+' : unread}
          </span>
        )}
      </button>

      {open && (
        <div className="absolute left-0 top-full mt-2 w-80 bg-white rounded-2xl shadow-xl border border-gray-100 z-50 max-h-[450px] flex flex-col">
          <div className="flex items-center justify-between px-4 py-3 border-b border-gray-100">
            <h3 className="font-semibold text-sm text-gray-800">الإشعارات</h3>
            {unread > 0 && (
              <button onClick={() => markAllRead.mutate()} className="text-xs text-brand-600 hover:text-brand-700 font-medium">
                تحديد الكل كمقروء
              </button>
            )}
          </div>
          <div className="overflow-y-auto flex-1">
            {!notifData ? (
              <div className="flex items-center justify-center py-8"><Loader className="w-5 h-5 animate-spin text-gray-400" /></div>
            ) : notifData.notifications?.length === 0 ? (
              <div className="text-center py-8 text-gray-400 text-sm">لا توجد إشعارات</div>
            ) : (
              notifData.notifications.map((n) => (
                <div
                  key={n.id}
                  className={`px-4 py-3 border-b border-gray-50 cursor-pointer hover:bg-gray-50 transition-colors ${!n.isRead ? 'bg-brand-50/40' : ''}`}
                  onClick={() => {
                    if (!n.isRead) markRead.mutate(n.id)
                    setOpen(false)
                  }}
                >
                  <div className="flex items-start gap-2">
                    <div className={`mt-0.5 w-2 h-2 rounded-full flex-shrink-0 ${!n.isRead ? 'bg-brand-500' : 'bg-transparent'}`} />
                    <div className="min-w-0 flex-1">
                      <p className="text-sm font-medium text-gray-800 truncate">{n.title}</p>
                      <p className="text-xs text-gray-500 mt-0.5 line-clamp-2">{n.body}</p>
                      <p className="text-[10px] text-gray-400 mt-1">{new Date(n.createdAt).toLocaleDateString('ar-EG')}</p>
                    </div>
                  </div>
                </div>
              ))
            )}
          </div>
          {unread > 0 && (
            <div className="px-4 py-2 border-t border-gray-100 text-center">
              <span className="text-xs text-gray-400">{unread} إشعار غير مقروء</span>
            </div>
          )}
        </div>
      )}
    </div>
  )
}