统一菜单命名并移除剩余台账工具(05-31-unified-menu-remove-ledger-tools-wave2)
这个提交包含在:
@@ -216,7 +216,7 @@ export function ContractsRegistryPage() {
|
||||
const [editor, setEditor] = useState<EditorState>(() => buildEmptyEditor(initialProjectId));
|
||||
const [showExpiring, setShowExpiring] = useState(false);
|
||||
const [showTrialData, setShowTrialData] = useState(false);
|
||||
const [showLedgerTools, setShowLedgerTools] = useState(false);
|
||||
const [showAdvancedFilters, setShowAdvancedFilters] = useState(false);
|
||||
const [showEditor, setShowEditor] = useState(false);
|
||||
const [confirmExportFormat, setConfirmExportFormat] = useState<ContractRegistryExportFormat | null>(null);
|
||||
const [confirmStatusTarget, setConfirmStatusTarget] = useState<ContractRegistryItem | null>(null);
|
||||
@@ -293,9 +293,9 @@ export function ContractsRegistryPage() {
|
||||
await refreshAll();
|
||||
setShowEditor(false);
|
||||
resetEditor();
|
||||
toast.success(editorId ? "合同台账已更新" : "合同台账已创建");
|
||||
toast.success(editorId ? "合同已更新" : "合同已创建");
|
||||
},
|
||||
onError: (error) => toast.error(resolveError(error, editorId ? "更新合同台账失败" : "创建合同台账失败")),
|
||||
onError: (error) => toast.error(resolveError(error, editorId ? "更新合同失败" : "创建合同失败")),
|
||||
});
|
||||
|
||||
const toggleStatusMutation = useMutation({
|
||||
@@ -324,15 +324,15 @@ export function ContractsRegistryPage() {
|
||||
}),
|
||||
onSuccess: (response, format) => {
|
||||
downloadBlob(response.blob, response.filename ?? `contracts-registry.${format}`);
|
||||
toast.success(`合同台账已导出为 ${format.toUpperCase()}`);
|
||||
toast.success(`合同已导出为 ${format.toUpperCase()}`);
|
||||
},
|
||||
onError: (error) => toast.error(resolveError(error, "导出合同台账失败")),
|
||||
onError: (error) => toast.error(resolveError(error, "导出合同失败")),
|
||||
});
|
||||
|
||||
return (
|
||||
<PageScaffold>
|
||||
<PageHeader
|
||||
title="合同台账"
|
||||
title="合同"
|
||||
actions={
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{initialProjectId ? (
|
||||
@@ -349,73 +349,58 @@ export function ContractsRegistryPage() {
|
||||
>
|
||||
新增合同
|
||||
</Button>
|
||||
<Button type="button" variant={showLedgerTools ? "default" : "secondary"} onClick={() => setShowLedgerTools((value) => !value)}>
|
||||
台账工具
|
||||
<Button
|
||||
type="button"
|
||||
variant={showExpiring ? "default" : "secondary"}
|
||||
disabled={expiringQuery.isLoading}
|
||||
onClick={() => setShowExpiring((value) => !value)}
|
||||
>
|
||||
到期列表 {expiringContractCount}
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button type="button" variant="secondary" disabled={exportMutation.isPending}>
|
||||
{exportMutation.isPending ? "导出中..." : "导出"}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onSelect={() => setConfirmExportFormat("csv")}>
|
||||
导出 CSV
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => setConfirmExportFormat("xlsx")}>
|
||||
导出 Excel
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button
|
||||
type="button"
|
||||
variant={showAdvancedFilters || filters.expiryDateFrom || filters.expiryDateTo || showTrialData ? "default" : "secondary"}
|
||||
onClick={() => setShowAdvancedFilters((value) => !value)}
|
||||
>
|
||||
筛选
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{showLedgerTools ? (
|
||||
<div className="detail-command-panel space-y-4" data-testid="contracts-ledger-tools">
|
||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold text-zinc-900">台账工具</h3>
|
||||
</div>
|
||||
<div className="flex shrink-0 flex-wrap gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
disabled={expiringQuery.isLoading}
|
||||
onClick={() => setShowExpiring((value) => !value)}
|
||||
>
|
||||
到期列表 {expiringContractCount}
|
||||
</Button>
|
||||
<Button type="button" variant="secondary" disabled={exportMutation.isPending} onClick={() => setConfirmExportFormat("csv")}>
|
||||
{exportMutation.isPending ? "导出中..." : "导出 CSV"}
|
||||
</Button>
|
||||
<Button type="button" variant="secondary" disabled={exportMutation.isPending} onClick={() => setConfirmExportFormat("xlsx")}>
|
||||
{exportMutation.isPending ? "导出中..." : "导出 Excel"}
|
||||
{expiringQuery.isError ? (
|
||||
<div className="text-sm text-red-600">{resolveError(expiringQuery.error, "加载到期预警失败")}</div>
|
||||
) : null}
|
||||
{showExpiring ? (
|
||||
<div className="detail-command-panel space-y-2" data-testid="contracts-expiring-list">
|
||||
{expiringItems.map((item) => (
|
||||
<div key={item.id} className="flex items-center justify-between gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm">
|
||||
<div className="min-w-0">
|
||||
<div className="truncate font-medium text-zinc-900">{item.contractNo} · {item.counterpartyName}</div>
|
||||
<div className="text-xs text-zinc-500">到期日 {formatDisplayDate(item.expiryDate)} · 责任人 {item.owner?.name ?? "-"}</div>
|
||||
</div>
|
||||
<Button type="button" size="sm" variant="secondary" onClick={() => openEditor(item)}>
|
||||
编辑
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div className="space-y-2">
|
||||
<Label>到期日开始</Label>
|
||||
<DateTextInput
|
||||
value={filters.expiryDateFrom}
|
||||
onChange={(event) => setFilters((current) => ({ ...current, expiryDateFrom: event.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>到期日结束</Label>
|
||||
<DateTextInput
|
||||
value={filters.expiryDateTo}
|
||||
onChange={(event) => setFilters((current) => ({ ...current, expiryDateTo: event.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{expiringQuery.isError ? (
|
||||
<div className="text-sm text-red-600">{resolveError(expiringQuery.error, "加载到期预警失败")}</div>
|
||||
) : null}
|
||||
{showExpiring ? (
|
||||
<div className="space-y-2 border-t border-zinc-200 pt-3">
|
||||
{expiringItems.map((item) => (
|
||||
<div key={item.id} className="flex items-center justify-between gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm">
|
||||
<div className="min-w-0">
|
||||
<div className="truncate font-medium text-zinc-900">{item.contractNo} · {item.counterpartyName}</div>
|
||||
<div className="text-xs text-zinc-500">到期日 {formatDisplayDate(item.expiryDate)} · 责任人 {item.owner?.name ?? "-"}</div>
|
||||
</div>
|
||||
<Button type="button" size="sm" variant="secondary" onClick={() => openEditor(item)}>
|
||||
编辑
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{!expiringQuery.isLoading && !expiringItems.length ? (
|
||||
<div className="text-sm text-zinc-500">暂无到期合同</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
{!expiringQuery.isLoading && !expiringItems.length ? (
|
||||
<div className="text-sm text-zinc-500">暂无到期合同</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -425,7 +410,7 @@ export function ContractsRegistryPage() {
|
||||
<div className="card-std space-y-4 p-6" data-testid="contracts-editor-panel">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold text-zinc-900">{editorId ? "编辑合同台账" : "新建合同台账"}</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">{editorId ? "编辑合同" : "新建合同"}</h3>
|
||||
<p className="mt-1 text-sm text-zinc-500">
|
||||
补齐合同类型、金额、签署/生效/到期日期,续约将自动创建新的草稿合同。
|
||||
</p>
|
||||
@@ -676,7 +661,7 @@ export function ContractsRegistryPage() {
|
||||
}
|
||||
onClick={() => saveMutation.mutate()}
|
||||
>
|
||||
{saveMutation.isPending ? (editorId ? "保存中..." : "创建中...") : editorId ? "保存修改" : "创建合同台账"}
|
||||
{saveMutation.isPending ? (editorId ? "保存中..." : "创建中...") : editorId ? "保存修改" : "创建合同"}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -704,13 +689,33 @@ export function ContractsRegistryPage() {
|
||||
>
|
||||
重置筛选
|
||||
</Button>
|
||||
{showLedgerTools ? (
|
||||
</>
|
||||
}
|
||||
advanced={showAdvancedFilters || filters.expiryDateFrom || filters.expiryDateTo || showTrialData ? (
|
||||
<div className="grid gap-3 md:grid-cols-[160px_160px_auto] xl:grid-cols-[180px_180px_auto]" data-testid="contracts-advanced-filters">
|
||||
<div className="space-y-2">
|
||||
<Label>到期日开始</Label>
|
||||
<DateTextInput
|
||||
value={filters.expiryDateFrom}
|
||||
onChange={(event) => setFilters((current) => ({ ...current, expiryDateFrom: event.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>到期日结束</Label>
|
||||
<DateTextInput
|
||||
value={filters.expiryDateTo}
|
||||
onChange={(event) => setFilters((current) => ({ ...current, expiryDateTo: event.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-end">
|
||||
<Button type="button" variant={showTrialData ? "default" : "secondary"} onClick={() => setShowTrialData((value) => !value)}>
|
||||
{showTrialData ? "隐藏测试数据" : "显示测试数据"}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
>
|
||||
<div className="grid gap-3 xl:grid-cols-[minmax(180px,1fr)_160px_160px_minmax(220px,1fr)]">
|
||||
<div>
|
||||
@@ -774,7 +779,7 @@ export function ContractsRegistryPage() {
|
||||
|
||||
{contractsQuery.isLoading ? <div className="text-sm text-zinc-500">加载中...</div> : null}
|
||||
{contractsQuery.isError ? (
|
||||
<div className="text-sm text-red-600">{resolveError(contractsQuery.error, "加载合同台账失败")}</div>
|
||||
<div className="text-sm text-red-600">{resolveError(contractsQuery.error, "加载合同失败")}</div>
|
||||
) : null}
|
||||
|
||||
{!showTrialData && hiddenTrialContractCount > 0 ? (
|
||||
@@ -804,7 +809,7 @@ export function ContractsRegistryPage() {
|
||||
</article>
|
||||
))}
|
||||
{!contractsQuery.isLoading && !visibleContracts.length ? (
|
||||
<div className="rounded-lg border border-zinc-200 bg-white p-4 text-sm text-zinc-500">暂无合同台账</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-white p-4 text-sm text-zinc-500">暂无合同</div>
|
||||
) : null}
|
||||
</BusinessMobileList>
|
||||
|
||||
@@ -884,7 +889,7 @@ export function ContractsRegistryPage() {
|
||||
) : (
|
||||
<tr>
|
||||
<td className="py-8 text-center text-sm text-zinc-500" colSpan={6}>
|
||||
暂无合同台账
|
||||
暂无合同
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
@@ -898,7 +903,7 @@ export function ContractsRegistryPage() {
|
||||
if (!open) setConfirmExportFormat(null);
|
||||
}}
|
||||
kind="sensitiveExport"
|
||||
targetName="合同台账"
|
||||
targetName="合同"
|
||||
description="导出文件可能包含合同金额、相对方、项目归属、责任人和归档位置摘要。请确认当前筛选范围和用途合规。"
|
||||
onConfirm={() => {
|
||||
if (!confirmExportFormat) return;
|
||||
|
||||
在新工单中引用
屏蔽一个用户