242 lines
6.2 KiB
JavaScript
242 lines
6.2 KiB
JavaScript
import React, { useState, useEffect, useContext, useCallback } from 'react'
|
|
import {
|
|
Typography,
|
|
Space,
|
|
Button,
|
|
Empty,
|
|
Spin,
|
|
message,
|
|
Popconfirm,
|
|
Flex,
|
|
Badge,
|
|
Dropdown
|
|
} from 'antd'
|
|
import {
|
|
BellOutlined,
|
|
DeleteOutlined,
|
|
CheckOutlined,
|
|
ReloadOutlined
|
|
} from '@ant-design/icons'
|
|
import axios from 'axios'
|
|
import PropTypes from 'prop-types'
|
|
import { AuthContext } from '../context/AuthContext'
|
|
import config from '../../../config'
|
|
import Notification from './Notification'
|
|
|
|
const { Text } = Typography
|
|
|
|
const NotificationCenter = ({ visible }) => {
|
|
const [notifications, setNotifications] = useState([])
|
|
const [loading, setLoading] = useState(false)
|
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
|
const [messageApi, contextHolder] = message.useMessage()
|
|
|
|
const { authenticated } = useContext(AuthContext)
|
|
|
|
const fetchNotifications = useCallback(async () => {
|
|
if (!authenticated) return
|
|
|
|
setLoading(true)
|
|
try {
|
|
const response = await axios.get(`${config.backendUrl}/notifications`, {
|
|
headers: {
|
|
Accept: 'application/json'
|
|
},
|
|
withCredentials: true
|
|
})
|
|
setNotifications(response.data)
|
|
} catch (error) {
|
|
console.error('Error fetching notifications:', error)
|
|
messageApi.error('Failed to fetch notifications')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [authenticated, messageApi])
|
|
|
|
const markAsRead = useCallback(
|
|
async (notificationId) => {
|
|
try {
|
|
await axios.put(
|
|
`${config.backendUrl}/notifications/${notificationId}/read`,
|
|
{},
|
|
{
|
|
headers: {
|
|
Accept: 'application/json'
|
|
},
|
|
withCredentials: true
|
|
}
|
|
)
|
|
|
|
// Update local state
|
|
setNotifications((prev) =>
|
|
prev.map((notification) => {
|
|
if (notification._id === notificationId) {
|
|
return { ...notification, read: true }
|
|
}
|
|
return notification
|
|
})
|
|
)
|
|
|
|
messageApi.success('Notification marked as read')
|
|
} catch (error) {
|
|
console.error('Error marking notification as read:', error)
|
|
messageApi.error('Failed to mark notification as read')
|
|
}
|
|
},
|
|
[messageApi]
|
|
)
|
|
|
|
const markAllAsRead = useCallback(async () => {
|
|
try {
|
|
await axios.put(
|
|
`${config.backendUrl}/notifications/read-all`,
|
|
{},
|
|
{
|
|
headers: {
|
|
Accept: 'application/json'
|
|
},
|
|
withCredentials: true
|
|
}
|
|
)
|
|
|
|
// Update local state
|
|
setNotifications((prev) =>
|
|
prev.map((notification) => ({ ...notification, read: true }))
|
|
)
|
|
|
|
messageApi.success('All notifications marked as read')
|
|
} catch (error) {
|
|
console.error('Error marking all notifications as read:', error)
|
|
messageApi.error('Failed to mark all notifications as read')
|
|
}
|
|
}, [messageApi])
|
|
|
|
const deleteAllNotifications = useCallback(async () => {
|
|
try {
|
|
await axios.delete(`${config.backendUrl}/notifications`, {
|
|
headers: {
|
|
Accept: 'application/json'
|
|
},
|
|
withCredentials: true
|
|
})
|
|
|
|
setNotifications([])
|
|
messageApi.success('All notifications deleted')
|
|
} catch (error) {
|
|
console.error('Error deleting all notifications:', error)
|
|
messageApi.error('Failed to delete all notifications')
|
|
} finally {
|
|
setShowDeleteConfirm(false)
|
|
}
|
|
}, [messageApi])
|
|
|
|
useEffect(() => {
|
|
if (visible && authenticated) {
|
|
fetchNotifications()
|
|
}
|
|
}, [visible, authenticated, fetchNotifications])
|
|
|
|
const unreadCount = notifications.filter(
|
|
(notification) => !notification.read
|
|
).length
|
|
|
|
const actionItems = {
|
|
items: [
|
|
{
|
|
label: 'Mark All Read',
|
|
key: 'markAllRead',
|
|
icon: <CheckOutlined />,
|
|
disabled: unreadCount === 0
|
|
},
|
|
{
|
|
label: 'Reload Notifications',
|
|
key: 'reloadNotifications',
|
|
icon: <ReloadOutlined />
|
|
},
|
|
{ type: 'divider' },
|
|
{
|
|
label: 'Delete All',
|
|
key: 'deleteAll',
|
|
icon: <DeleteOutlined />,
|
|
danger: true,
|
|
disabled: notifications.length === 0
|
|
}
|
|
],
|
|
onClick: ({ key }) => {
|
|
if (key === 'markAllRead') {
|
|
markAllAsRead()
|
|
} else if (key === 'reloadNotifications') {
|
|
fetchNotifications()
|
|
} else if (key === 'deleteAll') {
|
|
setShowDeleteConfirm(true)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!visible) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{contextHolder}
|
|
<Flex justify='space-between' align='center'>
|
|
<Space size='middle'>
|
|
<Dropdown menu={actionItems}>
|
|
<Button>Actions</Button>
|
|
</Dropdown>
|
|
<Badge count={unreadCount} size='small'>
|
|
<BellOutlined style={{ fontSize: '18px' }} />
|
|
</Badge>
|
|
</Space>
|
|
<Space>
|
|
<Button icon={<DeleteOutlined />} danger />
|
|
</Space>
|
|
</Flex>
|
|
|
|
<div style={{ maxHeight: 500, overflow: 'auto' }}>
|
|
{loading ? (
|
|
<div style={{ padding: '40px', textAlign: 'center' }}>
|
|
<Spin size='large' />
|
|
<div style={{ marginTop: 16 }}>
|
|
<Text type='secondary'>Loading notifications...</Text>
|
|
</div>
|
|
</div>
|
|
) : notifications.length === 0 ? (
|
|
<Empty
|
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
description='No notifications'
|
|
style={{ padding: '40px 20px' }}
|
|
/>
|
|
) : (
|
|
<Flex vertical gap='small' style={{ padding: '16px' }}>
|
|
{notifications.map((notification) => (
|
|
<Notification
|
|
key={notification._id}
|
|
notification={notification}
|
|
onMarkAsRead={markAsRead}
|
|
/>
|
|
))}
|
|
</Flex>
|
|
)}
|
|
</div>
|
|
|
|
<Popconfirm
|
|
title='Delete all notifications?'
|
|
description='This action cannot be undone.'
|
|
open={showDeleteConfirm}
|
|
onConfirm={deleteAllNotifications}
|
|
onCancel={() => setShowDeleteConfirm(false)}
|
|
okText='Yes'
|
|
cancelText='No'
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
NotificationCenter.propTypes = {
|
|
visible: PropTypes.bool.isRequired
|
|
}
|
|
|
|
export default NotificationCenter
|