diff --git a/client/src/components/common/ActionConfirmDialog.tsx b/client/src/components/common/ActionConfirmDialog.tsx new file mode 100644 index 00000000..772d9f22 --- /dev/null +++ b/client/src/components/common/ActionConfirmDialog.tsx @@ -0,0 +1,52 @@ +import type { ReactNode } from "react"; +import { getActionConfirmCopy, type ActionConfirmKind } from "../../lib/actionConfirm"; +import { ConfirmDialog } from "./ConfirmDialog"; + +type ActionConfirmDialogProps = { + open: boolean; + onOpenChange: (open: boolean) => void; + kind: ActionConfirmKind; + targetName?: string; + count?: number; + title?: string; + description?: ReactNode; + confirmLabel?: string; + variant?: "default" | "destructive"; + testId?: string; + confirmTestId?: string; + cancelTestId?: string; + onConfirm: () => void | Promise; +}; + +export function ActionConfirmDialog({ + open, + onOpenChange, + kind, + targetName, + count, + title, + description, + confirmLabel, + variant, + testId, + confirmTestId, + cancelTestId, + onConfirm, +}: ActionConfirmDialogProps) { + const copy = getActionConfirmCopy(kind, { targetName, count, detail: description }); + + return ( + + ); +} diff --git a/client/src/components/common/ConfirmDialog.tsx b/client/src/components/common/ConfirmDialog.tsx index 5cc2e6dd..3f06bac1 100644 --- a/client/src/components/common/ConfirmDialog.tsx +++ b/client/src/components/common/ConfirmDialog.tsx @@ -31,9 +31,9 @@ export function ConfirmDialog({ confirmLabel = "确认", cancelLabel = "取消", variant = "default", - testId, - confirmTestId, - cancelTestId, + testId = "action-confirm-dialog", + confirmTestId = "action-confirm-submit", + cancelTestId = "action-confirm-cancel", onConfirm, }: ConfirmDialogProps) { const [pending, setPending] = useState(false); diff --git a/client/src/components/travel/TravelReconciliationTab.tsx b/client/src/components/travel/TravelReconciliationTab.tsx index 4cbb45a6..badb0337 100644 --- a/client/src/components/travel/TravelReconciliationTab.tsx +++ b/client/src/components/travel/TravelReconciliationTab.tsx @@ -25,8 +25,10 @@ import { EmptyStateCard } from '../common/EmptyStateCard'; import { Pagination } from '../common/Pagination'; import { StatusBadge } from '../common/StatusBadge'; import { Button } from '../ui/button'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog'; import { Input } from '../ui/input'; import { Label } from '../ui/label'; +import { Textarea } from '../ui/textarea'; const PAGE_SIZE = 10; @@ -68,6 +70,8 @@ export function TravelReconciliationTab({ selectedBatchId, onSelectBatch }: Trav const [costCenterFilter, setCostCenterFilter] = useState(''); const [periodStartFilter, setPeriodStartFilter] = useState(''); const [periodEndFilter, setPeriodEndFilter] = useState(''); + const [confirmItemTarget, setConfirmItemTarget] = useState(null); + const [confirmItemNote, setConfirmItemNote] = useState(''); const [page, setPage] = useState(1); const [actionItemId, setActionItemId] = useState(null); @@ -248,11 +252,8 @@ export function TravelReconciliationTab({ selectedBatchId, onSelectBatch }: Trav }); function handleConfirm(item: TravelReconciliationItem) { - const note = window.prompt('确认备注(可选)', item.confirmNote ?? ''); - if (note === null) { - return; - } - confirmMutation.mutate({ item, note: note.trim() || null }); + setConfirmItemTarget(item); + setConfirmItemNote(item.confirmNote ?? ''); } const detail = detailQuery.data ?? null; @@ -260,6 +261,7 @@ export function TravelReconciliationTab({ selectedBatchId, onSelectBatch }: Trav const totalPages = Math.max(1, Math.ceil((listQuery.data?.total ?? 0) / PAGE_SIZE)); return ( + <>
{canCreateBatch ? ( @@ -520,5 +522,64 @@ export function TravelReconciliationTab({ selectedBatchId, onSelectBatch }: Trav ) : null}
+ { + if (!open) { + setConfirmItemTarget(null); + setConfirmItemNote(''); + } + }} + > + + + 确认对账异常项 + + 确认后该异常项将进入已确认状态,并写入差旅对账处理记录。备注可空。 + + +
+ +