import React, { useEffect, useState, useCallback } from "react"; import { Link } from "react-router-dom"; import { hsapApi } from "@/app/hsap-api"; import { Button } from "@/components/ui/Button"; import { Badge, StatusBadge } from "@/components/ui/Badge"; import { PageQueryState } from "@/components/PageQueryState"; import { ListPaginationBar } from "@/components/ListPaginationBar"; import { useAuth } from "@/app/AuthContext"; type RejectionCategory = { key: string; label: string }; export const AuditQueuePage: React.FC = () => { const { hasPermission: can } = useAuth(); const isReviewer = can("write:approval_review") || can("*"); const [approvals, setApprovals] = useState[]>([]); const [total, setTotal] = useState(0); const [offset, setOffset] = useState(0); const [limit, setLimit] = useState(20); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [filterStatus, setFilterStatus] = useState(""); const [selected, setSelected] = useState>(new Set()); const [batchMode, setBatchMode] = useState<"" | "approve" | "reject">(""); const [rejectCategory, setRejectCategory] = useState(""); const [rejectComment, setRejectComment] = useState(""); const [batchBusy, setBatchBusy] = useState(false); const [categories, setCategories] = useState([]); const [search, setSearch] = useState(""); const load = useCallback(async (newOffset = 0, newLimit = 20) => { setLoading(true); setError(null); try { const res = await hsapApi.listApprovals({ status: filterStatus || undefined, offset: newOffset, limit: newLimit }); let items = (res.items || []) as Record[]; if (search) { const q = search.toLowerCase(); items = items.filter((a) => String(a.action_label || a.action || "").toLowerCase().includes(q) || String(a.submitted_by || "").toLowerCase().includes(q) ); } setApprovals(items); setTotal(search ? items.length : res.total); setOffset(newOffset); setLimit(newLimit); } catch (e) { setError(String(e)); } setLoading(false); }, [filterStatus]); useEffect(() => { load(); }, [load]); useEffect(() => { if (isReviewer) { hsapApi.approvalRejectionCategories().then((r) => setCategories(r.categories || [])); } }, [isReviewer]); const toggleSelect = (id: string) => { setSelected((prev) => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; }); }; const toggleAll = () => { if (selected.size === approvals.length) { setSelected(new Set()); } else { setSelected(new Set(approvals.filter((a) => a.status === "pending").map((a) => a.id as string))); } }; const handleBatchAction = async () => { if (selected.size === 0) return; setBatchBusy(true); const ids = [...selected]; try { if (batchMode === "approve") { await hsapApi.batchApprove(ids); } else if (batchMode === "reject") { await hsapApi.batchReject(ids, rejectComment || undefined, rejectCategory || undefined); } setSelected(new Set()); setBatchMode(""); load(offset, limit); } catch (e) { setError(String(e)); } setBatchBusy(false); }; const handleSingleApprove = async (id: string) => { try { await hsapApi.approve(id); load(offset, limit); } catch (e) { setError(String(e)); } }; return (

审核队列

审核平台中所有操作请求

{/* Filter + Batch bar */}
{ setSearch(e.target.value); }} /> {isReviewer && selected.size > 0 && ( <> {selected.size} 项已选 {!batchMode ? ( <> ) : (
{batchMode === "approve" ? "批量通过" : "批量驳回"} {batchMode === "reject" && ( <> setRejectComment(e.target.value)} placeholder="备注(可选)" /> )}
)} )}
{isReviewer && } {isReviewer && } {approvals.map((a) => { const params = a.params as Record | undefined; const detail = params ? Object.entries(params).filter(([k]) => !["project", "submitted_by"].includes(k)).map(([k, v]) => `${k}=${v}`).join(", ") : ""; return ( {isReviewer && ( )} {isReviewer && ( )} ); })}
0 && selected.size === approvals.filter((a) => a.status === "pending").length} />操作 状态 详情 提单人 审核人 驳回原因 时间操作
{a.status === "pending" && ( toggleSelect(a.id as string)} /> )} {a.action_label as string || a.action as string || "—"} {detail || "—"} {a.submitted_by as string || "—"} {a.reviewed_by as string || "—"} {a.status === "rejected" && a.rejection_category ? ( {a.rejection_category as string} ) : a.status === "rejected" ? ( ) : null} {a.submitted_at as string || a.created_at as string || "—"} {a.status === "pending" && (
)}
load(o, limit)} onLimitChange={(l) => load(0, l)} />
); };