AP6 增加审批催办与效率摘要

这个提交包含在:
2026-05-12 10:41:47 +08:00
父节点 f99fbce3d8
当前提交 72e38db4e5
共修改 23 个文件,包含 1362 行新增和 12 行删除
+42 -1
查看文件
@@ -3,7 +3,12 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { isApiError } from "../api/types";
import { mapApiErrorMessage } from "../api/errorMessages";
import { approveWorkflowTask, fetchWorkflowTasks, type WorkflowTaskSummary } from "../api/workflow";
import {
approveWorkflowTask,
fetchWorkflowTasks,
fetchWorkflowTaskSummary,
type WorkflowTaskSummary,
} from "../api/workflow";
import { PageHeader } from "../components/common/PageHeader";
import { Pagination } from "../components/common/Pagination";
import { StatusBadge } from "../components/common/StatusBadge";
@@ -103,6 +108,13 @@ function displayMetric(task: WorkflowTaskSummary) {
return task.businessSummary?.amount ?? task.businessSummary?.duration ?? "-";
}
function formatSummaryTime(value: string | null | undefined) {
if (!value) {
return "-";
}
return value.slice(0, 19).replace("T", " ");
}
function isAlreadyProcessedTaskError(error: unknown) {
return isApiError(error) && error.details?.task === "already_processed";
}
@@ -143,6 +155,11 @@ export function WorkflowTasksPage() {
}),
});
const summaryQuery = useQuery({
queryKey: ["workflow", "tasks", "summary", "mine"],
queryFn: () => fetchWorkflowTaskSummary(),
});
const approveMutation = useMutation({
mutationFn: (taskId: string) => approveWorkflowTask(taskId),
onSuccess: async () => {
@@ -173,6 +190,30 @@ export function WorkflowTasksPage() {
<div className="space-y-4 md:space-y-6">
<PageHeader title="审批中心" subtitle="集中处理待办、查看已办和业务审批历史。" />
<div className="grid gap-3 md:grid-cols-4" data-testid="workflow-task-summary">
<div className="rounded-lg border border-zinc-200 bg-white px-4 py-3">
<div className="text-xs text-zinc-500">待办总数</div>
<div className="mt-2 text-2xl font-semibold text-zinc-900">{summaryQuery.data?.pendingTotal ?? "-"}</div>
</div>
<div className="rounded-lg border border-zinc-200 bg-white px-4 py-3">
<div className="text-xs text-zinc-500">超时待办</div>
<div className="mt-2 text-2xl font-semibold text-amber-700">{summaryQuery.data?.overdueTotal ?? "-"}</div>
</div>
<div className="rounded-lg border border-zinc-200 bg-white px-4 py-3">
<div className="text-xs text-zinc-500">最早待处理</div>
<div className="mt-2 text-sm font-medium text-zinc-900">{formatSummaryTime(summaryQuery.data?.earliestPendingAt)}</div>
</div>
<div className="rounded-lg border border-zinc-200 bg-white px-4 py-3">
<div className="text-xs text-zinc-500">业务分布</div>
<div className="mt-2 truncate text-sm font-medium text-zinc-900">
{(summaryQuery.data?.byEntityType ?? [])
.filter((item) => item.pendingTotal > 0)
.map((item) => `${entityLabel(item.key)} ${item.pendingTotal}`)
.join(" / ") || "-"}
</div>
</div>
</div>
<div className="card-std space-y-4 p-4 md:p-5">
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between">
<Tabs