import { useLocation, useNavigate, useSearchParams } from "react-router-dom"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { fetchNotifications, markAllNotificationsRead, markNotificationRead, type NotificationItem } from "../api/notifications"; import { isApiError } from "../api/types"; import { mapApiErrorMessage } from "../api/errorMessages"; import { PageHeader } from "../components/common/PageHeader"; import { Pagination } from "../components/common/Pagination"; import { PageScaffold } from "../components/patterns/PageScaffold"; import { Button } from "../components/ui/button"; import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs"; import { resolveMobileNotificationTargetLink } from "../lib/notificationLinks"; import { resolveErrorMessage } from "../lib/resolveErrorMessage"; const PAGE_SIZE = 20; const NOTIFICATION_TYPE_OPTIONS = [ { label: "全部", value: "" }, { label: "审批事件", value: "workflow.task_assigned,workflow.task_reminded,workflow.task_overdue" }, { 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" }, ]; function resolveNotificationError(error: unknown) { if (isApiError(error)) { return `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`; } return "加载通知失败"; } function formatTime(value: string) { return value.slice(0, 19).replace("T", " "); } function notificationTypeLabel(type: string) { if (type.startsWith("workflow.")) return "审批事件"; if (type.startsWith("reimbursement.")) return "报销事件"; if (type.startsWith("leave.")) return "请假事件"; if (type.startsWith("purchase_request.")) return "采购事件"; if (type.startsWith("travel_request.")) return "差旅事件"; if (type.startsWith("asset.")) return "资产事件"; if (type.startsWith("inventory_check.")) return "盘点事件"; return "系统事件"; } export function MobileNotificationsPage() { const queryClient = useQueryClient(); const navigate = useNavigate(); const location = useLocation(); const [searchParams, setSearchParams] = useSearchParams(); const currentMobileSource = `${location.pathname}${location.search}`; 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 openNotificationTarget = async (item: NotificationItem, targetLink: string | null) => { if (!targetLink) { toast.info("这条通知没有可直达的业务详情"); return; } try { if (!item.readAt) { await markReadMutation.mutateAsync(item.id); } navigate(targetLink, { state: { from: currentMobileSource } }); } catch (error) { toast.error(resolveErrorMessage(error)); } }; const markAllReadMutation = useMutation({ mutationFn: () => markAllNotificationsRead(), onSuccess: async () => { toast.success("已全部标为已读"); await queryClient.invalidateQueries({ queryKey: ["notifications"] }); }, onError: (error) => { toast.error(resolveErrorMessage(error)); }, }); const notificationItems = listQuery.data?.items ?? []; const totalPages = Math.max(1, Math.ceil((listQuery.data?.total ?? 0) / PAGE_SIZE)); return (
未读 {listQuery.data?.unreadCount ?? 0}} actions={ } />
updateFilters({ unreadOnly: value === "unread", page: 1, }) } > 全部 未读
{listQuery.isLoading ? (
加载中...
) : null} {listQuery.isError ? (

{resolveNotificationError(listQuery.error)}

) : null} {!listQuery.isLoading && !listQuery.isError && !notificationItems.length ? (
暂无通知
) : null}
{notificationItems.map((item) => { const targetLink = resolveMobileNotificationTargetLink(item); return ( ); })}
updateFilters({ page: nextPage, }) } testIdPrefix="mobile-notifications" />
); } export default MobileNotificationsPage;