MIF7 移动信息架构整改收口
这个提交包含在:
@@ -1,5 +1,5 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Link, useLocation, useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
@@ -35,6 +35,10 @@ const STATUS_LABEL: Record<ReimbursementStatus, string> = {
|
||||
cancelled: "已取消",
|
||||
};
|
||||
|
||||
function isReimbursementStatus(value: string | null): value is ReimbursementStatus {
|
||||
return ["draft", "reviewing", "approved", "paid", "rejected", "cancelled"].includes(value ?? "");
|
||||
}
|
||||
|
||||
function formatCurrency(value: string | null | undefined) {
|
||||
const numberValue = Number(value ?? 0);
|
||||
if (!Number.isFinite(numberValue)) {
|
||||
@@ -53,11 +57,13 @@ function formatDate(value: string | null | undefined) {
|
||||
function ReimbursementCard({
|
||||
item,
|
||||
canDeleteDraft,
|
||||
currentMobileSource,
|
||||
isDeleting,
|
||||
onDelete,
|
||||
}: {
|
||||
item: ReimbursementListItem;
|
||||
canDeleteDraft: boolean;
|
||||
currentMobileSource: string;
|
||||
isDeleting: boolean;
|
||||
onDelete: (item: ReimbursementListItem) => void;
|
||||
}) {
|
||||
@@ -89,7 +95,9 @@ function ReimbursementCard({
|
||||
|
||||
<div className={canDeleteDraft ? "mobile-two-action-grid mt-4" : "mt-4"}>
|
||||
<Button asChild className="w-full" type="button">
|
||||
<Link to={`/mobile/reimbursements/${item.id}`}>{actionLabel}</Link>
|
||||
<Link to={`/mobile/reimbursements/${item.id}`} state={{ from: currentMobileSource }}>
|
||||
{actionLabel}
|
||||
</Link>
|
||||
</Button>
|
||||
{canDeleteDraft ? (
|
||||
<Button
|
||||
@@ -110,12 +118,19 @@ function ReimbursementCard({
|
||||
|
||||
export function MobileReimbursementsPage() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const queryClient = useQueryClient();
|
||||
const { permissions } = useAuth();
|
||||
const [status, setStatus] = useState<ReimbursementStatus>("draft");
|
||||
const [status, setStatus] = useState<ReimbursementStatus>(() => {
|
||||
const queryStatus = searchParams.get("status");
|
||||
return isReimbursementStatus(queryStatus) ? queryStatus : "draft";
|
||||
});
|
||||
const [deleteTarget, setDeleteTarget] = useState<ReimbursementListItem | null>(null);
|
||||
const createIntentHandledRef = useRef(false);
|
||||
const canCreateExpense = hasPermission(permissions, "expense.create");
|
||||
const canDeleteExpense = hasPermission(permissions, "expense.delete_own");
|
||||
const currentMobileSource = `${location.pathname}${location.search}`;
|
||||
|
||||
const listQuery = useQuery({
|
||||
queryKey: ["mobile", "reimbursements", status],
|
||||
@@ -127,13 +142,32 @@ export function MobileReimbursementsPage() {
|
||||
onSuccess: async (response) => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["mobile", "reimbursements"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["reimbursements"] });
|
||||
navigate(`/mobile/reimbursements/${response.id}`);
|
||||
navigate(`/mobile/reimbursements/${response.id}`, { state: { from: currentMobileSource } });
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(resolveErrorMessage(error));
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const queryStatus = searchParams.get("status");
|
||||
if (isReimbursementStatus(queryStatus)) {
|
||||
setStatus(queryStatus);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.get("intent") !== "create" || !canCreateExpense || createIntentHandledRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
createIntentHandledRef.current = true;
|
||||
const nextParams = new URLSearchParams(searchParams);
|
||||
nextParams.delete("intent");
|
||||
setSearchParams(nextParams, { replace: true });
|
||||
createMutation.mutate();
|
||||
}, [canCreateExpense, createMutation, searchParams, setSearchParams]);
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (reimbursementId: string) => deleteReimbursement(reimbursementId),
|
||||
onSuccess: async () => {
|
||||
@@ -211,6 +245,7 @@ export function MobileReimbursementsPage() {
|
||||
{items.map((item) => (
|
||||
<ReimbursementCard
|
||||
canDeleteDraft={canDeleteExpense && item.status === "draft"}
|
||||
currentMobileSource={currentMobileSource}
|
||||
isDeleting={deleteMutation.isPending}
|
||||
item={item}
|
||||
key={item.id}
|
||||
|
||||
在新工单中引用
屏蔽一个用户