修复 R124 企业微信移动入口与部署迁移门禁
这个提交包含在:
@@ -4,12 +4,14 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
createReimbursement,
|
||||
deleteReimbursement,
|
||||
fetchReimbursements,
|
||||
type ReimbursementListItem,
|
||||
type ReimbursementStatus,
|
||||
} from "../api/reimbursements";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
import { hasPermission } from "../auth/permissions";
|
||||
import { ConfirmDialog } from "../components/common/ConfirmDialog";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs";
|
||||
import { PageHeader } from "../components/common/PageHeader";
|
||||
@@ -48,7 +50,17 @@ function formatDate(value: string | null | undefined) {
|
||||
return value || "未填写日期";
|
||||
}
|
||||
|
||||
function ReimbursementCard({ item }: { item: ReimbursementListItem }) {
|
||||
function ReimbursementCard({
|
||||
item,
|
||||
canDeleteDraft,
|
||||
isDeleting,
|
||||
onDelete,
|
||||
}: {
|
||||
item: ReimbursementListItem;
|
||||
canDeleteDraft: boolean;
|
||||
isDeleting: boolean;
|
||||
onDelete: (item: ReimbursementListItem) => void;
|
||||
}) {
|
||||
const actionLabel = item.status === "draft" || item.status === "rejected" ? "继续编辑" : "查看详情";
|
||||
const displayCode = resolveReimbursementDisplayCode(item);
|
||||
|
||||
@@ -75,9 +87,23 @@ function ReimbursementCard({ item }: { item: ReimbursementListItem }) {
|
||||
|
||||
{item.description ? <p className="mt-3 line-clamp-2 text-sm text-zinc-500">{item.description}</p> : null}
|
||||
|
||||
<Button asChild className="mt-4 w-full" type="button">
|
||||
<Link to={`/mobile/reimbursements/${item.id}`}>{actionLabel}</Link>
|
||||
</Button>
|
||||
<div className={canDeleteDraft ? "mt-4 grid grid-cols-2 gap-2" : "mt-4"}>
|
||||
<Button asChild className="w-full" type="button">
|
||||
<Link to={`/mobile/reimbursements/${item.id}`}>{actionLabel}</Link>
|
||||
</Button>
|
||||
{canDeleteDraft ? (
|
||||
<Button
|
||||
className="w-full"
|
||||
data-testid="mobile-reimbursement-delete"
|
||||
disabled={isDeleting}
|
||||
onClick={() => onDelete(item)}
|
||||
type="button"
|
||||
variant="destructive"
|
||||
>
|
||||
删除草稿
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -87,7 +113,9 @@ export function MobileReimbursementsPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const { permissions } = useAuth();
|
||||
const [status, setStatus] = useState<ReimbursementStatus>("draft");
|
||||
const [deleteTarget, setDeleteTarget] = useState<ReimbursementListItem | null>(null);
|
||||
const canCreateExpense = hasPermission(permissions, "expense.create");
|
||||
const canDeleteExpense = hasPermission(permissions, "expense.delete_own");
|
||||
|
||||
const listQuery = useQuery({
|
||||
queryKey: ["mobile", "reimbursements", status],
|
||||
@@ -106,6 +134,21 @@ export function MobileReimbursementsPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (reimbursementId: string) => deleteReimbursement(reimbursementId),
|
||||
onSuccess: async () => {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ["mobile", "reimbursements"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["reimbursements"] }),
|
||||
]);
|
||||
setDeleteTarget(null);
|
||||
toast.success("草稿已删除");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(resolveErrorMessage(error));
|
||||
},
|
||||
});
|
||||
|
||||
const items = listQuery.data?.items ?? [];
|
||||
const total = listQuery.data?.total ?? 0;
|
||||
const totalAmount = useMemo(
|
||||
@@ -166,10 +209,35 @@ export function MobileReimbursementsPage() {
|
||||
<div className="rounded-lg border border-dashed border-zinc-200 p-6 text-center text-sm text-zinc-500">暂无报销记录</div>
|
||||
) : null}
|
||||
{items.map((item) => (
|
||||
<ReimbursementCard item={item} key={item.id} />
|
||||
<ReimbursementCard
|
||||
canDeleteDraft={canDeleteExpense && item.status === "draft"}
|
||||
isDeleting={deleteMutation.isPending}
|
||||
item={item}
|
||||
key={item.id}
|
||||
onDelete={setDeleteTarget}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ConfirmDialog
|
||||
open={Boolean(deleteTarget)}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setDeleteTarget(null);
|
||||
}
|
||||
}}
|
||||
title="删除草稿?"
|
||||
description="删除后无法恢复,已添加发票会回到可用状态。"
|
||||
variant="destructive"
|
||||
confirmLabel="删除草稿"
|
||||
onConfirm={async () => {
|
||||
if (!deleteTarget) {
|
||||
return;
|
||||
}
|
||||
await deleteMutation.mutateAsync(deleteTarget.id);
|
||||
}}
|
||||
/>
|
||||
</PageScaffold>
|
||||
);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户