AP6 增加审批催办与效率摘要
这个提交包含在:
@@ -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
|
||||
|
||||
在新工单中引用
屏蔽一个用户