942 lines
32 KiB
TypeScript
942 lines
32 KiB
TypeScript
import { LoaderFunctionArgs, type MetaFunction } from "@remix-run/node";
|
||
import { useLoaderData, useNavigate, useParams } from "@remix-run/react";
|
||
import {
|
||
useGetSellerOrderDetail,
|
||
useUpdateSellerOrder,
|
||
} from "../requestHandler/use-order-hooks";
|
||
import { useServiceGetCouriers } from "../requestHandler/use-courier-hooks";
|
||
import { formatDate, formatPrice, formatTime, safeGoBack } from "../utils/helpers";
|
||
import {
|
||
MapPin,
|
||
Wallet,
|
||
User,
|
||
PhoneCall,
|
||
Mail,
|
||
Truck,
|
||
AlertTriangle,
|
||
X,
|
||
Edit,
|
||
Package,
|
||
Signpost,
|
||
NotebookPen
|
||
} from "lucide-react";
|
||
import HeaderWithSupport from "../components/HeaderWithSupport";
|
||
import { Button } from "../components/ui/button";
|
||
import {
|
||
Drawer,
|
||
DrawerContent,
|
||
DrawerHeader,
|
||
DrawerTitle,
|
||
DrawerTrigger,
|
||
DrawerFooter,
|
||
} from "../components/ui/drawer";
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogFooter,
|
||
DialogOverlay,
|
||
} from "../components/ui/dialog";
|
||
import { useState, useEffect } from "react";
|
||
import {
|
||
SellerOrderListStatusEnum,
|
||
SellerOrderUpdateStatusEnum,
|
||
SellerOrderUpdateCancelReasonEnum,
|
||
} from "../../src/api/types";
|
||
import { RadioGroup, RadioGroupItem } from "~/components/ui/radio-group";
|
||
import AppInput from "~/components/AppInput";
|
||
import AppTextArea from "~/components/AppTextArea";
|
||
import { toast } from "~/hooks/use-toast";
|
||
import { useStoreOwnership } from "../hooks/useStoreOwnership";
|
||
|
||
export const meta: MetaFunction = () => {
|
||
return [
|
||
{ title: "جزئیات سفارش - ویترون" },
|
||
{
|
||
name: "description",
|
||
content:
|
||
"مشاهده جزئیات کامل سفارش دریافتی در فروشگاه شما. مدیریت وضعیت سفارش، کد رهگیری و اطلاعات مشتری.",
|
||
},
|
||
{
|
||
name: "keywords",
|
||
content: "جزئیات سفارش، مدیریت سفارش، کد رهگیری، فروشگاه، ویترون",
|
||
},
|
||
{ name: "robots", content: "noindex, follow" },
|
||
{ property: "og:title", content: "جزئیات سفارش - ویترون" },
|
||
{
|
||
property: "og:description",
|
||
content:
|
||
"مشاهده جزئیات کامل سفارش دریافتی در فروشگاه شما. مدیریت وضعیت سفارش، کد رهگیری و اطلاعات مشتری.",
|
||
},
|
||
{ property: "og:type", content: "website" },
|
||
{ name: "twitter:card", content: "summary" },
|
||
{ name: "twitter:title", content: "جزئیات سفارش - ویترون" },
|
||
{
|
||
name: "twitter:description",
|
||
content:
|
||
"مشاهده جزئیات کامل سفارش دریافتی در فروشگاه شما. مدیریت وضعیت سفارش، کد رهگیری و اطلاعات مشتری.",
|
||
},
|
||
];
|
||
};
|
||
|
||
export async function loader({ params }: LoaderFunctionArgs) {
|
||
const { storeId, orderId } = params;
|
||
|
||
if (!storeId) {
|
||
throw new Response("Store ID is required", { status: 400 });
|
||
}
|
||
|
||
if (!orderId) {
|
||
throw new Response("Order ID is required", { status: 400 });
|
||
}
|
||
|
||
return { storeId, orderId };
|
||
}
|
||
|
||
export default function OrderDetail() {
|
||
const navigate = useNavigate();
|
||
const { orderId, storeId } = useLoaderData<typeof loader>();
|
||
const { data: order, isLoading, error } = useGetSellerOrderDetail(orderId);
|
||
const { data: sellerCouriers } = useServiceGetCouriers(storeId);
|
||
|
||
// Check store ownership - this will redirect if user doesn't own the store
|
||
const { isLoading: isOwnershipLoading } = useStoreOwnership(storeId);
|
||
|
||
// State for drawer and modal
|
||
const [isAcceptDrawerOpen, setIsAcceptDrawerOpen] = useState(false);
|
||
const [isCancelModalOpen, setIsCancelModalOpen] = useState(false);
|
||
const [isTrackingDrawerOpen, setIsTrackingDrawerOpen] = useState(false);
|
||
const [isEditTrackingDrawerOpen, setIsEditTrackingDrawerOpen] =
|
||
useState(false);
|
||
const [isReactivateModalOpen, setIsReactivateModalOpen] = useState(false);
|
||
const [isUpdating, setIsUpdating] = useState(false);
|
||
|
||
const handleBack = () => {
|
||
safeGoBack(navigate);
|
||
};
|
||
|
||
// Resolve courier name from selected seller courier id
|
||
const selectedSellerCourierId = order?.shippingInfo?.sellerCourier;
|
||
const selectedCourier =
|
||
sellerCouriers?.find(
|
||
(c: any) =>
|
||
c?.id === selectedSellerCourierId ||
|
||
c?.courierId === selectedSellerCourierId ||
|
||
c?.courier?.id === selectedSellerCourierId
|
||
) || undefined;
|
||
const courierName =
|
||
selectedCourier?.courierName ||
|
||
"-";
|
||
|
||
// Show loading while checking ownership
|
||
if (isOwnershipLoading) {
|
||
return (
|
||
<div className="min-h-[500px] bg-WHITE">
|
||
<HeaderWithSupport title="جزئیات سفارش" onBack={handleBack} />
|
||
<div className="flex justify-center items-center p-8">
|
||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className="min-h-[500px] bg-WHITE">
|
||
<HeaderWithSupport title="جزئیات سفارش" onBack={handleBack} />
|
||
<div className="flex justify-center items-center p-8">
|
||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (error || !order) {
|
||
return (
|
||
<div className="min-h-[500px] bg-WHITE">
|
||
<HeaderWithSupport title="جزئیات سفارش" onBack={handleBack} />
|
||
<div className="p-4">
|
||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||
<p className="text-red-700 text-center">
|
||
خطا در بارگذاری اطلاعات سفارش. لطفا دوباره تلاش کنید.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
return (
|
||
<>
|
||
<div className="bg-WHITE">
|
||
<HeaderWithSupport title="سفارش جدید" onBack={handleBack} />
|
||
<div className="w-full flex flex-col gap-2">
|
||
<div className="flex w-full items-center justify-around p-4">
|
||
<p className="text-R12 text-GRAY">
|
||
{order.createdAt && formatDate(order.createdAt)} -{" "}
|
||
{order.createdAt && formatTime(order.createdAt)}
|
||
</p>
|
||
<p className="text-R12 text-GRAY">
|
||
سفارش شماره {order.invoice?.invoiceNumber}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
{/* Order Items */}
|
||
{order.items && order.items.length > 0 && (
|
||
<div className="bg-WHITE p-4">
|
||
<div className="space-y-3">
|
||
{order.items.map((item, index) => {
|
||
console.log("item", item);
|
||
return (
|
||
<div
|
||
key={index}
|
||
className="flex flex-col items-center w-full gap-3"
|
||
>
|
||
{/* Product Info */}
|
||
<div className="w-full flex items-center justify-between line-clamp-1">
|
||
<div>
|
||
{index + 1}- {item.productTitle || "محصول"}(
|
||
{formatPrice(item.totalPrice)} تومان)
|
||
</div>
|
||
<div
|
||
onClick={() => {
|
||
navigate(`/product/${item.productId}`);
|
||
}}
|
||
role="button"
|
||
tabIndex={0}
|
||
onKeyDown={(e) => {
|
||
if (e.key === "Enter") {
|
||
navigate(`/product/${item.productId}`);
|
||
}
|
||
}}
|
||
className="text-R10 text-BLUE cursor-pointer shrink-0"
|
||
>
|
||
مشاهده محصول
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center w-full justify-between text-R12">
|
||
<div className="flex gap-1 items-center justify-center">
|
||
<span>تعداد:</span>
|
||
<div className="text-R12 font-bold px-2 py-1 bg-WHITE3 rounded-md border">
|
||
{item.quantity}
|
||
</div>
|
||
</div>
|
||
<div className="flex gap-1 items-center justify-center">
|
||
{item.variant?.color?.value !== "UNSELECTED" && (
|
||
<>
|
||
<span>رنگ:</span>
|
||
<div
|
||
className="h-10 w-10 rounded-md shadow-md"
|
||
style={{
|
||
backgroundColor:
|
||
item.variant?.color?.value !== "UNSELECTED"
|
||
? item.variant?.color?.info.detail
|
||
: "",
|
||
}}
|
||
/>
|
||
</>
|
||
)}
|
||
</div>
|
||
<div className="flex gap-1 items-center justify-center">
|
||
{item.variant?.size?.value !== "UNSELECTED" && (
|
||
<>
|
||
<span>سایز:</span>
|
||
<div className="h-10 w-10 rounded-md shadow-md bg-BLACK text-WHITE text-R12 items-center justify-center flex">
|
||
{item.variant?.size?.value !== "UNSELECTED"
|
||
? item.variant?.size?.info.persian
|
||
: ""}
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
)}
|
||
<div className="flex flex-col gap-2 px-4 py-2">
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<Wallet size={20} />
|
||
<p className="text-B12 font-bold">مبلغ فاکتور:</p>
|
||
<p className="text-R12">
|
||
{formatPrice(order?.totalPrice || "0")} تومان
|
||
</p>
|
||
</div>
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<User size={20} />
|
||
<p className="text-B12 font-bold">نام گیرنده:</p>
|
||
<p className="text-R12">{order.shippingInfo?.receiverName}</p>
|
||
</div>
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<PhoneCall size={20} />
|
||
<p className="text-B12 font-bold">شمارهی گیرنده:</p>
|
||
<p className="text-R12">{order.shippingInfo?.receiverPhone}</p>
|
||
</div>
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<MapPin size={20} />
|
||
<p className="text-B12 font-bold">آدرس:</p>
|
||
<p className="text-R12">{order.shippingInfo?.mainAddress}</p>
|
||
</div>
|
||
{order.shippingInfo?.entryReference ? (
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<Signpost size={20} />
|
||
<p className="text-B12 font-bold">پلاک:</p>
|
||
<p className="text-R12">{order.shippingInfo.entryReference}</p>
|
||
</div>
|
||
) : null}
|
||
{order.shippingInfo?.details ? (
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<NotebookPen size={20} />
|
||
<p className="text-B12 font-bold">توضیحات:</p>
|
||
<p className="text-R12">{order.shippingInfo.details}</p>
|
||
</div>
|
||
) : null}
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<Mail size={20} />
|
||
<p className="text-B12 font-bold">کد پستی:</p>
|
||
<p className="text-R12">{order.shippingInfo?.postalCode}</p>
|
||
</div>
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<Truck size={20} />
|
||
<p className="text-B12 font-bold">شیوه ارسال:</p>
|
||
<p className="text-R12">{courierName}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Action Buttons */}
|
||
{order.status === SellerOrderListStatusEnum.Pending && (
|
||
<div className="max-w-md mx-auto fixed bottom-[60px] left-0 right-0 bg-WHITE border-t p-4 flex gap-3">
|
||
<Button
|
||
variant="dark"
|
||
size="xl"
|
||
className="w-full text-WHITE bg-VITROWN_BLUE text-white"
|
||
disabled={isUpdating}
|
||
onClick={() => setIsAcceptDrawerOpen(true)}
|
||
>
|
||
<p className="text-R14">پذیرش سفارش</p>
|
||
</Button>
|
||
<Button
|
||
variant="primary"
|
||
size="xl"
|
||
className="w-full"
|
||
disabled={isUpdating}
|
||
onClick={() => setIsCancelModalOpen(true)}
|
||
>
|
||
<p className="text-R14">رد سفارش</p>
|
||
</Button>
|
||
</div>
|
||
)}
|
||
|
||
{order.status === SellerOrderListStatusEnum.Confirmed && (
|
||
<div className="max-w-md mx-auto fixed bottom-[60px] left-0 right-0 bg-WHITE border-t p-4 flex gap-3">
|
||
<Button
|
||
variant="dark"
|
||
size="xl"
|
||
className="w-full text-WHITE bg-VITROWN_BLUE text-white"
|
||
disabled={isUpdating}
|
||
onClick={() => setIsTrackingDrawerOpen(true)}
|
||
>
|
||
<Package size={16} className="ml-2" />
|
||
<p className="text-R14">افزودن کد رهگیری</p>
|
||
</Button>
|
||
</div>
|
||
)}
|
||
|
||
{order.status === SellerOrderListStatusEnum.Shipped && (
|
||
<div className="max-w-md mx-auto fixed bottom-[60px] left-0 right-0 bg-WHITE border-t p-4 flex gap-3">
|
||
<Button
|
||
variant="dark"
|
||
size="xl"
|
||
className="w-full text-WHITE bg-VITROWN_BLUE text-white"
|
||
disabled={isUpdating}
|
||
onClick={() => setIsEditTrackingDrawerOpen(true)}
|
||
>
|
||
<Edit size={16} className="ml-2" />
|
||
<p className="text-R14">ویرایش کد رهگیری</p>
|
||
</Button>
|
||
</div>
|
||
)}
|
||
|
||
{order.status === SellerOrderListStatusEnum.Cancelled && (
|
||
<div className="max-w-md mx-auto bg-WHITE border-t p-4 flex gap-3">
|
||
<p className="text-R12 text-GRAY">این سفارش لغو شده است</p>
|
||
</div>
|
||
)}
|
||
|
||
{order.status === SellerOrderListStatusEnum.Delivered && (
|
||
<div className="fixed bottom-[60px] left-0 right-0 bg-WHITE border-t p-4">
|
||
<div className="bg-green-50 border border-green-200 rounded-lg p-4 text-center">
|
||
<Package size={24} className="mx-auto mb-2 text-green-600" />
|
||
<p className="text-green-700 font-medium">
|
||
محصول با موفقیت به دست مشتری رسیده است
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<AcceptOrderDrawer
|
||
isAcceptDrawerOpen={isAcceptDrawerOpen}
|
||
setIsAcceptDrawerOpen={setIsAcceptDrawerOpen}
|
||
orderId={orderId}
|
||
setIsUpdating={setIsUpdating}
|
||
/>
|
||
<CancelOrderModal
|
||
isCancelModalOpen={isCancelModalOpen}
|
||
setIsCancelModalOpen={setIsCancelModalOpen}
|
||
orderId={orderId}
|
||
setIsUpdating={setIsUpdating}
|
||
/>
|
||
<AddTrackingDrawer
|
||
isTrackingDrawerOpen={isTrackingDrawerOpen}
|
||
setIsTrackingDrawerOpen={setIsTrackingDrawerOpen}
|
||
orderId={orderId}
|
||
setIsUpdating={setIsUpdating}
|
||
/>
|
||
<EditTrackingDrawer
|
||
isEditTrackingDrawerOpen={isEditTrackingDrawerOpen}
|
||
setIsEditTrackingDrawerOpen={setIsEditTrackingDrawerOpen}
|
||
orderId={orderId}
|
||
initialTrackingCode={order?.shippingInfo?.trackingCode || ""}
|
||
setIsUpdating={setIsUpdating}
|
||
/>
|
||
<ReactivateOrderModal
|
||
isReactivateModalOpen={isReactivateModalOpen}
|
||
setIsReactivateModalOpen={setIsReactivateModalOpen}
|
||
orderId={orderId}
|
||
setIsUpdating={setIsUpdating}
|
||
/>
|
||
</>
|
||
);
|
||
}
|
||
const AcceptOrderDrawer = ({
|
||
isAcceptDrawerOpen,
|
||
setIsAcceptDrawerOpen,
|
||
orderId,
|
||
setIsUpdating,
|
||
}: {
|
||
isAcceptDrawerOpen: boolean;
|
||
setIsAcceptDrawerOpen: (open: boolean) => void;
|
||
orderId: string;
|
||
setIsUpdating: (isUpdating: boolean) => void;
|
||
}) => {
|
||
const [trackingCode, setTrackingCode] = useState("");
|
||
const navigate = useNavigate();
|
||
const { storeId } = useParams();
|
||
const { mutate: updateOrder, isPending: isUpdating } = useUpdateSellerOrder();
|
||
setIsUpdating(isUpdating);
|
||
const handleAcceptOrder = () => {
|
||
if (!trackingCode.trim()) return;
|
||
updateOrder(
|
||
{
|
||
orderId,
|
||
data: {
|
||
status: SellerOrderUpdateStatusEnum.Shipped,
|
||
trackingCode: trackingCode.trim(),
|
||
},
|
||
},
|
||
{
|
||
onSuccess: () => {
|
||
setIsAcceptDrawerOpen(false);
|
||
setTrackingCode("");
|
||
toast({
|
||
title: "سفارش با موفقیت پذیرش شد",
|
||
description: "سفارش با موفقیت پذیرش شد",
|
||
});
|
||
navigate(`/store/${storeId}/orders`);
|
||
},
|
||
}
|
||
);
|
||
};
|
||
const handleAcceptOrderWithoutTrackingCode = () => {
|
||
updateOrder(
|
||
{ orderId, data: { status: SellerOrderUpdateStatusEnum.Confirmed } },
|
||
{
|
||
onSuccess: () => {
|
||
setIsAcceptDrawerOpen(false);
|
||
toast({
|
||
title: "سفارش با موفقیت پذیرش شد",
|
||
description: "سفارش با موفقیت پذیرش شد",
|
||
});
|
||
navigate(`/store/${storeId}/orders`);
|
||
},
|
||
}
|
||
);
|
||
};
|
||
|
||
return (
|
||
<Drawer open={isAcceptDrawerOpen} onOpenChange={setIsAcceptDrawerOpen}>
|
||
<DrawerTrigger asChild></DrawerTrigger>
|
||
<DrawerContent>
|
||
<DrawerHeader>
|
||
<DrawerTitle className="text-center">پذیرش سفارش</DrawerTitle>
|
||
<X
|
||
onClick={() => setIsAcceptDrawerOpen(false)}
|
||
className="absolute top-4 left-4 cursor-pointer"
|
||
/>
|
||
</DrawerHeader>
|
||
<div className="p-4 space-y-4">
|
||
<div>
|
||
<AppInput
|
||
inputTitle="کد رهگیری محصول ارسال شده"
|
||
inputPlaceholder="کد رهگیری را وارد کنید"
|
||
inputValue={trackingCode}
|
||
inputOnChange={(e) => setTrackingCode(e.target.value)}
|
||
className="w-full"
|
||
inputDir={trackingCode === "" ? "rtl" : "ltr"}
|
||
inputTitleFontSize="text-R14"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<DrawerFooter>
|
||
<div className="flex gap-3">
|
||
<Button
|
||
onClick={handleAcceptOrder}
|
||
variant="dark"
|
||
size="xl"
|
||
disabled={!trackingCode.trim() || isUpdating}
|
||
className="flex-1"
|
||
>
|
||
{isUpdating ? "در حال ارسال..." : "ارسال"}
|
||
</Button>
|
||
<Button
|
||
variant="primary"
|
||
size="xl"
|
||
onClick={handleAcceptOrderWithoutTrackingCode}
|
||
className="flex-1"
|
||
>
|
||
بعدا ارسال میکنم
|
||
</Button>
|
||
</div>
|
||
</DrawerFooter>
|
||
</DrawerContent>
|
||
</Drawer>
|
||
);
|
||
};
|
||
|
||
const CancelOrderModal = ({
|
||
isCancelModalOpen,
|
||
setIsCancelModalOpen,
|
||
orderId,
|
||
setIsUpdating,
|
||
}: {
|
||
isCancelModalOpen: boolean;
|
||
setIsCancelModalOpen: (open: boolean) => void;
|
||
orderId: string;
|
||
setIsUpdating: (isUpdating: boolean) => void;
|
||
}) => {
|
||
const navigate = useNavigate();
|
||
const { storeId } = useParams();
|
||
const [cancelReason, setCancelReason] = useState<
|
||
SellerOrderUpdateCancelReasonEnum | ""
|
||
>("");
|
||
const [cancelDescription, setCancelDescription] = useState("");
|
||
const { mutate: updateOrder, isPending: isUpdating } = useUpdateSellerOrder();
|
||
setIsUpdating(isUpdating);
|
||
|
||
const handleCancelOrder = () => {
|
||
if (!cancelReason) return;
|
||
|
||
const data = {
|
||
status: SellerOrderUpdateStatusEnum.Cancelled,
|
||
cancelReason,
|
||
...(cancelReason === SellerOrderUpdateCancelReasonEnum.Other &&
|
||
cancelDescription.trim() && {
|
||
cancelDescription: cancelDescription.trim(),
|
||
}),
|
||
};
|
||
|
||
updateOrder(
|
||
{ orderId, data },
|
||
{
|
||
onSuccess: () => {
|
||
setIsCancelModalOpen(false);
|
||
setCancelReason("");
|
||
setCancelDescription("");
|
||
toast({
|
||
title: "سفارش با موفقیت لغو شد",
|
||
description: "سفارش با موفقیت لغو شد",
|
||
});
|
||
navigate(`/store/${storeId}/orders`);
|
||
},
|
||
}
|
||
);
|
||
};
|
||
|
||
return (
|
||
<Dialog open={isCancelModalOpen} onOpenChange={setIsCancelModalOpen}>
|
||
<DialogOverlay className="bg-BLACK/50" />
|
||
<DialogContent className="p-4 bg-WHITE overflow-y-auto rounded-[15px] w-[calc(100%-40px)]">
|
||
<DialogHeader className="text-center space-y-4">
|
||
<div className="flex justify-center">
|
||
<div className="p-2 bg-red-100 rounded-full">
|
||
<AlertTriangle className="h-6 w-6 text-red-600" />
|
||
</div>
|
||
</div>
|
||
<DialogTitle className="text-center">لغو سفارش</DialogTitle>
|
||
<p className="text-sm text-BLACK2">علت لغو کردن این سفارش چیست؟</p>
|
||
</DialogHeader>
|
||
<div className="space-y-4 py-4">
|
||
<RadioGroup
|
||
onValueChange={(value) =>
|
||
setCancelReason(value as SellerOrderUpdateCancelReasonEnum)
|
||
}
|
||
value={cancelReason}
|
||
className="flex flex-col gap-4 px-4"
|
||
dir="rtl"
|
||
>
|
||
<div className="flex items-center gap-1">
|
||
<RadioGroupItem
|
||
value={SellerOrderUpdateCancelReasonEnum.OutOfStock}
|
||
id="outOfStock"
|
||
className="size-5"
|
||
/>
|
||
<label htmlFor="outOfStock" className="flex flex-col gap-1">
|
||
<p className="text-R14">اتمام موجودی</p>
|
||
</label>
|
||
</div>
|
||
<div className="flex items-center gap-1">
|
||
<RadioGroupItem
|
||
value={SellerOrderUpdateCancelReasonEnum.OutOfServiceArea}
|
||
id="outOfServiceArea"
|
||
className="size-5"
|
||
/>
|
||
<label htmlFor="outOfServiceArea" className="flex flex-col gap-1">
|
||
<p className="text-R14">خارج از محدوده سرویس دهی</p>
|
||
</label>
|
||
</div>
|
||
<div className="flex items-center gap-1">
|
||
<RadioGroupItem
|
||
value={SellerOrderUpdateCancelReasonEnum.Other}
|
||
id="other"
|
||
className="size-5"
|
||
/>
|
||
<label htmlFor="other" className="flex flex-col gap-1">
|
||
<p className="text-R14">سایر دلایل</p>
|
||
</label>
|
||
</div>
|
||
</RadioGroup>
|
||
|
||
{cancelReason === SellerOrderUpdateCancelReasonEnum.Other && (
|
||
<div className="px-4">
|
||
<AppTextArea
|
||
inputTitle="توضیحات لغو سفارش"
|
||
inputPlaceholder="علت لغو سفارش را توضیح دهید..."
|
||
inputValue={cancelDescription}
|
||
inputOnChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||
setCancelDescription(e.target.value)
|
||
}
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<DialogFooter>
|
||
<div className="flex gap-3 w-full">
|
||
<Button
|
||
onClick={handleCancelOrder}
|
||
disabled={
|
||
!cancelReason ||
|
||
(cancelReason === SellerOrderUpdateCancelReasonEnum.Other &&
|
||
!cancelDescription.trim()) ||
|
||
isUpdating
|
||
}
|
||
variant="dark"
|
||
size="xl"
|
||
className="flex-1"
|
||
>
|
||
{isUpdating ? "در حال پردازش..." : "تایید"}
|
||
</Button>
|
||
<Button
|
||
variant="primary"
|
||
size="xl"
|
||
onClick={() => setIsCancelModalOpen(false)}
|
||
className="flex-1"
|
||
>
|
||
انصراف
|
||
</Button>
|
||
</div>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
};
|
||
|
||
const AddTrackingDrawer = ({
|
||
isTrackingDrawerOpen,
|
||
setIsTrackingDrawerOpen,
|
||
orderId,
|
||
setIsUpdating,
|
||
}: {
|
||
isTrackingDrawerOpen: boolean;
|
||
setIsTrackingDrawerOpen: (open: boolean) => void;
|
||
orderId: string;
|
||
setIsUpdating: (isUpdating: boolean) => void;
|
||
}) => {
|
||
const [trackingCode, setTrackingCode] = useState("");
|
||
const navigate = useNavigate();
|
||
const { storeId } = useParams();
|
||
const { mutate: updateOrder, isPending: isUpdating } = useUpdateSellerOrder();
|
||
setIsUpdating(isUpdating);
|
||
|
||
const handleAddTracking = () => {
|
||
if (!trackingCode.trim()) return;
|
||
updateOrder(
|
||
{
|
||
orderId,
|
||
data: {
|
||
status: SellerOrderUpdateStatusEnum.Shipped,
|
||
trackingCode: trackingCode.trim(),
|
||
},
|
||
},
|
||
{
|
||
onSuccess: () => {
|
||
setIsTrackingDrawerOpen(false);
|
||
setTrackingCode("");
|
||
toast({
|
||
title: "کد رهگیری با موفقیت اضافه شد",
|
||
description: "سفارش به حالت ارسال شده تغییر یافت",
|
||
});
|
||
navigate(`/store/${storeId}/orders`);
|
||
},
|
||
}
|
||
);
|
||
};
|
||
|
||
return (
|
||
<Drawer open={isTrackingDrawerOpen} onOpenChange={setIsTrackingDrawerOpen}>
|
||
<DrawerTrigger asChild></DrawerTrigger>
|
||
<DrawerContent>
|
||
<DrawerHeader>
|
||
<DrawerTitle className="text-center">افزودن کد رهگیری</DrawerTitle>
|
||
<X
|
||
onClick={() => setIsTrackingDrawerOpen(false)}
|
||
className="absolute top-4 left-4 cursor-pointer"
|
||
/>
|
||
</DrawerHeader>
|
||
<div className="p-4 space-y-4">
|
||
<div>
|
||
<AppInput
|
||
inputTitle="کد رهگیری محصول ارسال شده"
|
||
inputPlaceholder="کد رهگیری را وارد کنید"
|
||
inputValue={trackingCode}
|
||
inputOnChange={(e) => setTrackingCode(e.target.value)}
|
||
className="w-full"
|
||
inputDir={trackingCode === "" ? "rtl" : "ltr"}
|
||
inputTitleFontSize="text-R14"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<DrawerFooter>
|
||
<div className="flex gap-3">
|
||
<Button
|
||
onClick={handleAddTracking}
|
||
variant="dark"
|
||
size="xl"
|
||
disabled={!trackingCode.trim() || isUpdating}
|
||
className="flex-1"
|
||
>
|
||
{isUpdating ? "در حال ارسال..." : "ارسال"}
|
||
</Button>
|
||
<Button
|
||
variant="primary"
|
||
size="xl"
|
||
onClick={() => setIsTrackingDrawerOpen(false)}
|
||
className="flex-1"
|
||
>
|
||
انصراف
|
||
</Button>
|
||
</div>
|
||
</DrawerFooter>
|
||
</DrawerContent>
|
||
</Drawer>
|
||
);
|
||
};
|
||
|
||
const EditTrackingDrawer = ({
|
||
isEditTrackingDrawerOpen,
|
||
setIsEditTrackingDrawerOpen,
|
||
orderId,
|
||
initialTrackingCode,
|
||
setIsUpdating,
|
||
}: {
|
||
isEditTrackingDrawerOpen: boolean;
|
||
setIsEditTrackingDrawerOpen: (open: boolean) => void;
|
||
orderId: string;
|
||
initialTrackingCode: string;
|
||
setIsUpdating: (isUpdating: boolean) => void;
|
||
}) => {
|
||
const [trackingCode, setTrackingCode] = useState(initialTrackingCode);
|
||
|
||
// Update tracking code when modal opens or initial value changes
|
||
useEffect(() => {
|
||
if (isEditTrackingDrawerOpen) {
|
||
setTrackingCode(initialTrackingCode);
|
||
}
|
||
}, [isEditTrackingDrawerOpen, initialTrackingCode]);
|
||
const navigate = useNavigate();
|
||
const { storeId } = useParams();
|
||
const { mutate: updateOrder, isPending: isUpdating } = useUpdateSellerOrder();
|
||
setIsUpdating(isUpdating);
|
||
|
||
const handleEditTracking = () => {
|
||
if (!trackingCode.trim()) return;
|
||
updateOrder(
|
||
{
|
||
orderId,
|
||
data: {
|
||
trackingCode: trackingCode.trim(),
|
||
},
|
||
},
|
||
{
|
||
onSuccess: () => {
|
||
setIsEditTrackingDrawerOpen(false);
|
||
toast({
|
||
title: "کد رهگیری با موفقیت ویرایش شد",
|
||
description: "کد رهگیری سفارش بهروزرسانی شد",
|
||
});
|
||
navigate(`/store/${storeId}/orders`);
|
||
},
|
||
}
|
||
);
|
||
};
|
||
|
||
return (
|
||
<Drawer
|
||
open={isEditTrackingDrawerOpen}
|
||
onOpenChange={setIsEditTrackingDrawerOpen}
|
||
>
|
||
<DrawerTrigger asChild></DrawerTrigger>
|
||
<DrawerContent>
|
||
<DrawerHeader>
|
||
<DrawerTitle className="text-center">ویرایش کد رهگیری</DrawerTitle>
|
||
<X
|
||
onClick={() => setIsEditTrackingDrawerOpen(false)}
|
||
className="absolute top-4 left-4 cursor-pointer"
|
||
/>
|
||
</DrawerHeader>
|
||
<div className="p-4 space-y-4">
|
||
<div>
|
||
<AppInput
|
||
inputTitle="کد رهگیری محصول ارسال شده"
|
||
inputPlaceholder="کد رهگیری را وارد کنید"
|
||
inputValue={trackingCode}
|
||
inputOnChange={(e) => setTrackingCode(e.target.value)}
|
||
className="w-full"
|
||
inputDir={trackingCode === "" ? "rtl" : "ltr"}
|
||
inputTitleFontSize="text-R14"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<DrawerFooter>
|
||
<div className="flex gap-3">
|
||
<Button
|
||
onClick={handleEditTracking}
|
||
variant="dark"
|
||
size="xl"
|
||
disabled={!trackingCode.trim() || isUpdating}
|
||
className="flex-1"
|
||
>
|
||
{isUpdating ? "در حال ویرایش..." : "ویرایش"}
|
||
</Button>
|
||
<Button
|
||
variant="primary"
|
||
size="xl"
|
||
onClick={() => setIsEditTrackingDrawerOpen(false)}
|
||
className="flex-1"
|
||
>
|
||
انصراف
|
||
</Button>
|
||
</div>
|
||
</DrawerFooter>
|
||
</DrawerContent>
|
||
</Drawer>
|
||
);
|
||
};
|
||
|
||
const ReactivateOrderModal = ({
|
||
isReactivateModalOpen,
|
||
setIsReactivateModalOpen,
|
||
orderId,
|
||
setIsUpdating,
|
||
}: {
|
||
isReactivateModalOpen: boolean;
|
||
setIsReactivateModalOpen: (open: boolean) => void;
|
||
orderId: string;
|
||
setIsUpdating: (isUpdating: boolean) => void;
|
||
}) => {
|
||
const navigate = useNavigate();
|
||
const { storeId } = useParams();
|
||
const { mutate: updateOrder, isPending: isUpdating } = useUpdateSellerOrder();
|
||
setIsUpdating(isUpdating);
|
||
|
||
const handleReactivateOrder = () => {
|
||
updateOrder(
|
||
{
|
||
orderId,
|
||
data: {
|
||
status: SellerOrderUpdateStatusEnum.Confirmed,
|
||
},
|
||
},
|
||
{
|
||
onSuccess: () => {
|
||
setIsReactivateModalOpen(false);
|
||
toast({
|
||
title: "سفارش با موفقیت فعال شد",
|
||
description: "سفارش به حالت تایید شده تغییر یافت",
|
||
});
|
||
navigate(`/store/${storeId}/orders`);
|
||
},
|
||
}
|
||
);
|
||
};
|
||
|
||
return (
|
||
<Dialog
|
||
open={isReactivateModalOpen}
|
||
onOpenChange={setIsReactivateModalOpen}
|
||
>
|
||
<DialogOverlay className="bg-BLACK/50" />
|
||
<DialogContent className="p-4 bg-WHITE overflow-y-auto rounded-[15px] w-[calc(100%-40px)]">
|
||
<DialogHeader className="text-center space-y-4">
|
||
<div className="flex justify-center">
|
||
<div className="p-2 bg-green-100 rounded-full">
|
||
<Package className="h-6 w-6 text-green-600" />
|
||
</div>
|
||
</div>
|
||
<DialogTitle className="text-center">
|
||
فعال کردن مجدد سفارش
|
||
</DialogTitle>
|
||
<p className="text-sm text-BLACK2">
|
||
آیا از فعال کردن مجدد این سفارش اطمینان دارید؟
|
||
</p>
|
||
</DialogHeader>
|
||
<DialogFooter>
|
||
<div className="flex gap-3 w-full">
|
||
<Button
|
||
onClick={handleReactivateOrder}
|
||
disabled={isUpdating}
|
||
variant="dark"
|
||
size="xl"
|
||
className="flex-1"
|
||
>
|
||
{isUpdating ? "در حال پردازش..." : "تایید"}
|
||
</Button>
|
||
<Button
|
||
variant="primary"
|
||
size="xl"
|
||
onClick={() => setIsReactivateModalOpen(false)}
|
||
className="flex-1"
|
||
>
|
||
انصراف
|
||
</Button>
|
||
</div>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
};
|