From 689376787e4f55b7cf0a402364eb17acb6bc43e5 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Tue, 24 Feb 2026 09:55:28 +0330 Subject: [PATCH] notification admin --- .env | 4 +- src/index.css | 18 +++++- src/pages/notification/Notification.tsx | 64 +++++++++++++------ .../notification/hooks/useNotificationData.ts | 21 +++--- .../service/NotificationService.ts | 29 +++++++-- .../notification/types/NotificationTypes.ts | 24 +++++++ src/shared/Sidebar.tsx | 7 -- 7 files changed, 123 insertions(+), 44 deletions(-) diff --git a/.env b/.env index 35e868d..d884c74 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -VITE_API_BASE_URL = 'https://negareh-api.dev.danakcorp.com' -# VITE_API_BASE_URL = 'http://10.56.147.88:4000' +# VITE_API_BASE_URL = 'https://negareh-api.dev.danakcorp.com' +VITE_API_BASE_URL = 'http://10.29.35.88:4000' VITE_TOKEN_NAME = 'negareh_at' VITE_REFRESH_TOKEN_NAME = 'negareh_art' diff --git a/src/index.css b/src/index.css index bbaa943..aedb0c0 100644 --- a/src/index.css +++ b/src/index.css @@ -61,7 +61,9 @@ tbody tr:nth-child(odd) { width: 100% !important; margin: 0px; padding-right: 16px !important; - transition: background-color 0.3s ease, border-color 0.3s ease; + transition: + background-color 0.3s ease, + border-color 0.3s ease; } .readOny .rmdp-input { background-color: #f5f5f5 !important; @@ -79,3 +81,17 @@ tbody tr:nth-child(odd) { .dltr { direction: ltr; } + +.modalGlass2 { + background: rgba(255, 255, 255, 0.502); + background-blend-mode: multiply; + + /* backdrop-filter: blur(5px); */ +} + +.modalGlass3 { + background: rgba(62, 61, 61, 0.2); + background-blend-mode: multiply; + + backdrop-filter: blur(44px); +} diff --git a/src/pages/notification/Notification.tsx b/src/pages/notification/Notification.tsx index 45f52e7..5af2214 100644 --- a/src/pages/notification/Notification.tsx +++ b/src/pages/notification/Notification.tsx @@ -4,20 +4,34 @@ import XIcon from '../../assets/images/close-circle.svg'; import { clx } from '../../helpers/utils'; import StatusCircle from '@/components/StatusCircle'; import { useOutsideClick } from '@/hooks/useOutSideClick'; -import { type NotificationItemType, NotificationTypeEnum } from './types/NotificationTypes'; +import { type NotificationListItemType, NotificationTypeEnum } from './types/NotificationTypes'; import Button from '@/components/Button'; import { t } from '@/locale'; +import { useQueryClient } from '@tanstack/react-query'; +import { useGetNotification, useGetUnseenCount, useReadAll } from './hooks/useNotificationData'; +import { timeAgo } from '@/config/func'; +import InfiniteScroll from 'react-infinite-scroll-component'; +import MoonLoader from 'react-spinners/MoonLoader'; const Notifications: FC = () => { + const queryClient = useQueryClient(); const ref = useOutsideClick(() => setShowModal(false)); const [activeTab, setActiveTab] = useState<'read' | 'unread'>('unread'); const [showModal, setShowModal] = useState(false); - const posts: NotificationItemType[] = []; - // const getDashboard = useGetNotification('all') + const { data } = useGetUnseenCount(); + const { mutate: readAll, isPending } = useReadAll(); + const { data: notifications, fetchNextPage, hasNextPage } = useGetNotification(activeTab); + const notificationList: NotificationListItemType[] = + notifications?.pages.flatMap((page) => page.data) ?? []; const handleAllRead = () => { - // TODO: Implement read all functionality - } + readAll(undefined, { + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['notifications'] }); + queryClient.invalidateQueries({ queryKey: ['unseen-count'] }); + }, + }); + }; const handleRedirect = (type: NotificationTypeEnum) => { switch (type) { @@ -66,9 +80,8 @@ const Notifications: FC = () => {
setShowModal(!showModal)} className='relative cursor-pointer'> -
- {/* {getDashboard.data?.pages?.[0]?.data?.notificationCount || 0} */} - 2 +
+ {data?.data?.count}
@@ -106,7 +119,7 @@ const Notifications: FC = () => {
- { - posts.map((item: NotificationItemType) => ( -
handleRedirect(item.type)} className="bg-white cursor-pointer h-fit gap-4 items-start rounded-xl bg-opacity-60 p-3 mb-4 flex" key={item.id}> - { - !item.isRead && + + +
+ } + scrollableTarget="notificationsContainer" + > + {notificationList.map((item) => ( +
handleRedirect(item.title as NotificationTypeEnum)} + className="bg-white cursor-pointer h-fit gap-4 items-start rounded-xl bg-opacity-60 p-3 mb-4 flex" + key={item.id} + > + {item.seenAt === null && (
- } + )}
-
{item.message}
+
{item.content}
-
{/* timeAgo(item.createdAt) */}
+
{timeAgo(item.createdAt)}
{item.title}
@@ -137,8 +163,8 @@ const Notifications: FC = () => {
- )) - } + ))} +
)} diff --git a/src/pages/notification/hooks/useNotificationData.ts b/src/pages/notification/hooks/useNotificationData.ts index 56a0d63..2ef23fd 100644 --- a/src/pages/notification/hooks/useNotificationData.ts +++ b/src/pages/notification/hooks/useNotificationData.ts @@ -1,17 +1,13 @@ import * as api from "../service/NotificationService"; -import { useInfiniteQuery, useMutation } from "@tanstack/react-query"; +import { useInfiniteQuery, useMutation, useQuery } from "@tanstack/react-query"; export const useGetNotification = (status: "all" | "read" | "unread") => { return useInfiniteQuery({ queryKey: ["notifications", status], - queryFn: ({ pageParam = 1 }) => api.getNotifications(pageParam, status), // فراخوانی API برای گرفتن داده‌ها - initialPageParam: 1, // صفحه اول به طور پیشفرض - getNextPageParam: (lastPage) => { - const currentPage = lastPage.data?.pager.page; - const totalPages = lastPage.data?.pager.totalPages; - - return currentPage < totalPages ? currentPage + 1 : undefined; - }, + queryFn: ({ pageParam }) => + api.getNotifications(pageParam ?? 1, status), + initialPageParam: 1 as number | string, + getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined, }); }; @@ -20,3 +16,10 @@ export const useReadAll = () => { mutationFn: () => api.readAll(), }); }; + +export const useGetUnseenCount = () => { + return useQuery({ + queryKey: ["unseen-count"], + queryFn: () => api.getUnseenCount(), + }); +}; diff --git a/src/pages/notification/service/NotificationService.ts b/src/pages/notification/service/NotificationService.ts index 8427851..4706873 100644 --- a/src/pages/notification/service/NotificationService.ts +++ b/src/pages/notification/service/NotificationService.ts @@ -1,18 +1,35 @@ import axios from "../../../config/axios"; +import type { + NotificationsResponseType, + UnseenCountResponseType, +} from "../types/NotificationTypes"; export const getNotifications = async ( - page: number, - status: "all" | "read" | "unread" -) => { + pageOrCursor: number | string, + status: "all" | "read" | "unread", +): Promise => { let query = ``; if (status !== "all") { - query = `&isRead=${status === "read" ? 1 : 0}`; + query = `&status=${status === "read" ? "seen" : "unseen"}`; } - const { data } = await axios.get(`/notifications?page=${page}${query}`); + const pageParam = + typeof pageOrCursor === "number" + ? `page=${pageOrCursor}` + : `cursor=${encodeURIComponent(pageOrCursor)}`; + const { data } = await axios.get( + `/admin/notifications?${pageParam}${query}&limit=10`, + ); return data; }; export const readAll = async () => { - const { data } = await axios.patch(`/notifications/read-all`); + const { data } = await axios.put(`/admin/notifications/read/all`); + return data; +}; + +export const getUnseenCount = async (): Promise => { + const { data } = await axios.get( + `/admin/notifications/unseen-count`, + ); return data; }; diff --git a/src/pages/notification/types/NotificationTypes.ts b/src/pages/notification/types/NotificationTypes.ts index 6db0b64..4da1c67 100644 --- a/src/pages/notification/types/NotificationTypes.ts +++ b/src/pages/notification/types/NotificationTypes.ts @@ -1,3 +1,27 @@ +import type { BaseResponse } from "@/shared/types/Types"; + +export type UnseenCountType = { + count: number; +}; + +export type UnseenCountResponseType = BaseResponse; + +/** آیتم نوتیفیکیشن در پاسخ لیست API */ +export type NotificationListItemType = { + id: string; + createdAt: string; + deletedAt: string | null; + user: string; + admin: string | null; + title: string; + content: string; + seenAt: string | null; +}; + +export type NotificationsResponseType = BaseResponse & { + nextCursor: string | null; +}; + export type NotificationItemType = { id: string; title: string; diff --git a/src/shared/Sidebar.tsx b/src/shared/Sidebar.tsx index a47bcdd..401170f 100644 --- a/src/shared/Sidebar.tsx +++ b/src/shared/Sidebar.tsx @@ -11,7 +11,6 @@ import { NotificationStatus, People, Printer, - Profile2User, Receipt21, Teacher, User, @@ -118,12 +117,6 @@ const SideBar: FC = () => {
{t('sidebar.other')}
- } - title={'کاربران'} - isActive={isActive('/tickets')} - link={Paths.tickets.list} - /> }