重构资产盘点与移动扫码页面(05-30-asset-inventory-mobile-redesign)
这个提交包含在:
@@ -15,8 +15,10 @@ import {
|
||||
} from "../api/inventoryChecks";
|
||||
import { fetchCostCenters, fetchLegalEntities } from "../api/masterData";
|
||||
import { isApiError } from "../api/types";
|
||||
import { ActionConfirmDialog } from "../components/common/ActionConfirmDialog";
|
||||
import { PageHeader } from "../components/common/PageHeader";
|
||||
import { TableSkeleton } from "../components/common/TableSkeleton";
|
||||
import { PageScaffold } from "../components/patterns/PageScaffold";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Input } from "../components/ui/input";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
|
||||
@@ -84,6 +86,7 @@ export function InventoryChecksPage() {
|
||||
const [recordAssetId, setRecordAssetId] = useState("");
|
||||
const [recordResult, setRecordResult] = useState<InventoryCheckResult>("matched");
|
||||
const [recordNote, setRecordNote] = useState("");
|
||||
const [confirmCompleteCheck, setConfirmCompleteCheck] = useState(false);
|
||||
|
||||
const inventoryChecksQuery = useQuery({
|
||||
queryKey: ["inventory-checks"],
|
||||
@@ -122,6 +125,19 @@ export function InventoryChecksPage() {
|
||||
() => (inventoryChecksQuery.data?.items ?? []).find((item) => item.id === selectedCheckId) ?? null,
|
||||
[inventoryChecksQuery.data?.items, selectedCheckId],
|
||||
);
|
||||
const inventoryStats = useMemo(() => {
|
||||
const items = inventoryChecksQuery.data?.items ?? [];
|
||||
return items.reduce(
|
||||
(acc, item) => {
|
||||
acc.total += 1;
|
||||
if (item.status === "draft") acc.draft += 1;
|
||||
if (item.status === "counting") acc.counting += 1;
|
||||
if (item.status === "completed") acc.completed += 1;
|
||||
return acc;
|
||||
},
|
||||
{ total: 0, draft: 0, counting: 0, completed: 0 },
|
||||
);
|
||||
}, [inventoryChecksQuery.data?.items]);
|
||||
|
||||
const scopedAssets = useMemo(() => {
|
||||
const items = assetsQuery.data?.items ?? [];
|
||||
@@ -228,10 +244,10 @@ export function InventoryChecksPage() {
|
||||
}, [costCentersQuery.data?.items, legalEntitiesQuery.data?.items, scopeType]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageScaffold className="space-y-6">
|
||||
<PageHeader
|
||||
title="资产盘点"
|
||||
subtitle="创建、启动、记录并完成资产盘点任务。"
|
||||
subtitle="按任务执行盘点:先确定范围,再启动、扫码/录入、核对结果并确认完成。"
|
||||
actions={
|
||||
<Button
|
||||
type="button"
|
||||
@@ -244,6 +260,29 @@ export function InventoryChecksPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
<section className="ops-status-strip grid gap-4 md:grid-cols-4" data-testid="inventory-check-summary">
|
||||
<div className="finance-metric-card">
|
||||
<div className="text-sm text-zinc-500">盘点任务</div>
|
||||
<div className="mt-1 text-2xl font-semibold text-zinc-900">{inventoryStats.total}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">按季度或事件触发</div>
|
||||
</div>
|
||||
<div className="finance-metric-card">
|
||||
<div className="text-sm text-zinc-500">草稿</div>
|
||||
<div className="mt-1 text-2xl font-semibold text-zinc-900">{inventoryStats.draft}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">待启动后录入</div>
|
||||
</div>
|
||||
<div className="finance-metric-card">
|
||||
<div className="text-sm text-zinc-500">盘点中</div>
|
||||
<div className="mt-1 text-2xl font-semibold text-zinc-900">{inventoryStats.counting}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">可扫码或手工录入</div>
|
||||
</div>
|
||||
<div className="finance-metric-card">
|
||||
<div className="text-sm text-zinc-500">已完成</div>
|
||||
<div className="mt-1 text-2xl font-semibold text-zinc-900">{inventoryStats.completed}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">保留差异和备注</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-[360px_minmax(0,1fr)]">
|
||||
<div className="card-std space-y-4 p-6">
|
||||
<h3 className="text-base font-semibold text-zinc-900">新建盘点任务</h3>
|
||||
@@ -347,7 +386,7 @@ export function InventoryChecksPage() {
|
||||
</Button>
|
||||
) : null}
|
||||
{selectedCheck.status === "counting" ? (
|
||||
<Button data-testid="inventory-complete" type="button" variant="secondary" onClick={() => completeCheckMutation.mutate()} disabled={completeCheckMutation.isPending}>
|
||||
<Button data-testid="inventory-complete" type="button" variant="secondary" onClick={() => setConfirmCompleteCheck(true)} disabled={completeCheckMutation.isPending}>
|
||||
{completeCheckMutation.isPending ? "完成中..." : "完成盘点"}
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -426,6 +465,15 @@ export function InventoryChecksPage() {
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ActionConfirmDialog
|
||||
open={confirmCompleteCheck}
|
||||
onOpenChange={setConfirmCompleteCheck}
|
||||
kind="complete"
|
||||
targetName={selectedCheck?.checkNo}
|
||||
description={`完成后该盘点任务将停止继续录入。当前已记录 ${selectedCheck?.items.length ?? 0} 条结果,请确认差异和备注已核对。`}
|
||||
confirmLabel="确认完成盘点"
|
||||
onConfirm={() => completeCheckMutation.mutate()}
|
||||
/>
|
||||
</PageScaffold>
|
||||
);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户