feat(reimbursement-small-team-form-validation): 完善付款批次核对与回单预览
这个提交包含在:
@@ -40,7 +40,7 @@ export function AttachmentGovernanceBadges({
|
||||
<span className={sensitivityClass(attachment.sensitivityLevel)}>
|
||||
{SENSITIVITY_LEVEL_LABEL[attachment.sensitivityLevel] ?? attachment.sensitivityLevel}
|
||||
</span>
|
||||
{attachmentRequiresDownloadReason(attachment) ? <span className="badge-warning">需填用途</span> : null}
|
||||
{attachmentRequiresDownloadReason(attachment) ? <span className="badge-warning">下载需说明用途</span> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type Props = {
|
||||
description: string | null;
|
||||
editable: boolean;
|
||||
mobile?: boolean;
|
||||
showDescription?: boolean;
|
||||
};
|
||||
|
||||
export function ReimbursementHeaderEditor({
|
||||
@@ -22,14 +23,18 @@ export function ReimbursementHeaderEditor({
|
||||
description,
|
||||
editable,
|
||||
mobile = false,
|
||||
showDescription = true,
|
||||
}: Props) {
|
||||
const queryClient = useQueryClient();
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [date, setDate] = useState(reimbursementDate ?? "");
|
||||
const [reason, setReason] = useState(description ?? "");
|
||||
const [error, setError] = useState("");
|
||||
const dirty = editing && (date !== (reimbursementDate ?? "") || reason !== (description ?? ""));
|
||||
const runGuarded = useUnsavedChangesGuard(dirty, "报销事由和日期");
|
||||
const dirty = editing && (
|
||||
date !== (reimbursementDate ?? "") ||
|
||||
(showDescription && reason !== (description ?? ""))
|
||||
);
|
||||
const runGuarded = useUnsavedChangesGuard(dirty, showDescription ? "报销事由和日期" : "报销日期");
|
||||
|
||||
useEffect(() => {
|
||||
if (editing) return;
|
||||
@@ -40,7 +45,7 @@ export function ReimbursementHeaderEditor({
|
||||
const mutation = useMutation({
|
||||
mutationFn: () => updateReimbursement(reimbursementId, {
|
||||
reimbursementDate: date,
|
||||
description: reason.trim() || null,
|
||||
...(showDescription ? { description: reason.trim() || null } : {}),
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
setError("");
|
||||
@@ -59,16 +64,19 @@ export function ReimbursementHeaderEditor({
|
||||
});
|
||||
|
||||
if (!editing) {
|
||||
if (!showDescription && !editable) return null;
|
||||
return (
|
||||
<div className={mobile ? "mt-3" : "mt-3 rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2"}>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 text-sm leading-6 text-zinc-600">
|
||||
<span className="font-medium text-zinc-800">报销事由:</span>
|
||||
{description || "未填写"}
|
||||
</div>
|
||||
{showDescription ? (
|
||||
<div className="min-w-0 text-sm leading-6 text-zinc-600">
|
||||
<span className="font-medium text-zinc-800">报销事由:</span>
|
||||
{description || "未填写"}
|
||||
</div>
|
||||
) : <div className="min-w-0 text-sm leading-6 text-zinc-600">报销日期可在草稿阶段修改</div>}
|
||||
{editable ? (
|
||||
<Button size="sm" type="button" variant="secondary" onClick={() => setEditing(true)}>
|
||||
编辑
|
||||
{showDescription ? "编辑" : "编辑报销日期"}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -78,15 +86,17 @@ export function ReimbursementHeaderEditor({
|
||||
|
||||
return (
|
||||
<div className="mt-3 space-y-3 rounded-lg border border-primary-100 bg-primary-50/40 p-3" data-testid="reimbursement-header-editor">
|
||||
<div className={mobile ? "space-y-3" : "grid gap-3 md:grid-cols-[12rem_1fr]"}>
|
||||
<div className={mobile || !showDescription ? "space-y-3" : "grid gap-3 md:grid-cols-[12rem_1fr]"}>
|
||||
<label className="space-y-1 text-sm text-zinc-700">
|
||||
<span>报销日期</span>
|
||||
<Input type="date" value={date} disabled={mutation.isPending} onChange={(event) => setDate(event.target.value)} />
|
||||
</label>
|
||||
<label className="space-y-1 text-sm text-zinc-700">
|
||||
<span>报销事由</span>
|
||||
<Textarea rows={2} maxLength={500} value={reason} disabled={mutation.isPending} onChange={(event) => setReason(event.target.value)} />
|
||||
</label>
|
||||
{showDescription ? (
|
||||
<label className="space-y-1 text-sm text-zinc-700">
|
||||
<span>报销事由</span>
|
||||
<Textarea rows={2} maxLength={500} value={reason} disabled={mutation.isPending} onChange={(event) => setReason(event.target.value)} />
|
||||
</label>
|
||||
) : null}
|
||||
</div>
|
||||
{error ? <p className="text-sm text-red-600">{error}</p> : null}
|
||||
<div className="flex gap-2">
|
||||
|
||||
在新工单中引用
屏蔽一个用户