From 24689bf932f85ad87fb9cecf3aaa51f3e18d9d97 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sat, 8 Nov 2025 09:25:37 +0330 Subject: [PATCH] cancel compelete --- src/config/Pages.ts | 1 + src/pages/orders/Cancels.tsx | 144 +++++++ .../orders/components/CancelItemsModal.tsx | 375 ++++++++++++++++++ .../orders/components/CompleteCancelModal.tsx | 137 +++++++ src/pages/orders/hooks/useOrderData.ts | 15 + src/pages/orders/service/OrderService.ts | 22 + src/pages/orders/types/Types.ts | 112 +++++- src/router/Main.tsx | 2 + src/shared/components/OrdersSubMenu.tsx | 12 +- 9 files changed, 816 insertions(+), 4 deletions(-) create mode 100644 src/pages/orders/Cancels.tsx create mode 100644 src/pages/orders/components/CancelItemsModal.tsx create mode 100644 src/pages/orders/components/CompleteCancelModal.tsx diff --git a/src/config/Pages.ts b/src/config/Pages.ts index d7dce85..3ba84e5 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -189,6 +189,7 @@ export const Pages = { detailSeller: "/orders/detail-seller/", return: "/orders/returns", returnDetails: "/orders/return-details/", + cancels: "/orders/cancels", }, shipment: { list: "/shipment/list", diff --git a/src/pages/orders/Cancels.tsx b/src/pages/orders/Cancels.tsx new file mode 100644 index 0000000..486a3e8 --- /dev/null +++ b/src/pages/orders/Cancels.tsx @@ -0,0 +1,144 @@ +import PageTitle from '@/components/PageTitle' +import { type FC, useState } from 'react' +import { useGetCancels } from './hooks/useOrderData' +import PageLoading from '@/components/PageLoading' +import Error from '@/components/Error' +import PaginationUi from '@/components/PaginationUi' +import Td from '@/components/Td' +import StatusWithText from '@/components/StatusWithText' +import { formatDateShort, formatPrice } from '@/helpers/func' +import { type CancelOrderType } from '@/pages/orders/types/Types' +import { Eye } from 'iconsax-react' +import CancelItemsModal from '@/pages/orders/components/CancelItemsModal' +import { type PagerType as ProductPagerType } from '@/pages/products/types/Types' + +const Cancels: FC = () => { + const [currentPage, setCurrentPage] = useState(1) + const [isModalOpen, setIsModalOpen] = useState(false) + const [selectedCancel, setSelectedCancel] = useState(null) + const { data, isLoading, error } = useGetCancels(currentPage) + + if (isLoading) { + return ( +
+ +
+ ) + } + + if (error) { + return ( +
+ +
+ ) + } + + const cancelsOrders = data?.results?.cancelsOrders || [] + const pager = data?.results?.pager + + const getStatusVariant = (status: string) => { + switch (status) { + case 'Approved': return 'success' + case 'Completed': return 'success' + case 'Rejected': return 'error' + case 'Pending': + default: return 'warning' + } + } + + const getStatusText = (status: string) => { + switch (status) { + case 'Approved': return 'تایید شده' + case 'Completed': return 'تکمیل شده' + case 'Rejected': return 'رد شده' + case 'Pending': return 'در انتظار' + default: return status + } + } + + const handleEyeClick = (cancel: CancelOrderType) => { + setSelectedCancel(cancel) + setIsModalOpen(true) + } + + return ( +
+ + +
+ + + + + + + {cancelsOrders.length === 0 ? ( + + + + ) : ( + cancelsOrders.map((cancel: CancelOrderType) => { + const totalQuantity = cancel?.cancelOrderItems?.quantity || 0 + const orderId = cancel?.order?._id || null + const productName = cancel?.cancelOrderItems?.shipmentItem?.product?.title_fa || '-' + const cancellerName = cancel?.cancellerDetails?.fullName || '-' + + return ( + + + + + ) + }) + )} + +
+ + + + + + + +
+ کنسلی یافت نشد +
+ + + + + + + + + +
+
+ + setCurrentPage(page)} + /> + + { setIsModalOpen(false); setSelectedCancel(null); }} + data={selectedCancel} + /> +
+ ) +} + +export default Cancels \ No newline at end of file diff --git a/src/pages/orders/components/CancelItemsModal.tsx b/src/pages/orders/components/CancelItemsModal.tsx new file mode 100644 index 0000000..5e478da --- /dev/null +++ b/src/pages/orders/components/CancelItemsModal.tsx @@ -0,0 +1,375 @@ +import { type FC, Fragment, useState } from 'react' +import DefaulModal from '@/components/DefaulModal' +import StatusWithText from '@/components/StatusWithText' +import { formatDateShort, formatPrice } from '@/helpers/func' +import { type CancelOrderType } from '@/pages/orders/types/Types' +import Button from '@/components/Button' +import { DocumentUpload } from 'iconsax-react' +import CompleteCancelModal from '@/pages/orders/components/CompleteCancelModal' + +type Props = { + open: boolean + close: () => void + data: CancelOrderType | null +} + +const getStatusVariant = (status: string) => { + switch (status) { + case 'Approved': return 'success' + case 'Completed': return 'success' + case 'Rejected': return 'error' + case 'Pending': + default: return 'warning' + } +} + +const getStatusText = (status: string) => { + switch (status) { + case 'Approved': return 'تایید شده' + case 'Completed': return 'تکمیل شده' + case 'Rejected': return 'رد شده' + case 'Pending': return 'در انتظار' + default: return status + } +} + +const CancelItemsModal: FC = ({ open, close, data }) => { + const cancelItem = data?.cancelOrderItems + const [isCompleteModalOpen, setIsCompleteModalOpen] = useState(false) + + return ( + + {!data ? ( +
داده‌ای برای نمایش وجود ندارد
+ ) : ( + + {data.status === 'Pending' && ( +
+ +
+ )} + + {/* اطلاعات سفارش */} +
+

اطلاعات سفارش

+
+
+
شماره سفارش
+
+ {data.order?._id ? `#${data.order._id}` : '-'} +
+
+
+
وضعیت سفارش
+
+ {data.order?.orderStatus || '-'} +
+
+
+
وضعیت کنسلی
+
+ +
+
+
+
مبلغ کل
+
{formatPrice(Number(data.total_price || 0))} تومان
+
+
+
تاریخ ثبت کنسلی
+
{formatDateShort(data.createdAt)}
+
+ {data.cancellDate && ( +
+
تاریخ تایید کنسلی
+
{formatDateShort(data.cancellDate)}
+
+ )} +
+
+ + {/* اطلاعات کنسل‌کننده */} + {data.cancellerDetails && ( +
+

اطلاعات کنسل‌کننده

+
+
+
نام
+
+ {data.cancellerDetails.fullName || '-'} +
+
+
+
شماره تماس
+
+ {data.cancellerDetails.phoneNumber || '-'} +
+
+
+
نوع کنسل‌کننده
+
+ {data.cancellerRef === 'User' ? 'کاربر' : data.cancellerRef || '-'} +
+
+
+
+ )} + + {/* آدرس ارسال */} + {data.order?.shipmentAddress && ( +
+

آدرس ارسال

+
+
+
شهر
+
+ {data.order.shipmentAddress.city || '-'} +
+
+
+
استان
+
+ {data.order.shipmentAddress.province || '-'} +
+
+
+
آدرس کامل
+
+ {data.order.shipmentAddress.address || '-'} +
+
+
+
کد پستی
+
+ {data.order.shipmentAddress.postalCode || '-'} +
+
+
+
تلفن
+
+ {data.order.shipmentAddress.phone || '-'} +
+
+
+
+ )} + + {/* اطلاعات محصول کنسل‌شده */} + {cancelItem && ( +
+

اطلاعات محصول کنسل‌شده

+ + {/* کارت محصول */} +
+
+ {cancelItem.shipmentItem?.product?.imagesUrl?.cover && ( + {cancelItem.shipmentItem.product.title_fa} + )} +
+

+ {cancelItem.shipmentItem?.product?.title_fa || '-'} +

+
+
+ قیمت فروش: + + {formatPrice(cancelItem.shipmentItem?.selling_price || 0)} تومان + +
+
+ قیمت خرده‌فروشی: + + {formatPrice(cancelItem.shipmentItem?.retail_price || 0)} تومان + +
+
+ هزینه ارسال: + + {cancelItem.shipmentItem?.isFreeShip + ? 'رایگان' + : `${formatPrice(cancelItem.shipmentItem?.shipmentCost || 0)} تومان`} + +
+
+ تعداد سفارش: + {cancelItem.shipmentItem?.quantity || 0} +
+
+ تعداد کنسل‌شده: + + {cancelItem.shipmentItem?.cancelled_quantity || 0} + +
+
+ تعداد مرجوع‌شده: + + {cancelItem.shipmentItem?.returned_quantity || 0} + +
+ {cancelItem.shipmentItem?.discount_percent !== undefined && cancelItem.shipmentItem?.discount_percent > 0 && ( +
+ درصد تخفیف: + + {cancelItem.shipmentItem.discount_percent}% + +
+ )} +
+
+
+
+ + {/* اطلاعات فروشگاه و وضعیت */} +
+ {cancelItem.orderItem?.shipmentItems?.product?.shop?.shopName && ( +
+
نام فروشگاه
+
+ {cancelItem.orderItem.shipmentItems.product.shop.shopName} +
+
+ )} + {cancelItem.orderItem?.shipmentItems?.product?.shop?.shopCode && ( +
+
کد فروشگاه
+
+ {cancelItem.orderItem.shipmentItems.product.shop.shopCode} +
+
+ )} + {cancelItem.orderItem?.status && ( +
+
وضعیت آیتم سفارش
+
+ {cancelItem.orderItem.status} +
+
+ )} +
+ + {/* دلیل و توضیحات کنسلی */} +
+
+
دلیل کنسلی
+
+ {cancelItem.reason?.title || '-'} +
+
+ {cancelItem.comment && ( +
+
توضیحات کاربر
+
+ {cancelItem.comment} +
+
+ )} + {data.cancellComment && ( +
+
توضیحات ادمین
+
+ {data.cancellComment} +
+
+ )} +
+
+ )} + + {/* اطلاعات ارسال */} + {cancelItem?.orderItem && ( +
+

اطلاعات ارسال

+
+
+
روش ارسال
+
+ {cancelItem.orderItem.shipper || '-'} +
+
+ {cancelItem.orderItem.trackCode && ( +
+
کد رهگیری
+
+ {cancelItem.orderItem.trackCode} +
+
+ )} + {cancelItem.orderItem.postingDate && ( +
+
تاریخ ارسال
+
+ {formatDateShort(cancelItem.orderItem.postingDate)} +
+
+ )} +
+
تعداد آیتم‌ها
+
+ {cancelItem.orderItem.itemsCount || 0} +
+
+
+
+ )} + + {/* اطلاعات مالی */} + {cancelItem?.orderItem && ( +
+

اطلاعات مالی

+
+
+
مبلغ فروش
+
+ {formatPrice(cancelItem.orderItem.totalSellingPrice || 0)} تومان +
+
+
+
هزینه ارسال
+
+ {formatPrice(cancelItem.orderItem.totalShipmentCost || 0)} تومان +
+
+
+
تخفیف کوپن
+
+ {formatPrice(cancelItem.orderItem.totalCouponDiscount || 0)} تومان +
+
+
+
مبلغ نهایی
+
+ {formatPrice(cancelItem.orderItem.totalPaymentPrice || 0)} تومان +
+
+
+
+ )} +
+ )} + setIsCompleteModalOpen(false)} + cancelId={data?._id ?? ''} + onSuccess={() => { + setIsCompleteModalOpen(false) + close() + }} + /> +
+ ) +} + +export default CancelItemsModal + diff --git a/src/pages/orders/components/CompleteCancelModal.tsx b/src/pages/orders/components/CompleteCancelModal.tsx new file mode 100644 index 0000000..2ece4ee --- /dev/null +++ b/src/pages/orders/components/CompleteCancelModal.tsx @@ -0,0 +1,137 @@ +import { type FC, useEffect, useState } from 'react' +import DefaulModal from '@/components/DefaulModal' +import DatePicker from '@/components/DatePicker' +import Textarea from '@/components/Textarea' +import UploadBox from '@/components/UploadBox' +import Button from '@/components/Button' +import { useCompleteCancel } from '@/pages/orders/hooks/useOrderData' +import { useUploadSingle } from '@/pages/uploader/hooks/useUploaderData' +import { useQueryClient } from '@tanstack/react-query' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/helpers/utils' + +type Props = { + open: boolean + onClose: () => void + cancelId: string + onSuccess?: () => void +} + +const CompleteCancelModal: FC = ({ open, onClose, cancelId, onSuccess }) => { + const [refundDate, setRefundDate] = useState('') + const [refundComment, setRefundComment] = useState('') + const [refundReceiptFile, setRefundReceiptFile] = useState([]) + const [isUploading, setIsUploading] = useState(false) + + const completeCancel = useCompleteCancel() + const uploadSingle = useUploadSingle() + const queryClient = useQueryClient() + + const resetForm = () => { + setRefundDate('') + setRefundComment('') + setRefundReceiptFile([]) + setIsUploading(false) + } + + useEffect(() => { + if (!open) { + resetForm() + } + }, [open]) + + const handleClose = () => { + resetForm() + onClose() + } + + const handleSubmit = async () => { + if (!cancelId) { + toast.error('شناسه کنسلی نامعتبر است') + return + } + + try { + setIsUploading(true) + let receiptUrl = '' + + if (refundReceiptFile.length > 0) { + const uploadResponse = await uploadSingle.mutateAsync(refundReceiptFile[0]) + receiptUrl = uploadResponse?.results?.url?.url || '' + } + + await completeCancel.mutateAsync({ + id: cancelId, + params: { + refundDate, + refundComment, + refundReceipt: receiptUrl, + }, + }) + + await queryClient.invalidateQueries({ queryKey: ['cancels'] }) + toast.success('کنسلی با موفقیت تکمیل شد') + handleClose() + onSuccess?.() + } catch (error) { + toast.error(extractErrorMessage(error)) + } finally { + setIsUploading(false) + } + } + + return ( + +
+
+ setRefundDate(date)} + defaulValue={refundDate} + /> +
+ +