cancel compelete
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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<CancelOrderType | null>(null)
|
||||
const { data, isLoading, error } = useGetCancels(currentPage)
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<PageLoading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<Error errorText="خطا در بارگذاری کنسلیها" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<PageTitle title1="کنسلی ها" />
|
||||
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-5 w-full'>
|
||||
<table className='w-full text-sm'>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={'شماره سفارش'} />
|
||||
<Td text={'کنسلکننده'} />
|
||||
<Td text={'محصول'} />
|
||||
<Td text={'وضعیت'} />
|
||||
<Td text={'مبلغ کل'} />
|
||||
<Td text={'تاریخ ثبت'} />
|
||||
<Td text={'تعداد'} />
|
||||
<Td text={'جزئیات'} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{cancelsOrders.length === 0 ? (
|
||||
<tr className='tr'>
|
||||
<td colSpan={8} className='text-center py-8 text-muted-foreground'>
|
||||
کنسلی یافت نشد
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
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 (
|
||||
<tr key={cancel._id} className='tr'>
|
||||
<Td text={orderId ? `#${orderId}` : '-'} />
|
||||
<Td text={cancellerName} />
|
||||
<Td text={productName} />
|
||||
<Td text=''>
|
||||
<StatusWithText
|
||||
variant={getStatusVariant(cancel?.status)}
|
||||
text={getStatusText(cancel?.status)}
|
||||
/>
|
||||
</Td>
|
||||
<Td text={`${formatPrice(Number(cancel?.total_price || 0))} تومان`} />
|
||||
<Td text={formatDateShort(cancel?.createdAt)} />
|
||||
<Td text={String(totalQuantity)} />
|
||||
<Td text="">
|
||||
<button
|
||||
onClick={() => handleEyeClick(cancel)}
|
||||
className="cursor-pointer hover:opacity-70 transition-opacity"
|
||||
>
|
||||
<Eye size={16} color="#4B5563" />
|
||||
</button>
|
||||
</Td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<PaginationUi
|
||||
pager={pager ? {
|
||||
...pager,
|
||||
nextPage: pager.nextPage ? String(pager.nextPage) : null,
|
||||
} as ProductPagerType : null}
|
||||
onPageChange={(page) => setCurrentPage(page)}
|
||||
/>
|
||||
|
||||
<CancelItemsModal
|
||||
open={isModalOpen}
|
||||
close={() => { setIsModalOpen(false); setSelectedCancel(null); }}
|
||||
data={selectedCancel}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Cancels
|
||||
@@ -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<Props> = ({ open, close, data }) => {
|
||||
const cancelItem = data?.cancelOrderItems
|
||||
const [isCompleteModalOpen, setIsCompleteModalOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<DefaulModal open={open} close={close} isHeader title_header='جزئیات کنسلی' width={900}>
|
||||
{!data ? (
|
||||
<div className='py-6 text-center text-muted-foreground'>دادهای برای نمایش وجود ندارد</div>
|
||||
) : (
|
||||
<Fragment>
|
||||
{data.status === 'Pending' && (
|
||||
<div className='flex justify-end my-4'>
|
||||
<Button
|
||||
onClick={() => setIsCompleteModalOpen(true)}
|
||||
className='bg-blue-600 hover:bg-blue-700 text-white w-fit'
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<DocumentUpload color='#fff' size={18} />
|
||||
<span>تکمیل کنسلی</span>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* اطلاعات سفارش */}
|
||||
<div className='my-4'>
|
||||
<h3 className='text-base font-semibold mb-3'>اطلاعات سفارش</h3>
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3'>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>شماره سفارش</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.order?._id ? `#${data.order._id}` : '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>وضعیت سفارش</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.order?.orderStatus || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>وضعیت کنسلی</div>
|
||||
<div className='mt-0.5'>
|
||||
<StatusWithText variant={getStatusVariant(data.status)} text={getStatusText(data.status)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>مبلغ کل</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>{formatPrice(Number(data.total_price || 0))} تومان</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>تاریخ ثبت کنسلی</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>{formatDateShort(data.createdAt)}</div>
|
||||
</div>
|
||||
{data.cancellDate && (
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>تاریخ تایید کنسلی</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>{formatDateShort(data.cancellDate)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* اطلاعات کنسلکننده */}
|
||||
{data.cancellerDetails && (
|
||||
<div className='mb-4'>
|
||||
<h3 className='text-base font-semibold mb-3'>اطلاعات کنسلکننده</h3>
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3'>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>نام</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.cancellerDetails.fullName || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>شماره تماس</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.cancellerDetails.phoneNumber || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>نوع کنسلکننده</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.cancellerRef === 'User' ? 'کاربر' : data.cancellerRef || '-'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* آدرس ارسال */}
|
||||
{data.order?.shipmentAddress && (
|
||||
<div className='mb-4'>
|
||||
<h3 className='text-base font-semibold mb-3'>آدرس ارسال</h3>
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-3'>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>شهر</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.order.shipmentAddress.city || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>استان</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.order.shipmentAddress.province || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border col-span-full'>
|
||||
<div className='text-[11px] text-muted-foreground'>آدرس کامل</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.order.shipmentAddress.address || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>کد پستی</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.order.shipmentAddress.postalCode || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>تلفن</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.order.shipmentAddress.phone || '-'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* اطلاعات محصول کنسلشده */}
|
||||
{cancelItem && (
|
||||
<div className='mb-4'>
|
||||
<h3 className='text-base font-semibold mb-3'>اطلاعات محصول کنسلشده</h3>
|
||||
|
||||
{/* کارت محصول */}
|
||||
<div className='p-4 rounded-lg border border-border mb-4'>
|
||||
<div className='flex gap-4'>
|
||||
{cancelItem.shipmentItem?.product?.imagesUrl?.cover && (
|
||||
<img
|
||||
src={cancelItem.shipmentItem.product.imagesUrl.cover}
|
||||
alt={cancelItem.shipmentItem.product.title_fa}
|
||||
className='w-24 h-24 object-cover rounded-lg'
|
||||
/>
|
||||
)}
|
||||
<div className='flex-1'>
|
||||
<h4 className='font-medium mb-2'>
|
||||
{cancelItem.shipmentItem?.product?.title_fa || '-'}
|
||||
</h4>
|
||||
<div className='grid grid-cols-2 md:grid-cols-3 gap-2 text-xs'>
|
||||
<div>
|
||||
<span className='text-muted-foreground'>قیمت فروش: </span>
|
||||
<span className='font-medium'>
|
||||
{formatPrice(cancelItem.shipmentItem?.selling_price || 0)} تومان
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-muted-foreground'>قیمت خردهفروشی: </span>
|
||||
<span className='font-medium'>
|
||||
{formatPrice(cancelItem.shipmentItem?.retail_price || 0)} تومان
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-muted-foreground'>هزینه ارسال: </span>
|
||||
<span className='font-medium'>
|
||||
{cancelItem.shipmentItem?.isFreeShip
|
||||
? 'رایگان'
|
||||
: `${formatPrice(cancelItem.shipmentItem?.shipmentCost || 0)} تومان`}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-muted-foreground'>تعداد سفارش: </span>
|
||||
<span className='font-medium'>{cancelItem.shipmentItem?.quantity || 0}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-muted-foreground'>تعداد کنسلشده: </span>
|
||||
<span className='font-medium text-red-600'>
|
||||
{cancelItem.shipmentItem?.cancelled_quantity || 0}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className='text-muted-foreground'>تعداد مرجوعشده: </span>
|
||||
<span className='font-medium text-orange-600'>
|
||||
{cancelItem.shipmentItem?.returned_quantity || 0}
|
||||
</span>
|
||||
</div>
|
||||
{cancelItem.shipmentItem?.discount_percent !== undefined && cancelItem.shipmentItem?.discount_percent > 0 && (
|
||||
<div>
|
||||
<span className='text-muted-foreground'>درصد تخفیف: </span>
|
||||
<span className='font-medium text-green-600'>
|
||||
{cancelItem.shipmentItem.discount_percent}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* اطلاعات فروشگاه و وضعیت */}
|
||||
<div className='grid grid-cols-1 md:grid-cols-3 gap-3 mb-4'>
|
||||
{cancelItem.orderItem?.shipmentItems?.product?.shop?.shopName && (
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>نام فروشگاه</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{cancelItem.orderItem.shipmentItems.product.shop.shopName}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{cancelItem.orderItem?.shipmentItems?.product?.shop?.shopCode && (
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>کد فروشگاه</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{cancelItem.orderItem.shipmentItems.product.shop.shopCode}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{cancelItem.orderItem?.status && (
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>وضعیت آیتم سفارش</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{cancelItem.orderItem.status}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* دلیل و توضیحات کنسلی */}
|
||||
<div className='grid grid-cols-1 gap-3'>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>دلیل کنسلی</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{cancelItem.reason?.title || '-'}
|
||||
</div>
|
||||
</div>
|
||||
{cancelItem.comment && (
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>توضیحات کاربر</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{cancelItem.comment}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{data.cancellComment && (
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>توضیحات ادمین</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{data.cancellComment}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* اطلاعات ارسال */}
|
||||
{cancelItem?.orderItem && (
|
||||
<div className='mb-4'>
|
||||
<h3 className='text-base font-semibold mb-3'>اطلاعات ارسال</h3>
|
||||
<div className='grid grid-cols-1 md:grid-cols-3 gap-3'>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>روش ارسال</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{cancelItem.orderItem.shipper || '-'}
|
||||
</div>
|
||||
</div>
|
||||
{cancelItem.orderItem.trackCode && (
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>کد رهگیری</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{cancelItem.orderItem.trackCode}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{cancelItem.orderItem.postingDate && (
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>تاریخ ارسال</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{formatDateShort(cancelItem.orderItem.postingDate)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>تعداد آیتمها</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{cancelItem.orderItem.itemsCount || 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* اطلاعات مالی */}
|
||||
{cancelItem?.orderItem && (
|
||||
<div className='mb-4'>
|
||||
<h3 className='text-base font-semibold mb-3'>اطلاعات مالی</h3>
|
||||
<div className='grid grid-cols-2 md:grid-cols-4 gap-3'>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>مبلغ فروش</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{formatPrice(cancelItem.orderItem.totalSellingPrice || 0)} تومان
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>هزینه ارسال</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{formatPrice(cancelItem.orderItem.totalShipmentCost || 0)} تومان
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>تخفیف کوپن</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{formatPrice(cancelItem.orderItem.totalCouponDiscount || 0)} تومان
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2 rounded-lg border border-border'>
|
||||
<div className='text-[11px] text-muted-foreground'>مبلغ نهایی</div>
|
||||
<div className='mt-0.5 text-sm font-medium'>
|
||||
{formatPrice(cancelItem.orderItem.totalPaymentPrice || 0)} تومان
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
<CompleteCancelModal
|
||||
open={isCompleteModalOpen}
|
||||
onClose={() => setIsCompleteModalOpen(false)}
|
||||
cancelId={data?._id ?? ''}
|
||||
onSuccess={() => {
|
||||
setIsCompleteModalOpen(false)
|
||||
close()
|
||||
}}
|
||||
/>
|
||||
</DefaulModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CancelItemsModal
|
||||
|
||||
@@ -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<Props> = ({ open, onClose, cancelId, onSuccess }) => {
|
||||
const [refundDate, setRefundDate] = useState('')
|
||||
const [refundComment, setRefundComment] = useState('')
|
||||
const [refundReceiptFile, setRefundReceiptFile] = useState<File[]>([])
|
||||
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 (
|
||||
<DefaulModal
|
||||
open={open}
|
||||
close={handleClose}
|
||||
isHeader
|
||||
title_header='تکمیل کنسلی'
|
||||
>
|
||||
<div className='space-y-4 mt-4 w-full sm:w-[420px]'>
|
||||
<div>
|
||||
<DatePicker
|
||||
label='تاریخ بازگشت وجه'
|
||||
placeholder='تاریخ را انتخاب کنید'
|
||||
onChange={(date) => setRefundDate(date)}
|
||||
defaulValue={refundDate}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
label='توضیحات بازگشت وجه'
|
||||
placeholder='توضیحات را وارد کنید'
|
||||
value={refundComment}
|
||||
onChange={(event) => setRefundComment(event.target.value)}
|
||||
rows={4}
|
||||
/>
|
||||
|
||||
<UploadBox
|
||||
label='رسید بازگشت وجه'
|
||||
onChange={(files) => setRefundReceiptFile(files)}
|
||||
isReset={!open}
|
||||
/>
|
||||
|
||||
<div className='flex justify-end items-center pt-4'>
|
||||
<div className='flex gap-3'>
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
className='bg-gray-200 hover:bg-gray-300 text-gray-700 w-fit'
|
||||
>
|
||||
انصراف
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
className='bg-blue-600 hover:bg-blue-700 text-white w-fit'
|
||||
disabled={!refundDate || !refundComment || isUploading || completeCancel.isPending}
|
||||
>
|
||||
{isUploading || completeCancel.isPending ? 'در حال انجام...' : 'تکمیل کنسلی'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DefaulModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CompleteCancelModal
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/OrderService";
|
||||
import { useSharedStore } from "@/shared/store/sharedStore";
|
||||
import type { CompleteReturnType } from "../types/ReturnTypes";
|
||||
import type { CompleteCancelType } from "../types/Types";
|
||||
|
||||
export const useGetNativeOrders = (
|
||||
page: number = 1,
|
||||
@@ -112,3 +113,17 @@ export const useCompleteReturn = () => {
|
||||
api.compeleteReturn(id, params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetCancels = (page: number = 1) => {
|
||||
return useQuery({
|
||||
queryKey: ["cancels", page],
|
||||
queryFn: () => api.getCancels(page),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCompleteCancel = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ id, params }: { id: string; params: CompleteCancelType }) =>
|
||||
api.completeCancel(id, params),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,6 +5,8 @@ import type {
|
||||
} from "../types/ReturnTypes";
|
||||
import type {
|
||||
CancelReasonsResponseType,
|
||||
CancelsResponseType,
|
||||
CompleteCancelType,
|
||||
CreateCancelReasonType,
|
||||
CreateReturnReasonType,
|
||||
NativeOrdersResponseType,
|
||||
@@ -118,3 +120,23 @@ export const compeleteReturn = async (
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getCancels = async (
|
||||
page: number = 1
|
||||
): Promise<CancelsResponseType> => {
|
||||
const { data } = await axios.get(`/admin/orders/cancel`, {
|
||||
params: { page },
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const completeCancel = async (
|
||||
id: string,
|
||||
params: CompleteCancelType
|
||||
) => {
|
||||
const { data } = await axios.post(
|
||||
`/admin/orders/cancel/${id}/complete`,
|
||||
params
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -209,7 +209,7 @@ export type PagerType = {
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
prevPage: boolean | null;
|
||||
nextPage: string | null;
|
||||
nextPage: boolean | null;
|
||||
};
|
||||
|
||||
export type OrdersResponseType = {
|
||||
@@ -464,3 +464,113 @@ export type ReturnOrderDetailResponseType = {
|
||||
returnOrder: ReturnOrderDetailType;
|
||||
};
|
||||
};
|
||||
|
||||
// تایپهای مربوط به کنسلیها
|
||||
export type CancellerDetailsType = {
|
||||
_id: string;
|
||||
fullName: string;
|
||||
phoneNumber: string;
|
||||
dateOfBirth: string | null;
|
||||
address: string;
|
||||
nationalCode: string | null;
|
||||
homeNumber: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
__v: number;
|
||||
};
|
||||
|
||||
export type CancelOrderOrderType = {
|
||||
_id: number;
|
||||
user: string;
|
||||
payment: string;
|
||||
shipmentAddress: ShipmentAddressType;
|
||||
orderStatus: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
__v: number;
|
||||
};
|
||||
|
||||
export type CancelOrderItemOrderItemType = {
|
||||
_id: string;
|
||||
order: number;
|
||||
shop: string;
|
||||
shipmentItems: {
|
||||
product: {
|
||||
shop: ShopType;
|
||||
};
|
||||
};
|
||||
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 CancelOrderItemShipmentItemType = {
|
||||
_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 CancelOrderItemType = {
|
||||
quantity: number;
|
||||
comment: string;
|
||||
reason: CancelReasonType;
|
||||
_id: string;
|
||||
cancelOrderId: string;
|
||||
orderItem: CancelOrderItemOrderItemType;
|
||||
shipmentItem: CancelOrderItemShipmentItemType;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
__v: number;
|
||||
};
|
||||
|
||||
export type CancelOrderType = {
|
||||
_id: string;
|
||||
order: CancelOrderOrderType;
|
||||
status: string;
|
||||
total_price: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
cancelOrderItems: CancelOrderItemType;
|
||||
canceller: string;
|
||||
cancellerRef: string;
|
||||
cancellReceipt: string | null;
|
||||
cancellComment: string | null;
|
||||
cancellDate: string | null;
|
||||
__v: number;
|
||||
cancellerDetails: CancellerDetailsType;
|
||||
};
|
||||
|
||||
export type CancelsResponseType = {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: {
|
||||
pager: PagerType;
|
||||
cancelsOrders: CancelOrderType[];
|
||||
};
|
||||
};
|
||||
|
||||
export type CompleteCancelType = {
|
||||
refundDate: string;
|
||||
refundComment: string;
|
||||
refundReceipt: string;
|
||||
};
|
||||
|
||||
@@ -82,6 +82,7 @@ import Reviews from '@/pages/products/Reviews'
|
||||
import Questions from '@/pages/products/Questions'
|
||||
import Returns from '@/pages/orders/Returns'
|
||||
import ReturnDetails from '@/pages/orders/ReturnDetails'
|
||||
import Cancels from '@/pages/orders/Cancels'
|
||||
const MainRouter: FC = () => {
|
||||
|
||||
const { hasSubMenu } = useSharedStore()
|
||||
@@ -201,6 +202,7 @@ const MainRouter: FC = () => {
|
||||
|
||||
<Route path={Pages.orders.return} element={<Returns />} />
|
||||
<Route path={`${Pages.orders.returnDetails}:id`} element={<ReturnDetails />} />
|
||||
<Route path={Pages.orders.cancels} element={<Cancels />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,13 +22,13 @@ const OrdersSubMenu: FC = () => {
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col mt-10 gap-4'>
|
||||
<SubMenuItem
|
||||
{/* <SubMenuItem
|
||||
title={'لیست سفارشات (فروشندگان)'}
|
||||
isActive={isActive('/seller')}
|
||||
link={Pages.orders.sellers}
|
||||
/>
|
||||
/> */}
|
||||
<SubMenuItem
|
||||
title={'لیست سفارشات (مدیر)'}
|
||||
title={'همه ی سفارشات'}
|
||||
isActive={isActive('Admin')}
|
||||
link={Pages.orders.admin}
|
||||
/>
|
||||
@@ -58,6 +58,12 @@ const OrdersSubMenu: FC = () => {
|
||||
isActive={isActive('return')}
|
||||
link={Pages.orders.return}
|
||||
/>
|
||||
|
||||
<SubMenuItem
|
||||
title={'کنسلی ها'}
|
||||
isActive={isActive('cancels')}
|
||||
link={Pages.orders.cancels}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user