This commit is contained in:
@@ -1,17 +1,25 @@
|
||||
{
|
||||
"amount": "Amount",
|
||||
"couponDiscount": "Coupon Discount",
|
||||
"confirmRefund": "Confirm refund",
|
||||
"discount": "Discount Amount",
|
||||
"feeAmount": "Fee Amount",
|
||||
"method": "Payment Method",
|
||||
"orderNumber": "Order Number",
|
||||
"refund": "Refund",
|
||||
"refundConfirmDescription": "This refund will immediately invalidate the user's subscription and deduct the related agent commission.",
|
||||
"refundConfirmTitle": "Confirm refund",
|
||||
"refundConfirmWarning": "This action cannot be repeated after the order is refunded.",
|
||||
"refundSubmitting": "Refunding...",
|
||||
"refundSuccess": "Refund completed.",
|
||||
"status": {
|
||||
"0": "Status",
|
||||
"1": "Pending",
|
||||
"2": "Paid",
|
||||
"3": "Cancelled",
|
||||
"4": "Closed",
|
||||
"5": "Completed"
|
||||
"5": "Completed",
|
||||
"6": "Refunded"
|
||||
},
|
||||
"subscribe": "Subscribe",
|
||||
"subscribePrice": "Subscription Price",
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
{
|
||||
"amount": "金额",
|
||||
"couponDiscount": "优惠券折扣",
|
||||
"confirmRefund": "确认退费",
|
||||
"discount": "折扣金额",
|
||||
"feeAmount": "手续费",
|
||||
"method": "支付方式",
|
||||
"orderNumber": "订单编号",
|
||||
"refund": "退费",
|
||||
"refundConfirmDescription": "该退费操作会使用户订阅立即失效,并扣减关联代理佣金。",
|
||||
"refundConfirmTitle": "确认退费",
|
||||
"refundConfirmWarning": "订单退费后不可再次重复执行,请谨慎操作。",
|
||||
"refundSubmitting": "退费中...",
|
||||
"refundSuccess": "退费成功。",
|
||||
"status": {
|
||||
"0": "状态",
|
||||
"1": "待支付",
|
||||
"2": "已支付",
|
||||
"3": "已取消",
|
||||
"4": "已关闭",
|
||||
"5": "已完成"
|
||||
"5": "已完成",
|
||||
"6": "已退费"
|
||||
},
|
||||
"subscribe": "订阅",
|
||||
"subscribePrice": "订阅价格",
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useSearch } from "@tanstack/react-router";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@workspace/ui/components/alert-dialog";
|
||||
import { Badge } from "@workspace/ui/components/badge";
|
||||
import { Button } from "@workspace/ui/components/button";
|
||||
import {
|
||||
@@ -16,18 +28,27 @@ import { cn } from "@workspace/ui/lib/utils";
|
||||
import {
|
||||
activateOrder,
|
||||
getOrderList,
|
||||
refundOrder,
|
||||
updateOrderStatus,
|
||||
} from "@workspace/ui/services/admin/order";
|
||||
import { useRef } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { Display } from "@/components/display";
|
||||
import { useGlobalStore } from "@/stores/global";
|
||||
import { useSubscribe } from "@/stores/subscribe";
|
||||
import { formatDate } from "@/utils/common";
|
||||
import { UserDetail } from "../user/user-detail";
|
||||
|
||||
const REFUNDED_ORDER_STATUS = 6;
|
||||
|
||||
export default function Order() {
|
||||
const { t } = useTranslation("order");
|
||||
const sp = useSearch({ strict: false }) as Record<string, string | undefined>;
|
||||
const user = useGlobalStore((state) => state.user);
|
||||
const [confirmingOrderId, setConfirmingOrderId] = useState<number | null>(
|
||||
null
|
||||
);
|
||||
const initialFilters = {
|
||||
user_id: sp.user_id ? Number(sp.user_id) : undefined,
|
||||
search: sp.search || undefined,
|
||||
@@ -53,6 +74,11 @@ export default function Order() {
|
||||
label: t("status.5", "Completed"),
|
||||
className: "bg-green-500",
|
||||
},
|
||||
{
|
||||
value: REFUNDED_ORDER_STATUS,
|
||||
label: t("status.6", "Refunded"),
|
||||
className: "bg-red-500",
|
||||
},
|
||||
];
|
||||
|
||||
const typeOptions = [
|
||||
@@ -65,10 +91,101 @@ export default function Order() {
|
||||
const ref = useRef<ProTableActions>(null);
|
||||
|
||||
const { subscribes, getSubscribeName } = useSubscribe();
|
||||
const canRefundOrders = Boolean(user?.is_admin);
|
||||
|
||||
const refundMutation = useMutation({
|
||||
mutationFn: async (order: API.Order) => {
|
||||
await refundOrder({ id: order.id });
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success(t("refundSuccess", "Refund completed."));
|
||||
setConfirmingOrderId(null);
|
||||
ref.current?.refresh();
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Refund order failed", error);
|
||||
setConfirmingOrderId(null);
|
||||
},
|
||||
});
|
||||
|
||||
const isRefundedOrder = (order: API.Order) =>
|
||||
order.status === REFUNDED_ORDER_STATUS ||
|
||||
order.status_name === t("status.6", "Refunded") ||
|
||||
order.status_name?.toLowerCase() === "refunded";
|
||||
|
||||
const canRefundOrder = (order: API.Order) =>
|
||||
canRefundOrders && !isRefundedOrder(order);
|
||||
|
||||
return (
|
||||
<ProTable<API.Order, any>
|
||||
action={ref}
|
||||
actions={{
|
||||
render: (order) => {
|
||||
if (!canRefundOrder(order)) return [];
|
||||
|
||||
const isPending =
|
||||
refundMutation.isPending && confirmingOrderId === order.id;
|
||||
|
||||
return [
|
||||
<AlertDialog
|
||||
key={`refund-${order.id}`}
|
||||
onOpenChange={(open) => {
|
||||
setConfirmingOrderId(open ? order.id : null);
|
||||
}}
|
||||
open={confirmingOrderId === order.id}
|
||||
>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button disabled={isPending} size="sm" variant="destructive">
|
||||
{isPending
|
||||
? t("refundSubmitting", "Refunding...")
|
||||
: t("refund", "Refund")}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{t("refundConfirmTitle", "Confirm refund")}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription asChild>
|
||||
<div className="space-y-2">
|
||||
<p>
|
||||
{t(
|
||||
"refundConfirmDescription",
|
||||
"This refund will immediately invalidate the user's subscription and deduct the related agent commission."
|
||||
)}
|
||||
</p>
|
||||
<p className="font-medium text-foreground">
|
||||
{t(
|
||||
"refundConfirmWarning",
|
||||
"This action cannot be repeated after the order is refunded."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isPending}>
|
||||
{t("cancel", "Cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
disabled={isPending}
|
||||
onClick={async (event) => {
|
||||
event.preventDefault();
|
||||
if (isPending) return;
|
||||
await refundMutation.mutateAsync(order);
|
||||
}}
|
||||
>
|
||||
{isPending
|
||||
? t("refundSubmitting", "Refunding...")
|
||||
: t("confirmRefund", "Confirm refund")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>,
|
||||
];
|
||||
},
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "order_no",
|
||||
@@ -253,8 +370,13 @@ export default function Order() {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Badge>
|
||||
{option?.label || t(`status.${row.getValue("status")}`)}
|
||||
<Badge
|
||||
variant={isRefundedOrder(order) ? "destructive" : "default"}
|
||||
>
|
||||
{option?.label ||
|
||||
(order.status_name
|
||||
? t(`status.${row.getValue("status")}`, order.status_name)
|
||||
: t(`status.${row.getValue("status")}`))}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user