diff --git a/src/config/Pages.ts b/src/config/Pages.ts index 2cb1106..d7dce85 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -188,6 +188,7 @@ export const Pages = { detailUser: "/orders/detail-user/", detailSeller: "/orders/detail-seller/", return: "/orders/returns", + returnDetails: "/orders/return-details/", }, shipment: { list: "/shipment/list", diff --git a/src/pages/orders/ReturnDetails.tsx b/src/pages/orders/ReturnDetails.tsx new file mode 100644 index 0000000..6d65fca --- /dev/null +++ b/src/pages/orders/ReturnDetails.tsx @@ -0,0 +1,319 @@ +import { type FC, useState } from 'react' +import { useParams, useNavigate } from 'react-router-dom' +import { useGetReturnDetails, useApproveReturn, useRejectReturn } from './hooks/useOrderData' +import PageTitle from '@/components/PageTitle' +import PageLoading from '@/components/PageLoading' +import { formatPrice } from '@/helpers/func' +import StatusWithText from '@/components/StatusWithText' +import { Calendar, Money3, Box, Location, CloseCircle, TickCircle, Shop } from 'iconsax-react' +import Button from '@/components/Button' +import { useQueryClient } from '@tanstack/react-query' +import ModalConfrim from '@/components/ModalConfrim' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/helpers/utils' + +const ReturnDetails: FC = () => { + const { id } = useParams() + const navigate = useNavigate() + const queryClient = useQueryClient() + const { data, isLoading, error } = useGetReturnDetails(id!) + const approveReturn = useApproveReturn() + const rejectReturn = useRejectReturn() + + const [showApproveModal, setShowApproveModal] = useState(false) + const [showRejectModal, setShowRejectModal] = useState(false) + + const handleApprove = async () => { + try { + await approveReturn.mutateAsync(id!) + queryClient.invalidateQueries({ queryKey: ['return-details', id] }) + queryClient.invalidateQueries({ queryKey: ['order-returns'] }) + setShowApproveModal(false) + navigate('/orders/returns') + } catch (error) { + console.error(error) + } + } + + const handleReject = async () => { + await rejectReturn.mutateAsync(id!, { + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['return-details', id] }) + queryClient.invalidateQueries({ queryKey: ['order-returns'] }) + setShowRejectModal(false) + navigate('/orders/returns') + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } + + if (isLoading) { + return + } + + if (error) { + return ( +
+
+
خطا در بارگذاری داده‌ها
+
لطفا دوباره تلاش کنید
+
+
+ ) + } + + if (!data?.results?.returnOrder) { + return ( +
+
+
مرجوعی یافت نشد
+
+
+ ) + } + + const returnOrder = data.results.returnOrder + + const getStatusVariant = (status: string): 'success' | 'error' | 'warning' => { + switch (status) { + case 'Approved': + return 'success' + case 'Rejected': + return 'error' + case 'Pending': + return 'warning' + default: + return 'warning' + } + } + + const getStatusText = (status: string): string => { + switch (status) { + case 'Approved': + return 'تایید شده' + case 'Rejected': + return 'رد شده' + case 'Pending': + return 'در انتظار' + default: + return status + } + } + + return ( +
+ + + {/* هدر */} +
+
+

جزئیات مرجوعی

+ +
+
+ شماره سفارش: #{returnOrder.order._id} +
+
+ + {/* دکمه‌های عملیات */} + {data?.results?.returnOrder?.status === 'Pending' && ( +
+ + +
+ )} + + {/* کارت‌های اطلاعاتی */} +
+ {/* اطلاعات فروشگاه */} +
+
+
+ +
+

فروشگاه

+
+
+
{returnOrder.shopName}
+
+ +
+ {returnOrder.createdAt} +
+
+
+
+ + {/* مبلغ کل */} +
+
+
+ +
+

مبلغ مرجوعی

+
+
+
+ {formatPrice(returnOrder.total_price)} تومان +
+
+
+ + {/* آدرس */} +
+
+
+ +
+

آدرس

+
+
+
{returnOrder.order.shipmentAddress.address}
+
+
استان: {returnOrder.order.shipmentAddress.province}
+
شهر: {returnOrder.order.shipmentAddress.city}
+
پلاک: {returnOrder.order.shipmentAddress.plaque}
+
کد پستی: {returnOrder.order.shipmentAddress.postalCode}
+
تلفن: {returnOrder.order.shipmentAddress.phone}
+
+
+
+
+ + {/* آیتم‌های مرجوعی */} +
+
+
+ +
+

آیتم‌های مرجوعی

+
+ +
+ {returnOrder.returnOrderItems.map((item, index) => ( +
+ {/* هدر با وضعیت */} +
+

محصول مرجوعی

+ +
+ + {/* اطلاعات محصول با تصویر */} +
+
+ {/* تصویر محصول */} +
+ {item.shipmentItem.product.title_fa} +
+ + {/* جزئیات محصول */} +
+

{item.shipmentItem.product.title_fa}

+
+
+ تعداد مرجوعی: {item.quantity} +
+
+ قیمت واحد: {formatPrice(item.shipmentItem.selling_price)} تومان +
+
+ مبلغ کل: {formatPrice(item.quantity * item.shipmentItem.selling_price)} تومان +
+ {item.shipmentItem.discount_percent > 0 && ( +
+ تخفیف: {item.shipmentItem.discount_percent}% +
+ )} +
+ قیمت خرده‌فروشی: {formatPrice(item.shipmentItem.retail_price)} تومان +
+
+ هزینه ارسال: {formatPrice(item.shipmentItem.shipmentCost)} تومان +
+ {item.shipmentItem.isFreeShip && ( +
+ ارسال رایگان +
+ )} +
+
+
+
+ + {/* دلیل مرجوعی */} +
+
دلیل مرجوعی:
+
{item.reason.title}
+ {item.reason.is_mandatory_picture && ( +
+ * نیازمند تصویر +
+ )} + {item.reason.fineRule && ( +
+ جریمه: {item.reason.fineRule.title} ({item.reason.fineRule.fine_percentage}%) +
+ )} +
+ + {/* توضیحات کاربر */} + {item.comment && ( +
+
توضیحات کاربر:
+
{item.comment}
+
+ )} +
+ ))} +
+
+ + {/* مودال‌ها */} + setShowApproveModal(false)} + onConfrim={handleApprove} + label="آیا از تایید این مرجوعی اطمینان دارید؟" + isLoading={approveReturn.isPending} + /> + + setShowRejectModal(false)} + onConfrim={handleReject} + label="آیا از رد این مرجوعی اطمینان دارید؟" + isLoading={rejectReturn.isPending} + /> +
+ ) +} + +export default ReturnDetails \ No newline at end of file diff --git a/src/pages/orders/Returns.tsx b/src/pages/orders/Returns.tsx index a73e674..4071f6d 100644 --- a/src/pages/orders/Returns.tsx +++ b/src/pages/orders/Returns.tsx @@ -10,6 +10,8 @@ import { formatDateShort, formatPrice } from '@/helpers/func' import { type ReturnOrderType } from '@/pages/orders/types/ReturnTypes' import { Eye } from 'iconsax-react' import ReturnItemsModal from '@/pages/orders/components/ReturnItemsModal' +import { Link } from 'react-router-dom' +import { Pages } from '@/config/Pages' const Returns: FC = () => { const [currentPage, setCurrentPage] = useState(1) @@ -93,13 +95,10 @@ const Returns: FC = () => { - + )) diff --git a/src/pages/orders/hooks/useOrderData.ts b/src/pages/orders/hooks/useOrderData.ts index 6881eb9..267ceef 100644 --- a/src/pages/orders/hooks/useOrderData.ts +++ b/src/pages/orders/hooks/useOrderData.ts @@ -84,3 +84,23 @@ export const useAddTrackCode = () => { mutationFn: api.addTrackCode, }); }; + +export const useGetReturnDetails = (id: string) => { + return useQuery({ + queryKey: ["return-details", id], + queryFn: () => api.getReturnDetails(id), + enabled: !!id, + }); +}; + +export const useApproveReturn = () => { + return useMutation({ + mutationFn: api.approveReturn, + }); +}; + +export const useRejectReturn = () => { + return useMutation({ + mutationFn: api.rejectReturn, + }); +}; diff --git a/src/pages/orders/service/OrderService.ts b/src/pages/orders/service/OrderService.ts index 174adfb..fcbe0e7 100644 --- a/src/pages/orders/service/OrderService.ts +++ b/src/pages/orders/service/OrderService.ts @@ -6,6 +6,7 @@ import type { CreateReturnReasonType, NativeOrdersResponseType, OrderDetailResponseType, + ReturnOrderDetailResponseType, ReturnReasonsResponseType, } from "../types/Types"; @@ -86,3 +87,20 @@ export const addTrackCode = async (params: AddTrackCodeType) => { ); return data; }; + +export const getReturnDetails = async ( + id: string +): Promise => { + const { data } = await axios.get(`/admin/orders/returns/${id}`); + return data; +}; + +export const approveReturn = async (id: string) => { + const { data } = await axios.post(`/admin/orders/returns/${id}/approve`); + return data; +}; + +export const rejectReturn = async (id: string) => { + const { data } = await axios.post(`/admin/orders/returns/${id}/reject`); + return data; +}; diff --git a/src/pages/orders/types/Types.ts b/src/pages/orders/types/Types.ts index 5075aef..5b2a3b6 100644 --- a/src/pages/orders/types/Types.ts +++ b/src/pages/orders/types/Types.ts @@ -337,3 +337,129 @@ export type CreateReturnReasonType = { is_mandatory_picture: boolean; fineRule: string; }; + +// تایپ‌های مربوط به جزئیات مرجوعی +export type ReturnOrderDetailShipmentItemType = { + _id: string; + product: number; + variant: string; + quantity: number; + cancelled_quantity: number; + returned_quantity: number; + selling_price: number; + retail_price: number; + discount_percent: number; + shipmentCost: number; + isFreeShip: boolean; +}; + +export type ReturnOrderDetailOrderItemType = { + _id: string; + order: number; + shop: string; + shipmentItems: ReturnOrderDetailShipmentItemType[]; + shipper: number; + totalSellingPrice: number; + totalRetailPrice: number; + totalShipmentCost: number; + totalCouponDiscount: number; + totalPaymentPrice: number; + itemsCount: number; + trackCode: string | null; + postingDate: string | null; + postingReceipt: string | null; + status: string; + createdAt: string; + updatedAt: string; + __v: number; +}; + +export type ReturnOrderDetailProductType = { + _id: number; + title_fa: string; + title_en: string; + seoTitle: string; + seoDescription: string | null; + model: string; + source: string; + description: string; + metaDescription: string; + tags: string[]; + advantages: string[]; + disAdvantages: string[]; + totalRate: number; + category: string; + shop: string; + brand: string; + status: string; + adminComments: string | null; + step: number; + isFake: boolean; + variants: string[]; + voice: string | null; + deleted: boolean; + specifications: unknown[]; + createdAt: string; + updatedAt: string; + __v: number; + imagesUrl: ImagesUrlType; + rate: number; +}; + +export type ReturnOrderDetailReturnItemShipmentItemType = { + _id: string; + product: ReturnOrderDetailProductType; + variant: string; + quantity: number; + cancelled_quantity: number; + returned_quantity: number; + selling_price: number; + retail_price: number; + discount_percent: number; + shipmentCost: number; + isFreeShip: boolean; +}; + +export type ReturnOrderDetailReturnItemType = { + _id: string; + returnOrderId: string; + orderItem: ReturnOrderDetailOrderItemType; + shipmentItem: ReturnOrderDetailReturnItemShipmentItemType; + quantity: number; + comment: string; + reason: ReturnReasonType; + status: string; + createdAt: string; + updatedAt: string; + __v: number; +}; + +export type ReturnOrderDetailOrderType = { + _id: number; + user: string; + payment: string; + shipmentAddress: ShipmentAddressType; + orderStatus: string; + createdAt: string; + updatedAt: string; + __v: number; +}; + +export type ReturnOrderDetailType = { + _id: string; + order: ReturnOrderDetailOrderType; + status: string; + total_price: number; + shopName: string; + createdAt: string; + updatedAt: string; + returnOrderItems: ReturnOrderDetailReturnItemType[]; +}; + +export type ReturnOrderDetailResponseType = { + status: number; + success: boolean; + results: { + returnOrder: ReturnOrderDetailType; + }; +}; diff --git a/src/router/Main.tsx b/src/router/Main.tsx index 3a777c4..de916ad 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -81,6 +81,7 @@ import TicketMessages from '@/pages/ticket/TicketMessages' import Reviews from '@/pages/products/Reviews' import Questions from '@/pages/products/Questions' import Returns from '@/pages/orders/Returns' +import ReturnDetails from '@/pages/orders/ReturnDetails' const MainRouter: FC = () => { const { hasSubMenu } = useSharedStore() @@ -199,6 +200,7 @@ const MainRouter: FC = () => { } /> } /> + } />