import Avatar from '@/components/ui/Avatar' import Badge from '@/components/ui/Badge' import Button from '@/components/ui/Button' import Dropdown from '@/components/ui/Dropdown' import ScrollBar from '@/components/ui/ScrollBar' import Spinner from '@/components/ui/Spinner' import Tooltip from '@/components/ui/Tooltip' import { APP_NAME, AVATAR_URL } from '@/constants/app.constant' import NotificationChannels from '@/constants/notification-channel.enum' import { ROUTES_ENUM } from '@/routes/route.constant' import { getList, updateRead, updateReadAll, updateReadMany, updateSentMany, } from '@/services/notification.service' import { useStoreState } from '@/store' import withHeaderItem from '@/utils/hoc/withHeaderItem' import { useLocalization } from '@/utils/hooks/useLocalization' import useResponsive from '@/utils/hooks/useResponsive' import useThemeClass from '@/utils/hooks/useThemeClass' import isLastChild from '@/utils/isLastChild' import classNames from 'classnames' import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import { useCallback, useEffect, useRef, useState } from 'react' import { FaEnvelopeOpen } from 'react-icons/fa' import { FcAdvertising } from 'react-icons/fc' import { Link } from 'react-router-dom' import { Notification as Notify, toast } from '../ui' dayjs.extend(relativeTime) type NotificationList = { id: string creatorId: string tenantId?: string notificationType: string message: string date: string readed: boolean } const notificationHeight = 'h-72' const notificationInterval = 120000 // 2 minutes const NotificationToggle = ({ className, unreadCount, }: { className?: string unreadCount: number }) => { return (
{unreadCount > 0 ? ( ) : ( )}
) } const _Notification = ({ className }: { className?: string }) => { const { translate } = useLocalization() const [notificationList, setNotificationList] = useState([]) const [unreadNotificationCount, setUnreadNotificationCount] = useState(0) const [loading, setLoading] = useState(false) // Aynı bildirimin sekme ömrü boyunca ikinci kez toast/desktop olarak // gösterilmesini engeller. const pushedIds = useRef(new Set()) const listLoaded = useRef(false) const { bgTheme } = useThemeClass() const { larger } = useResponsive() const direction = useStoreState((state) => state.theme.direction) const tabHasFocus = useStoreState((a) => a.base.common.tabHasFocus) const tabHasFocusRef = useRef(tabHasFocus) useEffect(() => { tabHasFocusRef.current = tabHasFocus }, [tabHasFocus]) const getUnreadCount = useCallback(async () => { const resp = await getList({ channels: [NotificationChannels.UiActivity], isListRequest: false, isRead: false, maxResultCount: 1, }) setUnreadNotificationCount(resp.data?.totalCount ?? 0) }, []) const getPushNotifications = useCallback(async () => { const desktopGranted = 'Notification' in window && window.Notification.permission === 'granted' const channels = [NotificationChannels.UiToast] if (desktopGranted) channels.push(NotificationChannels.Desktop) const resp = await getList({ channels, isListRequest: false, isSent: false, maxResultCount: 1000, }) const pending = (resp.data.items ?? []).filter( (a) => !a.isSent && !pushedIds.current.has(a.id), ) if (pending.length === 0) { return } pending.forEach((a) => pushedIds.current.add(a.id)) for (const notification of pending) { if (notification.notificationChannel === NotificationChannels.UiToast) { toast.push( {notification.message} , { placement: 'bottom-end' }, ) continue } const title = notification.notificationType || APP_NAME const options: NotificationOptions = { body: notification.message, dir: 'ltr', requireInteraction: true, } if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { const reg = await navigator.serviceWorker.ready await reg.showNotification(title, options) } else { new window.Notification(title, options) } } const processedIds = pending.map((a) => a.id) await updateSentMany(processedIds, true) await updateReadMany(processedIds, true) }, []) useEffect(() => { if ('Notification' in window && window.Notification.permission === 'default') { window.Notification.requestPermission() } const poll = () => { getUnreadCount() getPushNotifications() } poll() const intervalId = setInterval(() => { if (tabHasFocusRef.current) { poll() } }, notificationInterval) return () => clearInterval(intervalId) }, [getUnreadCount, getPushNotifications]) const onNotificationOpen = useCallback(async () => { const currentUnread = notificationList.filter((a) => !a.readed).length if (listLoaded.current && currentUnread === unreadNotificationCount) { return } setLoading(true) try { const resp = await getList({ channels: [NotificationChannels.UiActivity], isListRequest: false, maxResultCount: 1000, }) const items = resp.data.items ?? [] if (items.length > 0) { await updateSentMany( items.map((notification) => notification.id), true, ) } setNotificationList( items.map((a) => ({ id: a.id, notificationType: a.notificationType, date: a.creationTime.toLocaleString(), message: a.message, creatorId: a.creatorId, tenantId: a.tenantId, readed: a.isRead, })) as NotificationList[], ) listLoaded.current = true } finally { setLoading(false) } }, [notificationList, unreadNotificationCount]) const onMarkAllAsRead = useCallback(async () => { await updateReadAll(NotificationChannels.UiActivity, true) setNotificationList((list) => list.map((item) => ({ ...item, readed: true }))) setUnreadNotificationCount(0) }, []) const onMarkAsRead = useCallback( async (id: string) => { const target = notificationList.find((item) => item.id === id) if (!target || target.readed) { return } await updateRead(id, true) setNotificationList((list) => list.map((item) => (item.id === id ? { ...item, readed: true } : item)), ) setUnreadNotificationCount((count) => Math.max(0, count - 1)) }, [notificationList], ) const noResult = !loading && listLoaded.current && notificationList.length === 0 return ( } menuClass="p-0 min-w-[280px] md:min-w-[340px]" placement={larger.md ? 'bottom-end' : 'bottom-center'} onOpen={onNotificationOpen} >
{translate('::Abp.Identity.ActivityLogs.Notifications')}
{notificationList.map((item, index) => (
onMarkAsRead(item.id)} >
{item.notificationType && ( {item.notificationType} )}
{item.message}
{dayjs(item.date).fromNow()}
))} {loading && (
)} {noResult && (
no-notification
No notifications!

Please Try again later

)}
{translate('::Abp.Identity.ActivityLogs.ViewAllActivity')}
) } const Notification = withHeaderItem(_Notification) export default Notification