import { useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import {
fetchWecomApprovalArchive,
fetchWecomApprovalArchiveDetail,
type WecomApprovalArchiveFinding,
type WecomApprovalArchiveItem,
type WecomApprovalArchiveScope,
type WecomApprovalArchiveStatus,
type WecomApprovalArchiveType,
} from "../api/wecomApprovalArchive";
import { useAuth } from "../auth/AuthContext";
import { PageHeader } from "../components/common/PageHeader";
import { StatusBadge } from "../components/common/StatusBadge";
import { TableSkeleton } from "../components/common/TableSkeleton";
import { SectionPanel } from "../components/patterns/SectionPanel";
import { Button } from "../components/ui/button";
import { Label } from "../components/ui/label";
import { mapApiErrorMessage } from "../api/errorMessages";
import { isApiError } from "../api/types";
const TYPE_OPTIONS: Array<{ value: "" | WecomApprovalArchiveType; label: string }> = [
{ value: "", label: "全部类型" },
{ value: "leave", label: "请假" },
{ value: "missed_punch", label: "补卡" },
];
const STATUS_OPTIONS: Array<{ value: "" | WecomApprovalArchiveStatus; label: string }> = [
{ value: "", label: "全部状态" },
{ value: "approved", label: "已通过" },
{ value: "rejected", label: "已驳回" },
{ value: "revoked", label: "已撤销" },
{ value: "terminated", label: "已终止" },
{ value: "pending", label: "处理中" },
{ value: "unknown", label: "未知" },
];
const FINDING_OPTIONS: Array<{ value: "" | WecomApprovalArchiveFinding; label: string }> = [
{ value: "", label: "全部对账" },
{ value: "open", label: "仅未关闭 finding" },
{ value: "resolved", label: "已关闭 finding" },
{ value: "any", label: "有 finding" },
];
function resolveError(error: unknown, fallback = "读取企微审批历史失败") {
if (isApiError(error)) {
return `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`;
}
return error instanceof Error ? error.message : fallback;
}
function typeLabel(value: string) {
if (value === "leave") return "请假";
if (value === "missed_punch") return "补卡";
return value || "未知";
}
function sourceLabel(value: string | null) {
if (value === "callback") return "回调";
if (value === "compensation_pull") return "补偿拉取";
return value || "-";
}
function actionLabel(value: string) {
if (value === "approve") return "同意";
if (value === "reject") return "驳回";
if (value === "revoke") return "撤销";
if (value === "terminate") return "终止";
if (value === "pending") return "状态记录";
return value || "状态记录";
}
function roleLabel(value: string) {
if (value === "applicant") return "申请人";
if (value === "approver") return "审批人";
if (value === "notifyer") return "抄送人";
return value || "-";
}
function formatTime(value: string | null) {
if (!value) return "-";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return "-";
return date.toLocaleString("zh-CN", {
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
}
function employeeLabel(employee: WecomApprovalArchiveItem["applicant"]) {
if (!employee) return "未映射";
return employee.employeeNo ? `${employee.name}(${employee.employeeNo})` : employee.name;
}
function canRequestAll(roleCodes: string[], permissions: string[]) {
return roleCodes.includes("hr") || roleCodes.includes("admin") || permissions.includes("*");
}
function ArchiveRow({
item,
active,
onSelect,
}: {
item: WecomApprovalArchiveItem;
active: boolean;
onSelect: () => void;
}) {
return (
<>
|
{typeLabel(item.templateType)}
{item.spName || "OA-POC 归档记录"}
单号前缀:{item.spNoPrefix || "-"}
|
{employeeLabel(item.applicant)} |
|
{formatTime(item.statusObservedAt || item.completedAt || item.appliedAt)} |
{sourceLabel(item.lastSource)}
{item.openFindingCount ? finding {item.openFindingCount} 项 : null}
|
|
>
);
}
export function WecomApprovalArchivePage() {
const { me, permissions } = useAuth();
const [scope, setScope] = useState("self");
const [type, setType] = useState<"" | WecomApprovalArchiveType>("");
const [status, setStatus] = useState<"" | WecomApprovalArchiveStatus>("");
const [finding, setFinding] = useState<"" | WecomApprovalArchiveFinding>("");
const [selectedId, setSelectedId] = useState("");
const allAllowed = canRequestAll(me?.roleCodes ?? [], permissions);
const query = useMemo(() => ({ scope, type, status, finding, pageSize: 50 }), [scope, type, status, finding]);
const listQuery = useQuery({
queryKey: ["wecom-approval-archive", query],
queryFn: () => fetchWecomApprovalArchive(query),
});
const detailQuery = useQuery({
enabled: Boolean(selectedId),
queryKey: ["wecom-approval-archive", "detail", selectedId],
queryFn: () => fetchWecomApprovalArchiveDetail(selectedId),
});
const items = listQuery.data?.items ?? [];
const detail = detailQuery.data?.item ?? null;
return (
企业微信主办,OA 自建留存
仅已归档记录
>
}
/>
{listQuery.isLoading ? : null}
{listQuery.isError ? {resolveError(listQuery.error)}
: null}
{!listQuery.isLoading && !listQuery.isError && !items.length ? (
暂无已归档的企微审批历史。若公网 OA DB 还没有留存数据,验收记录为 PARTIAL_ARCHIVE_NEEDS_DATA。
) : null}
{!listQuery.isLoading && !listQuery.isError && items.length ? (
<>
| 类型/单号 |
申请人 |
状态 |
状态时间 |
来源/对账 |
详情 |
{items.map((item) => (
setSelectedId(item.id)} />
))}
{items.map((item) => (
{typeLabel(item.templateType)}
单号前缀:{item.spNoPrefix || "-"}
{employeeLabel(item.applicant)} · {formatTime(item.statusObservedAt || item.completedAt || item.appliedAt)}
{sourceLabel(item.lastSource)}{item.openFindingCount ? ` · finding ${item.openFindingCount}` : ""}
))}
>
) : null}
{!selectedId ? 请选择一条归档记录查看详情。
: null}
{selectedId && detailQuery.isLoading ? : null}
{selectedId && detailQuery.isError ? {resolveError(detailQuery.error)}
: null}
{detail ? (
申请人
{employeeLabel(detail.applicant)}
单号前缀
{detail.spNoPrefix || "-"}
{detail.singleTapApproveRisk ?
企微同意可能单击即通过,OA 仅留存动作来源和历史审计。
: null}
字段摘要
{detail.fields.length ? detail.fields.map((field) => (
{field.fieldName || field.fieldKey}
{field.fieldType || "-"}
{field.sensitiveLevel === "restricted" ? "受限字段" : "普通字段"}
)) :
暂无字段摘要。
}
参与人与动作
{detail.participants.length ? detail.participants.map((item) => (
{roleLabel(item.role)}
{employeeLabel(item.employee)}
)) :
暂无映射参与人。
}
{detail.actions.length ? detail.actions.map((item) => (
{actionLabel(item.actionType)}
{employeeLabel(item.operator)}
{formatTime(item.actionTime)} · {sourceLabel(item.source)}
)) :
暂无动作历史。
}
对账 finding
{detail.findings.length ? detail.findings.map((item) => (
{item.findingType}
{item.status} · {item.severity} · {formatTime(item.detectedAt)}
)) :
暂无对账 finding。
}
) : null}
);
}