compelete return

This commit is contained in:
hamid zarghami
2025-11-04 16:37:09 +03:30
parent 9d4ff5b29c
commit b3d60bd384
6 changed files with 186 additions and 24 deletions
+126 -2
View File
@@ -1,16 +1,21 @@
import { type FC, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { useGetReturnDetails, useApproveReturn, useRejectReturn } from './hooks/useOrderData'
import { useGetReturnDetails, useApproveReturn, useRejectReturn, useCompleteReturn } 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 { Calendar, Money3, Box, Location, CloseCircle, TickCircle, Shop, DocumentUpload } 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'
import DefaulModal from '@/components/DefaulModal'
import Textarea from '@/components/Textarea'
import DatePicker from '@/components/DatePicker'
import UploadBox from '@/components/UploadBox'
import { useUploadSingle } from '@/pages/uploader/hooks/useUploaderData'
const ReturnDetails: FC = () => {
const { id } = useParams()
@@ -19,9 +24,16 @@ const ReturnDetails: FC = () => {
const { data, isLoading, error } = useGetReturnDetails(id!)
const approveReturn = useApproveReturn()
const rejectReturn = useRejectReturn()
const completeReturn = useCompleteReturn()
const uploadSingle = useUploadSingle()
const [showApproveModal, setShowApproveModal] = useState(false)
const [showRejectModal, setShowRejectModal] = useState(false)
const [showCompleteModal, setShowCompleteModal] = useState(false)
const [refundDate, setRefundDate] = useState('')
const [refundComment, setRefundComment] = useState('')
const [refundReceiptFile, setRefundReceiptFile] = useState<File[]>([])
const [isUploading, setIsUploading] = useState(false)
const handleApprove = async () => {
try {
@@ -49,6 +61,42 @@ const ReturnDetails: FC = () => {
})
}
const handleCompleteReturn = async () => {
try {
setIsUploading(true)
let receiptUrl = ''
// آپلود فایل رسید (اگر انتخاب شده باشد)
if (refundReceiptFile.length > 0) {
const uploadResponse = await uploadSingle.mutateAsync(refundReceiptFile[0])
receiptUrl = uploadResponse?.results?.url?.url || ''
}
// ارسال درخواست تکمیل مرجوعی
await completeReturn.mutateAsync({
id: id!,
params: {
refundDate,
refundComment,
refundReceipt: receiptUrl
}
})
queryClient.invalidateQueries({ queryKey: ['return-details', id] })
queryClient.invalidateQueries({ queryKey: ['order-returns'] })
setShowCompleteModal(false)
setRefundDate('')
setRefundComment('')
setRefundReceiptFile([])
toast.success('مرجوعی با موفقیت تکمیل شد')
navigate('/orders/returns')
} catch (error) {
toast.error(extractErrorMessage(error))
} finally {
setIsUploading(false)
}
}
if (isLoading) {
return <PageLoading />
}
@@ -80,6 +128,8 @@ const ReturnDetails: FC = () => {
switch (status) {
case 'Approved':
return 'success'
case 'Completed':
return 'success'
case 'Rejected':
return 'error'
case 'Pending':
@@ -93,6 +143,8 @@ const ReturnDetails: FC = () => {
switch (status) {
case 'Approved':
return 'تایید شده'
case 'Completed':
return 'تکمیل شده'
case 'Rejected':
return 'رد شده'
case 'Pending':
@@ -144,6 +196,21 @@ const ReturnDetails: FC = () => {
</div>
)}
{/* دکمه تکمیل مرجوعی */}
{data?.results?.returnOrder?.status === 'Approved' && (
<div className='flex gap-3 justify-end'>
<Button
onClick={() => setShowCompleteModal(true)}
className='bg-blue-600 hover:bg-blue-700 text-white w-fit'
>
<div className='flex items-center gap-2'>
<DocumentUpload color='#fff' size={20} />
<div>تکمیل مرجوعی</div>
</div>
</Button>
</div>
)}
{/* کارت‌های اطلاعاتی */}
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6'>
{/* اطلاعات فروشگاه */}
@@ -324,6 +391,63 @@ const ReturnDetails: FC = () => {
label="آیا از رد این مرجوعی اطمینان دارید؟"
isLoading={rejectReturn.isPending}
/>
{/* مودال تکمیل مرجوعی */}
<DefaulModal
open={showCompleteModal}
close={() => {
setShowCompleteModal(false)
setRefundDate('')
setRefundComment('')
setRefundReceiptFile([])
}}
isHeader={true}
title_header="تکمیل مرجوعی"
>
<div className="space-y-4 mt-4 w-[400px]">
<DatePicker
label="تاریخ بازگشت وجه"
placeholder="تاریخ را انتخاب کنید"
onChange={(date) => setRefundDate(date)}
defaulValue={refundDate}
/>
<Textarea
label="توضیحات بازگشت وجه"
placeholder="توضیحات را وارد کنید"
value={refundComment}
onChange={(e) => setRefundComment(e.target.value)}
rows={4}
/>
<UploadBox
label="رسید بازگشت وجه"
onChange={(files) => setRefundReceiptFile(files)}
isReset={!showCompleteModal}
/>
<div className="flex gap-3 justify-end pt-4">
<Button
onClick={() => {
setShowCompleteModal(false)
setRefundDate('')
setRefundComment('')
setRefundReceiptFile([])
}}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 w-fit"
>
انصراف
</Button>
<Button
onClick={handleCompleteReturn}
className="bg-blue-600 hover:bg-blue-700 text-white w-fit"
disabled={!refundDate || !refundComment || isUploading || completeReturn.isPending}
>
{isUploading || completeReturn.isPending ? 'در حال انجام...' : 'تکمیل مرجوعی'}
</Button>
</div>
</div>
</DefaulModal>
</div>
)
}
+29 -21
View File
@@ -41,6 +41,7 @@ const Returns: FC = () => {
const getStatusVariant = (status: string) => {
switch (status) {
case 'Approved': return 'success'
case 'Completed': return 'success'
case 'Rejected': return 'error'
case 'Pending':
default: return 'warning'
@@ -50,6 +51,7 @@ const Returns: FC = () => {
const getStatusText = (status: string) => {
switch (status) {
case 'Approved': return 'تایید شده'
case 'Completed': return 'تکمیل شده'
case 'Rejected': return 'رد شده'
case 'Pending': return 'در انتظار'
default: return status
@@ -76,32 +78,38 @@ const Returns: FC = () => {
<tbody>
{returnsOrders.length === 0 ? (
<tr className='tr'>
<td colSpan={6} className='text-center py-8 text-muted-foreground'>
<td colSpan={7} className='text-center py-8 text-muted-foreground'>
مرجوعی یافت نشد
</td>
</tr>
) : (
returnsOrders.map((ret: ReturnOrderType) => (
<tr key={ret._id} className='tr'>
<Td text={`#${ret?.order?._id ?? '-'}`} />
<Td text={ret?.shopName ?? '-'} />
<Td text=''>
<StatusWithText
variant={getStatusVariant(ret?.status)}
text={getStatusText(ret?.status)}
/>
</Td>
<Td text={`${formatPrice(Number(ret?.total_price || 0))} تومان`} />
<Td text={formatDateShort(ret?.createdAt)} />
<Td text={String(ret?.returnOrderItems?.quantity ?? 0)} />
<Td text="">
returnsOrders.map((ret: ReturnOrderType) => {
const totalQuantity = Array.isArray(ret?.returnOrderItems)
? ret.returnOrderItems.reduce((sum, item) => sum + (item?.quantity || 0), 0)
: 0
<Link to={`${Pages.orders.returnDetails}${ret._id}`}>
<Eye size={16} color="#4B5563" />
</Link>
</Td>
</tr>
))
return (
<tr key={ret._id} className='tr'>
<Td text={`#${ret?.order?._id ?? '-'}`} />
<Td text={ret?.shopName ?? '-'} />
<Td text=''>
<StatusWithText
variant={getStatusVariant(ret?.status)}
text={getStatusText(ret?.status)}
/>
</Td>
<Td text={`${formatPrice(Number(ret?.total_price || 0))} تومان`} />
<Td text={formatDateShort(ret?.createdAt)} />
<Td text={String(totalQuantity)} />
<Td text="">
<Link to={`${Pages.orders.returnDetails}${ret._id}`}>
<Eye size={16} color="#4B5563" />
</Link>
</Td>
</tr>
)
})
)}
</tbody>
</table>
@@ -14,6 +14,7 @@ type Props = {
const getStatusVariant = (status: string) => {
switch (status) {
case 'Approved': return 'success'
case 'Completed': return 'success'
case 'Rejected': return 'error'
case 'Pending':
default: return 'warning'
@@ -23,6 +24,7 @@ const getStatusVariant = (status: string) => {
const getStatusText = (status: string) => {
switch (status) {
case 'Approved': return 'تایید شده'
case 'Completed': return 'تکمیل شده'
case 'Rejected': return 'رد شده'
case 'Pending': return 'در انتظار'
default: return status
+8
View File
@@ -1,6 +1,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";
export const useGetNativeOrders = (
page: number = 1,
@@ -104,3 +105,10 @@ export const useRejectReturn = () => {
mutationFn: api.rejectReturn,
});
};
export const useCompleteReturn = () => {
return useMutation({
mutationFn: ({ id, params }: { id: string; params: CompleteReturnType }) =>
api.compeleteReturn(id, params),
});
};
+15 -1
View File
@@ -1,5 +1,8 @@
import axios from "../../../config/axios";
import type { AddTrackCodeType } from "../types/ReturnTypes";
import type {
AddTrackCodeType,
CompleteReturnType,
} from "../types/ReturnTypes";
import type {
CancelReasonsResponseType,
CreateCancelReasonType,
@@ -104,3 +107,14 @@ export const rejectReturn = async (id: string) => {
const { data } = await axios.post(`/admin/orders/returns/${id}/reject`);
return data;
};
export const compeleteReturn = async (
id: string,
params: CompleteReturnType
) => {
const { data } = await axios.post(
`/admin/orders/returns/${id}/complete`,
params
);
return data;
};
+6
View File
@@ -51,3 +51,9 @@ export type AddTrackCodeType = {
postingReceipt: string;
orderId: string;
};
export type CompleteReturnType = {
refundDate: string;
refundComment: string;
refundReceipt: string;
};