import { Link, useSearchParams } from "react-router-dom"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { fetchNotifications, markAllNotificationsRead, markNotificationRead } from "../api/notifications"; import { isApiError } from "../api/types"; import { mapApiErrorMessage } from "../api/errorMessages"; import { PageHeader } from "../components/common/PageHeader"; import { TableSkeleton } from "../components/common/TableSkeleton"; import { Pagination } from "../components/common/Pagination"; import { Button } from "../components/ui/button"; import { resolveNotificationTargetLink } from "../lib/notificationLinks"; import { resolveErrorMessage } from "../lib/resolveErrorMessage"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../components/ui/table"; import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs"; const PAGE_SIZE = 20; const NOTIFICATION_TYPE_OPTIONS = [ { label: "全部", value: "" }, { label: "审批待办", value: "workflow.task_assigned" }, { label: "报销", value: "reimbursement.approved,reimbursement.rejected,reimbursement.returned,reimbursement.paid" }, { label: "请假", value: "leave.approved,leave.rejected" }, { label: "采购", value: "purchase_request.approved,purchase_request.rejected" }, { label: "差旅", value: "travel_request.approved,travel_request.rejected" }, { label: "资产", value: "asset.assigned,asset.returned,asset.transferred" }, { label: "盘点", value: "inventory_check.completed" }, ]; export function NotificationsPage() { const queryClient = useQueryClient(); const [searchParams, setSearchParams] = useSearchParams(); const unreadOnlyRaw = searchParams.get("unreadOnly") ?? "0"; const unreadOnly = unreadOnlyRaw === "1" || unreadOnlyRaw === "true"; const page = Number(searchParams.get("page") ?? "1") || 1; const selectedType = searchParams.get("type") ?? ""; const updateFilters = (next: { unreadOnly?: boolean; page?: number; type?: string }) => { const params = new URLSearchParams(); params.set("unreadOnly", (next.unreadOnly ?? unreadOnly) ? "1" : "0"); params.set("page", String(next.page ?? page)); const nextType = next.type ?? selectedType; if (nextType) { params.set("type", nextType); } setSearchParams(params); }; const listQuery = useQuery({ queryKey: ["notifications", "list", unreadOnly, selectedType, page], queryFn: () => fetchNotifications({ unreadOnly, page, pageSize: PAGE_SIZE, ...(selectedType ? { type: selectedType } : {}), }), }); const markReadMutation = useMutation({ mutationFn: async (notificationId: string) => markNotificationRead(notificationId), onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["notifications"] }); }, }); const markAllReadMutation = useMutation({ mutationFn: () => markAllNotificationsRead(), onSuccess: async () => { toast.success("已全部标为已读"); await queryClient.invalidateQueries({ queryKey: ["notifications"] }); }, onError: (error) => { toast.error(resolveErrorMessage(error)); }, }); const totalPages = Math.max(1, Math.ceil((listQuery.data?.total ?? 0) / PAGE_SIZE)); const notificationItems = listQuery.data?.items ?? []; return (
} />
updateFilters({ unreadOnly: value === "unread", page: 1, }) } > 全部 未读
未读 {listQuery.data?.unreadCount ?? 0} 条
{listQuery.isLoading ? : null} {listQuery.isError ? (

{isApiError(listQuery.error) ? `${listQuery.error.code}: ${mapApiErrorMessage( listQuery.error.code, listQuery.error.details, listQuery.error.message, )}` : "加载通知失败"}

) : null} {!listQuery.isLoading && !listQuery.isError ? ( <>
{notificationItems.length ? ( notificationItems.map((item) => { const targetLink = resolveNotificationTargetLink(item); return (
{item.title}
{item.body ?
{item.body}
: null}
{item.readAt ? "已读" : "未读"}
{item.createdAt.slice(0, 19).replace("T", " ")}
{targetLink ? ( ) : null} {!item.readAt ? ( ) : null}
); }) ) : (
暂无通知
)}
标题时间状态操作 {notificationItems.length ? ( notificationItems.map((item) => { const targetLink = resolveNotificationTargetLink(item); return (
{item.title}
{item.body ? (
{item.body}
) : null}
{item.createdAt.slice(0, 19).replace("T", " ")} {item.readAt ? "已读" : "未读"}
{targetLink ? ( ) : null} {!item.readAt ? ( ) : null}
); }) ) : ( 暂无通知 )}
) : null} updateFilters({ page: p, }) } testIdPrefix="notifications" />
); }