return order

This commit is contained in:
hamid zarghami
2025-11-01 17:16:54 +03:30
parent a893677d1c
commit 91ab1717ac
8 changed files with 169 additions and 153 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ const Resume: FC<ResumeProps> = ({ jobId }) => {
const uploadFileMutation = useMutation({
mutationFn: api.uploadSingleFile,
onSuccess: (data) => {
setValue('resumeUrl', data?.results?.url?.url)
setValue('resumeUrl', data?.results?.data?.url?.url)
toast('فایل با موفقیت آپلود شد', 'success')
},
onError: () => {
+5 -2
View File
@@ -36,17 +36,20 @@ export type ResumeType = {
};
export interface FileUrl {
originalname: string;
size: number;
url: string;
type: string;
}
export interface UploadResults {
export interface UploadData {
message: string;
url: FileUrl;
}
export interface UploadResults {
data: UploadData;
}
export interface UploadResponse {
status: number;
success: boolean;
@@ -9,8 +9,8 @@ interface ReturnConfirmationProps {
quantities: { [key: string]: number }
reason: string
description: string
images: File[]
imagePreviewUrls: string[]
images: { [key: string]: File }
imagePreviewUrls: { [key: string]: string }
returnReasons: Array<{ _id: string; title: string }>
onSubmit: () => void
onBack: () => void
@@ -39,7 +39,7 @@ const ReturnConfirmation: React.FC<ReturnConfirmationProps> = ({
<div className='space-y-4'>
<h4 className='font-medium'>محصولات انتخاب شده</h4>
{selectedItems.map((item) => (
<div key={item._id} className='p-4 border rounded-lg'>
<div key={item._id} className='p-4 border rounded-lg space-y-3'>
<div className='flex items-center gap-4'>
<Image
src={item.product.imagesUrl.cover}
@@ -58,6 +58,19 @@ const ReturnConfirmation: React.FC<ReturnConfirmationProps> = ({
</div>
</div>
</div>
{/* تصویر این آیتم */}
{imagePreviewUrls[item._id] && (
<div>
<div className='text-xs text-gray-600 mb-2'>تصویر آپلود شده:</div>
<Image
src={imagePreviewUrls[item._id]}
width={100}
height={100}
className='max-w-[150px] max-h-[150px] object-contain rounded border'
alt='تصویر محصول'
/>
</div>
)}
</div>
))}
</div>
@@ -79,26 +92,6 @@ const ReturnConfirmation: React.FC<ReturnConfirmationProps> = ({
</div>
</div>
{/* تصاویر آپلود شده */}
{imagePreviewUrls.length > 0 && (
<div className='space-y-4'>
<h4 className='font-medium'>تصاویر آپلود شده</h4>
<div className='grid grid-cols-2 md:grid-cols-3 gap-4'>
{imagePreviewUrls.map((url, index) => (
<div key={index} className='relative'>
<Image
src={url}
width={100}
height={100}
className='max-w-[200px] max-h-[200px] object-contain rounded border'
alt={`تصویر ${index + 1}`}
/>
</div>
))}
</div>
</div>
)}
{/* اخطار */}
<div className='flex gap-2 text-xs text-[#7F7F7F] p-3 bg-primary/10 rounded-lg'>
<div className="size-5 bg-primary/20 rounded-full flex items-center justify-center">
@@ -17,7 +17,7 @@ interface ReturnFormData {
interface ReturnFormProps {
selectedItems: ShipmentItemDetail[]
onSubmit: (data: { quantities: { [key: string]: number }; reason: string; description: string; images: File[] }) => void
onSubmit: (data: { quantities: { [key: string]: number }; reason: string; description: string; images: { [key: string]: File } }) => void
onBack: () => void
isLoading: boolean
returnReasons: Array<{ _id: string; title: string }>
@@ -47,9 +47,8 @@ const ReturnForm: React.FC<ReturnFormProps> = ({
isLoading,
returnReasons
}) => {
const [images, setImages] = useState<File[]>([])
const [imagePreviewUrls, setImagePreviewUrls] = useState<string[]>([])
const fileInputRef = useRef<HTMLInputElement>(null)
const [images, setImages] = useState<{ [key: string]: File }>({})
const [imagePreviewUrls, setImagePreviewUrls] = useState<{ [key: string]: string }>({})
const {
register,
@@ -78,30 +77,33 @@ const ReturnForm: React.FC<ReturnFormProps> = ({
reset()
}
const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || [])
const validFiles = files.filter(file => {
const isValidType = file.type === 'image/jpeg' || file.type === 'image/png'
const isValidSize = file.size <= 10 * 1024 * 1024 // 10MB
return isValidType && isValidSize
})
const handleImageUpload = (itemId: string, event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) return
if (validFiles.length !== files.length) {
const isValidType = file.type === 'image/jpeg' || file.type === 'image/png'
const isValidSize = file.size <= 10 * 1024 * 1024 // 10MB
if (!isValidType || !isValidSize) {
alert('فقط فایل‌های JPG و PNG با حداکثر حجم ۱۰ مگابایت مجاز هستند')
return
}
const newImages = [...images, ...validFiles].slice(0, 5) // حداکثر ۵ عکس
setImages(newImages)
const newPreviewUrls = newImages.map(file => URL.createObjectURL(file))
setImagePreviewUrls(newPreviewUrls)
setImages(prev => ({ ...prev, [itemId]: file }))
setImagePreviewUrls(prev => ({ ...prev, [itemId]: URL.createObjectURL(file) }))
}
const removeImage = (index: number) => {
const newImages = images.filter((_, i) => i !== index)
const newPreviewUrls = imagePreviewUrls.filter((_, i) => i !== index)
setImages(newImages)
setImagePreviewUrls(newPreviewUrls)
const removeImage = (itemId: string) => {
setImages(prev => {
const newImages = { ...prev }
delete newImages[itemId]
return newImages
})
setImagePreviewUrls(prev => {
const newUrls = { ...prev }
delete newUrls[itemId]
return newUrls
})
}
return (
@@ -110,12 +112,12 @@ const ReturnForm: React.FC<ReturnFormProps> = ({
{/* انتخاب تعداد مرجوعی برای هر آیتم */}
<div className='space-y-4'>
<h4 className='font-medium'>تعداد مرجوعی</h4>
<h4 className='font-medium'>تعداد مرجوعی و آپلود تصویر</h4>
{selectedItems.map((item) => {
const availableQuantity = item.quantity - item.cancelled_quantity - item.returned_quantity
return (
<div key={item._id} className='p-4 border rounded-lg'>
<div className='flex items-center gap-4 mb-2'>
<div key={item._id} className='p-4 border rounded-lg space-y-3'>
<div className='flex items-center gap-4'>
<Image
src={item.product.imagesUrl.cover}
width={40}
@@ -143,6 +145,48 @@ const ReturnForm: React.FC<ReturnFormProps> = ({
className='w-20 px-2 py-1 border rounded text-center'
/>
</div>
{/* آپلود تصویر برای این آیتم */}
<div>
<div className='text-xs text-gray-600 mb-2'>
تصویر محصول (JPG یا PNG، حداکثر ۱۰ مگابایت)
</div>
{imagePreviewUrls[item._id] ? (
<div className='relative inline-block'>
<Image
src={imagePreviewUrls[item._id]}
width={100}
height={100}
className='max-w-[150px] max-h-[150px] object-contain rounded border'
alt='تصویر محصول'
/>
<button
type='button'
onClick={() => removeImage(item._id)}
className='absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center'
>
<CloseCircle color='white' size={14} />
</button>
</div>
) : (
<div>
<input
type='file'
accept='image/jpeg,image/png'
onChange={(e) => handleImageUpload(item._id, e)}
className='hidden'
id={`file-${item._id}`}
/>
<label
htmlFor={`file-${item._id}`}
className='flex items-center gap-2 px-4 py-2 border-2 border-dashed border-gray-300 rounded-lg hover:border-primary transition-colors cursor-pointer inline-flex'
>
<DocumentUpload color='gray' size={20} />
<span className='text-sm'>انتخاب تصویر</span>
</label>
</div>
)}
</div>
</div>
)
})}
@@ -178,60 +222,6 @@ const ReturnForm: React.FC<ReturnFormProps> = ({
)}
</div>
{/* آپلود عکس */}
<div>
<div className="text-sm font-medium mb-2">آپلود عکس</div>
<div className='text-xs text-gray-600 mb-3'>
اگر ایراد کالا قابل مشاهده است به صورتی عکس بگیرید که مشخص باشد (با فرمت JPG یا PNG و هر عکس حداکثر ۱۰ مگابایت)
</div>
{/* نمایش عکس‌های آپلود شده */}
{imagePreviewUrls.length > 0 && (
<div className='grid grid-cols-2 md:grid-cols-3 gap-4 mb-4'>
{imagePreviewUrls.map((url, index) => (
<div key={index} className='relative'>
<Image
src={url}
width={100}
height={100}
className='max-w-[200px] max-h-[200px] object-contain rounded border'
alt={`تصویر ${index + 1}`}
/>
<button
type='button'
onClick={() => removeImage(index)}
className='absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center'
>
<CloseCircle color='white' size={14} />
</button>
</div>
))}
</div>
)}
{/* دکمه آپلود */}
{images.length < 5 && (
<div>
<input
ref={fileInputRef}
type='file'
accept='image/jpeg,image/png'
multiple
onChange={handleImageUpload}
className='hidden'
/>
<button
type='button'
onClick={() => fileInputRef.current?.click()}
className='flex items-center gap-2 px-4 py-2 border-2 border-dashed border-gray-300 rounded-lg hover:border-primary transition-colors'
>
<DocumentUpload color='gray' size={20} />
<span>انتخاب عکس</span>
</button>
</div>
)}
</div>
{/* دکمه‌های عملیات */}
<div className='flex gap-3 justify-center'>
<Button
+61 -47
View File
@@ -3,7 +3,7 @@ import Layout from '@/hoc/Layout'
import { type NextPage } from 'next'
import { useParams, useRouter } from 'next/navigation'
import { useState } from 'react'
import { useGetOrderDetail, useReturnOrder, useGetReturnReasons } from '../../hooks/useOrdersData'
import { useGetOrderDetail, useReturnOrder, useGetReturnReasons, useUploadReturnImage } from '../../hooks/useOrdersData'
import ProductSelection from './components/ProductSelection'
import ReturnForm from './components/ReturnForm'
import ReturnConfirmation from './components/ReturnConfirmation'
@@ -17,7 +17,7 @@ interface FormData {
quantities: { [key: string]: number }
reason: string
description: string
images: File[]
images: { [key: string]: File }
}
const ReturnPage: NextPage = () => {
@@ -26,11 +26,12 @@ const ReturnPage: NextPage = () => {
const { data: order, isLoading: isOrderLoading } = useGetOrderDetail(id as string)
const returnOrderMutation = useReturnOrder()
const { data: returnReasons } = useGetReturnReasons()
const uploadImageMutation = useUploadReturnImage()
const [currentStep, setCurrentStep] = useState<Step>(1)
const [selectedItems, setSelectedItems] = useState<string[]>([])
const [formData, setFormData] = useState<FormData | null>(null)
const [imagePreviewUrls, setImagePreviewUrls] = useState<string[]>([])
const [imagePreviewUrls, setImagePreviewUrls] = useState<{ [key: string]: string }>({})
const handleItemSelect = (itemId: string, checked: boolean) => {
if (checked) {
@@ -43,7 +44,10 @@ const ReturnPage: NextPage = () => {
const handleFormSubmit = (data: FormData) => {
setFormData(data)
// ایجاد preview URLs برای تصاویر
const previewUrls = data.images.map(file => URL.createObjectURL(file))
const previewUrls: { [key: string]: string } = {}
Object.entries(data.images).forEach(([itemId, file]) => {
previewUrls[itemId] = URL.createObjectURL(file)
})
setImagePreviewUrls(previewUrls)
setCurrentStep(3)
}
@@ -54,55 +58,65 @@ const ReturnPage: NextPage = () => {
return
}
// تبدیل تصاویر به base64 یا URL
const imagePromises = formData.images.map(async (file) => {
return new Promise<string>((resolve) => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result as string)
reader.readAsDataURL(file)
try {
const returnItems = []
// آپلود تصاویر به صورت ترتیبی (یکی یکی)
for (const itemId of selectedItems) {
// Find the order item that contains this shipment item
const orderItem = order?.results.order.orderItems.find(orderItem =>
orderItem.shipmentItems.some(shipmentItem => shipmentItem._id === itemId)
)
const shipmentItem = orderItem?.shipmentItems.find(item => item._id === itemId)
if (!orderItem || !shipmentItem) continue
// آپلود تصویر این آیتم
let imageUrl = ''
if (formData.images[itemId]) {
try {
const response = await uploadImageMutation.mutateAsync(formData.images[itemId])
imageUrl = response.results.data.url.url
} catch (error) {
toast(`خطا در آپلود تصویر: ${extractErrorMessage(error)}`, 'error')
throw error
}
}
returnItems.push({
orderItemId: orderItem._id,
shipmentItemId: shipmentItem._id,
quantity: formData.quantities[itemId],
comment: formData.description,
reason: formData.reason,
imagesUrl: imageUrl
})
}
// ارسال درخواست مرجوعی به سرور
await returnOrderMutation.mutateAsync({
orderId: Number(id),
items: returnItems
}, {
onSuccess: () => {
toast('درخواست مرجوعی با موفقیت ثبت شد', 'success')
router.push(`/profile/orders/${id}/return/success`)
},
onError: (error) => {
toast(extractErrorMessage(error), 'error')
}
})
})
const imageUrls = await Promise.all(imagePromises)
const returnItems = selectedItems.map(itemId => {
// Find the order item that contains this shipment item
const orderItem = order?.results.order.orderItems.find(orderItem =>
orderItem.shipmentItems.some(shipmentItem => shipmentItem._id === itemId)
)
const shipmentItem = orderItem?.shipmentItems.find(item => item._id === itemId)
if (!orderItem || !shipmentItem) return null
return {
orderItemId: orderItem._id,
shipmentItemId: shipmentItem._id,
quantity: formData.quantities[itemId],
comment: formData.description,
reason: formData.reason,
images: imageUrls
}
}).filter(Boolean) as { orderItemId: string; shipmentItemId: string; quantity: number; comment: string; reason: string; images: string[] }[]
await returnOrderMutation.mutateAsync({
orderId: Number(id),
items: returnItems
}, {
onSuccess: () => {
toast('درخواست مرجوعی با موفقیت ثبت شد', 'success')
router.push(`/profile/orders/${id}/return/success`)
},
onError: (error) => {
toast(extractErrorMessage(error), 'error')
}
})
} catch (error) {
// خطا در آپلود تصاویر یا ارسال درخواست
console.error('Error in handleFinalSubmit:', error)
}
}
const handleBackToStep1 = () => {
setCurrentStep(1)
setFormData(null)
setImagePreviewUrls([])
setImagePreviewUrls({})
}
const handleBackToStep2 = () => {
@@ -50,3 +50,9 @@ export const useGetReturnOrder = () => {
queryFn: api.getReturnOrder,
});
};
export const useUploadReturnImage = () => {
return useMutation({
mutationFn: api.uploadReturnImage,
});
};
+10
View File
@@ -8,6 +8,7 @@ import {
ReturnReasonsResponse,
ReturnOrdersResponse,
} from "../types/Types";
import { UploadResponse } from "@/app/jobs/types/Types";
export const getOrders = async (activeTab: string): Promise<OrdersResponse> => {
const { data } = await axios.get(`/user/profile/orders?status=${activeTab}`);
@@ -45,3 +46,12 @@ export const getReturnOrder = async (): Promise<ReturnOrdersResponse> => {
const { data } = await axios.get(`/returns/user`);
return data;
};
export const uploadReturnImage = async (
file: File
): Promise<UploadResponse> => {
const formData = new FormData();
formData.append("image", file);
const { data } = await axios.post(`/returns/image/upload`, formData);
return data;
};
+1 -1
View File
@@ -339,7 +339,7 @@ export interface ReturnOrderItem {
quantity: number;
comment: string;
reason: string;
images: string[];
imagesUrl: string;
}
export interface ReturnOrderType {