完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)
这个提交包含在:
@@ -34,7 +34,9 @@ import { useAuth } from "../auth/AuthContext";
|
||||
import { hasPermission } from "../auth/permissions";
|
||||
import { CardSkeleton } from "../components/common/CardSkeleton";
|
||||
import { ConfirmDialog } from "../components/common/ConfirmDialog";
|
||||
import { useUnsavedChangesGuard } from "../components/common/UnsavedChangesGuard";
|
||||
import { AttachmentGovernanceBadges } from "../components/common/AttachmentGovernanceBadges";
|
||||
import { AttachmentDeleteConfirmDialog } from "../components/common/AttachmentDeleteConfirmDialog";
|
||||
import { useAttachmentDownloadReasonDialog } from "../components/common/useAttachmentDownloadReasonDialog";
|
||||
import { RiskFlagsCard } from "../components/common/RiskFlagsCard";
|
||||
import { StatusBadge } from "../components/common/StatusBadge";
|
||||
@@ -161,6 +163,7 @@ export function ReimbursementDetailPage() {
|
||||
const [selectedInvoices, setSelectedInvoices] = useState<SelectedInvoiceMap>({});
|
||||
const [selectedInvoiceFiles, setSelectedInvoiceFiles] = useState<File[]>([]);
|
||||
const [selectedAttachmentFiles, setSelectedAttachmentFiles] = useState<File[]>([]);
|
||||
const [deleteAttachmentTarget, setDeleteAttachmentTarget] = useState<{ id: string; originalName: string } | null>(null);
|
||||
const [invoiceActionError, setInvoiceActionError] = useState("");
|
||||
const [pendingOcrInvoiceIds, setPendingOcrInvoiceIds] = useState<string[]>([]);
|
||||
const [invoiceUploadProgress, setInvoiceUploadProgress] = useState({ completed: 0, total: 0 });
|
||||
@@ -170,6 +173,13 @@ export function ReimbursementDetailPage() {
|
||||
amount: "",
|
||||
description: "",
|
||||
});
|
||||
const runGuarded = useUnsavedChangesGuard(
|
||||
Boolean(itemEditor) ||
|
||||
selectedInvoiceFiles.length > 0 ||
|
||||
selectedAttachmentFiles.length > 0 ||
|
||||
Object.keys(selectedInvoices).length > 0,
|
||||
"报销表单",
|
||||
);
|
||||
|
||||
const detailQuery = useQuery({
|
||||
enabled: Boolean(reimbursementId),
|
||||
@@ -665,9 +675,11 @@ export function ReimbursementDetailPage() {
|
||||
});
|
||||
|
||||
const deleteAttachmentMutation = useMutation({
|
||||
mutationFn: (attachmentId: string) => deleteReimbursementAttachment(reimbursementId, attachmentId),
|
||||
mutationFn: ({ attachmentId, reason }: { attachmentId: string; reason: string }) =>
|
||||
deleteReimbursementAttachment(reimbursementId, attachmentId, reason),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["reimbursements", reimbursementId] });
|
||||
setDeleteAttachmentTarget(null);
|
||||
toast.success("附件材料已删除");
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -796,15 +808,19 @@ export function ReimbursementDetailPage() {
|
||||
};
|
||||
|
||||
const startCreateItem = () => {
|
||||
setItemEditor({ mode: "create" });
|
||||
setEditorInitialValues(computeEditorDefaults());
|
||||
setItemError("");
|
||||
runGuarded(() => {
|
||||
setItemEditor({ mode: "create" });
|
||||
setEditorInitialValues(computeEditorDefaults());
|
||||
setItemError("");
|
||||
});
|
||||
};
|
||||
|
||||
const startEditItem = (item: ReimbursementItem) => {
|
||||
setItemEditor({ mode: "edit", itemId: item.id });
|
||||
setEditorInitialValues(computeEditorDefaults(item));
|
||||
setItemError("");
|
||||
runGuarded(() => {
|
||||
setItemEditor({ mode: "edit", itemId: item.id });
|
||||
setEditorInitialValues(computeEditorDefaults(item));
|
||||
setItemError("");
|
||||
});
|
||||
};
|
||||
|
||||
const handleItemSave = (data: ItemEditorSaveData) => {
|
||||
@@ -812,8 +828,10 @@ export function ReimbursementDetailPage() {
|
||||
};
|
||||
|
||||
const handleItemCancel = () => {
|
||||
setItemEditor(null);
|
||||
setItemError("");
|
||||
runGuarded(() => {
|
||||
setItemEditor(null);
|
||||
setItemError("");
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmitAction = async () => {
|
||||
@@ -1539,7 +1557,7 @@ export function ReimbursementDetailPage() {
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
disabled={deleteAttachmentMutation.isPending}
|
||||
onClick={() => deleteAttachmentMutation.mutate(attachment.id)}
|
||||
onClick={() => setDeleteAttachmentTarget({ id: attachment.id, originalName: attachment.originalName })}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
@@ -1991,6 +2009,18 @@ export function ReimbursementDetailPage() {
|
||||
confirmLabel="删除草稿"
|
||||
onConfirm={() => { deleteReimbursementMutation.mutate(); }}
|
||||
/>
|
||||
<AttachmentDeleteConfirmDialog
|
||||
open={Boolean(deleteAttachmentTarget)}
|
||||
targetName={deleteAttachmentTarget?.originalName}
|
||||
pending={deleteAttachmentMutation.isPending}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setDeleteAttachmentTarget(null);
|
||||
}}
|
||||
onConfirm={(deleteReason) => {
|
||||
if (!deleteAttachmentTarget) return;
|
||||
deleteAttachmentMutation.mutate({ attachmentId: deleteAttachmentTarget.id, reason: deleteReason });
|
||||
}}
|
||||
/>
|
||||
</PageScaffold>
|
||||
);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户