R7E-16E/R7E-16F 高风险操作确认统一

这个提交包含在:
2026-05-15 22:55:00 +08:00
父节点 5c38d810c1
当前提交 bf791d7a67
共修改 18 个文件,包含 979 行新增和 68 行删除
+70 -9
查看文件
@@ -13,6 +13,7 @@ import { SectionPanel } from "../components/patterns/SectionPanel";
import { isApiError } from "../api/types";
import { StatusBadge } from "../components/common/StatusBadge";
import { Button } from "../components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../components/ui/dialog";
import { Input } from "../components/ui/input";
import { Label } from "../components/ui/label";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
@@ -173,6 +174,8 @@ export function ProjectDetailPage() {
nextFollowUpDate: "",
});
const [editingCommunicationLogId, setEditingCommunicationLogId] = useState<string | null>(null);
const [waiveChecklistTarget, setWaiveChecklistTarget] = useState<{ itemId: string; currentNote: string | null } | null>(null);
const [waiveChecklistNote, setWaiveChecklistNote] = useState("");
useEffect(() => {
if (!detail) return;
@@ -452,15 +455,8 @@ export function ProjectDetailPage() {
const handleChecklistStatusChange = (itemId: string, status: ProjectClosureChecklistStatus, currentNote: string | null) => {
if (status === "waived") {
const note = window.prompt("请填写豁免理由", currentNote ?? "");
if (note == null) {
return;
}
if (!note.trim()) {
toast.error("豁免必须填写说明");
return;
}
patchClosureChecklistMutation.mutate({ itemId, status, note: note.trim() });
setWaiveChecklistTarget({ itemId, currentNote });
setWaiveChecklistNote(currentNote ?? "");
return;
}
patchClosureChecklistMutation.mutate({ itemId, status });
@@ -491,6 +487,7 @@ export function ProjectDetailPage() {
const riskItems = risksQuery.data?.items ?? detail.risks;
return (
<>
<PageScaffold>
<EntityOverviewHero
title={`${detail.projectNo} · ${detail.name}`}
@@ -1061,6 +1058,70 @@ export function ProjectDetailPage() {
</div>
</div>
</PageScaffold>
<Dialog
open={Boolean(waiveChecklistTarget)}
onOpenChange={(open) => {
if (!open) {
setWaiveChecklistTarget(null);
setWaiveChecklistNote("");
}
}}
>
<DialogContent className="max-w-md" data-testid="action-confirm-dialog">
<DialogHeader>
<DialogTitle>确认豁免收口项</DialogTitle>
<DialogDescription>
豁免后该收口项会标记为已豁免,并写入项目收口记录。必须填写可理解说明。
</DialogDescription>
</DialogHeader>
<div className="space-y-2">
<label className="text-sm font-medium text-zinc-900" htmlFor="project-closure-waive-note">
豁免理由
</label>
<Textarea
id="project-closure-waive-note"
data-testid="project-closure-waive-note"
value={waiveChecklistNote}
onChange={(event) => setWaiveChecklistNote(event.target.value)}
placeholder="说明为什么该收口项可以豁免"
rows={3}
/>
</div>
<DialogFooter>
<Button
type="button"
variant="secondary"
data-testid="action-confirm-cancel"
disabled={patchClosureChecklistMutation.isPending}
onClick={() => {
setWaiveChecklistTarget(null);
setWaiveChecklistNote("");
}}
>
取消
</Button>
<Button
type="button"
variant="destructive"
data-testid="action-confirm-submit"
disabled={!waiveChecklistTarget || !waiveChecklistNote.trim() || patchClosureChecklistMutation.isPending}
onClick={() => {
if (!waiveChecklistTarget) return;
patchClosureChecklistMutation.mutate({
itemId: waiveChecklistTarget.itemId,
status: "waived",
note: waiveChecklistNote.trim(),
});
setWaiveChecklistTarget(null);
setWaiveChecklistNote("");
}}
>
{patchClosureChecklistMutation.isPending ? "处理中..." : "确认豁免"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}