From 700e757128e5bc8ba011d42981c6f5d8515a1f78 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Wed, 8 Oct 2025 09:08:01 +0330 Subject: [PATCH] return order + success page return order + ... --- public/images/success.png | Bin 0 -> 18515 bytes .../components/ShippingMethodSelector.tsx | 319 ++++++++++++++++++ src/app/cart/hooks/useCartData.ts | 13 + src/app/cart/payment/page.tsx | 10 +- src/app/cart/service/Service.ts | 12 + src/app/cart/shipping/page.tsx | 110 +++++- src/app/cart/types/Types.ts | 20 ++ src/app/profile/orders/[id]/page.tsx | 26 +- .../return/components/ProductSelection.tsx | 96 ++++++ .../return/components/ReturnConfirmation.tsx | 134 ++++++++ .../[id]/return/components/ReturnForm.tsx | 257 ++++++++++++++ src/app/profile/orders/[id]/return/page.tsx | 223 +++++++++++- .../orders/[id]/return/success/page.tsx | 39 +++ src/app/profile/orders/hooks/useOrdersData.ts | 13 + src/app/profile/orders/service/Service.ts | 12 + src/app/profile/orders/types/Types.ts | 28 ++ src/components/CartSummary.tsx | 7 +- 17 files changed, 1295 insertions(+), 24 deletions(-) create mode 100644 public/images/success.png create mode 100644 src/app/cart/components/ShippingMethodSelector.tsx create mode 100644 src/app/profile/orders/[id]/return/components/ProductSelection.tsx create mode 100644 src/app/profile/orders/[id]/return/components/ReturnConfirmation.tsx create mode 100644 src/app/profile/orders/[id]/return/components/ReturnForm.tsx create mode 100644 src/app/profile/orders/[id]/return/success/page.tsx diff --git a/public/images/success.png b/public/images/success.png new file mode 100644 index 0000000000000000000000000000000000000000..e835e91dac1f1d936b9f0fc75c816a93162dbef1 GIT binary patch literal 18515 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

V^^ddnVp`kE2j&+q9IK+TOO{E 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 literal 0 HcmV?d00001 diff --git a/src/app/cart/components/ShippingMethodSelector.tsx b/src/app/cart/components/ShippingMethodSelector.tsx new file mode 100644 index 0000000..26d9151 --- /dev/null +++ b/src/app/cart/components/ShippingMethodSelector.tsx @@ -0,0 +1,319 @@ +'use client' +import { useState, useEffect } from 'react' +import { Truck, Sort, Flash } from 'iconsax-react' +import { ShopShippingType, ShipmentInfoType } from '../types/Types' + +interface ShippingMethodSelectorProps { + shippingData: ShopShippingType[] + selectedShipments: Record + onShipmentSelect: (shopId: string, shipperId: number) => void + onClearSelections?: () => void +} + +const ShippingMethodSelector = ({ + shippingData, + selectedShipments, + onShipmentSelect, + onClearSelections +}: ShippingMethodSelectorProps) => { + const selectedCount = Object.keys(selectedShipments).length + const totalShops = shippingData?.length || 0 + + // State برای مرتب‌سازی + const [sortBy, setSortBy] = useState<'price' | 'time' | 'default'>('default') + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc') + + // State برای collapse/expand محصولات + const [expandedShops, setExpandedShops] = useState>(new Set()) + + // تابع مرتب‌سازی روش‌های ارسال + const sortShippers = (shippers: any[]) => { + if (sortBy === 'default') return shippers + + return [...shippers].sort((a, b) => { + let aValue, bValue + + if (sortBy === 'price') { + aValue = a.totalShippingCost + bValue = b.totalShippingCost + } else if (sortBy === 'time') { + aValue = a.shippingDaysRange?.[0] || 0 + bValue = b.shippingDaysRange?.[0] || 0 + } + + if (sortOrder === 'asc') { + return aValue - bValue + } else { + return bValue - aValue + } + }) + } + + // هندلر تغییر مرتب‌سازی + const handleSortChange = (newSortBy: 'price' | 'time' | 'default') => { + if (sortBy === newSortBy) { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') + } else { + setSortBy(newSortBy) + setSortOrder('asc') + } + } + + // هندلر toggle محصولات فروشگاه + const toggleShopExpansion = (shopId: string) => { + const newExpanded = new Set(expandedShops) + if (newExpanded.has(shopId)) { + newExpanded.delete(shopId) + } else { + newExpanded.add(shopId) + } + setExpandedShops(newExpanded) + } + + // هندلر انتخاب سریع بهترین روش ارسال برای همه فروشگاه‌ها + const handleQuickSelect = (criteria: 'cheapest' | 'fastest') => { + const newSelections: Record = {} + + shippingData?.forEach((shop) => { + if (shop.shippers && shop.shippers.length > 0) { + let bestShipper = shop.shippers[0] + + shop.shippers.forEach((shipper) => { + if (criteria === 'cheapest') { + if (shipper.totalShippingCost < bestShipper.totalShippingCost) { + bestShipper = shipper + } + } else if (criteria === 'fastest') { + const shipperTime = shipper.shippingDaysRange?.[0] || 999 + const bestTime = bestShipper.shippingDaysRange?.[0] || 999 + if (shipperTime < bestTime) { + bestShipper = shipper + } + } + }) + + newSelections[shop.shopId] = bestShipper.shipperId + } + }) + + // اعمال انتخاب‌ها + Object.entries(newSelections).forEach(([shopId, shipperId]) => { + onShipmentSelect(shopId, shipperId) + }) + } + + return ( +

+
+
+ +
+

روش ارسال

+

+ {selectedCount} از {totalShops} فروشگاه انتخاب شده +

+ {selectedCount > 0 && ( +

+ مجموع ارسال: {Object.entries(selectedShipments).reduce((total, [shopId, shipperId]) => { + const shop = shippingData?.find(s => s.shopId === shopId) + const shipper = shop?.shippers?.find(s => s.shipperId === shipperId) + return total + (shipper?.totalShippingCost || 0) + }, 0).toLocaleString('fa-IR')} تومان +

+ )} +
+
+
+ {/* انتخاب سریع */} +
+ +
+ + +
+
+ + {/* کنترل‌های مرتب‌سازی */} +
+ + +
+ + {onClearSelections && Object.keys(selectedShipments).length > 0 && ( + + )} +
+
+ +
+ {shippingData?.map((shop) => ( +
+
+
+

{shop.shopName}

+

+ {shop.items?.length || 0} محصول +

+
+ {selectedShipments[shop.shopId] && ( +
+ + + + انتخاب شد +
+ )} +
+ + {/* نمایش محصولات فروشگاه */} +
+ + {expandedShops.has(shop.shopId) && ( +
+ {shop.items?.map((item, index) => ( +
+ {item.product.title_fa} +
+
+ {item.product.title_fa} +
+
+
+ تعداد: {item.quantity} +
+
+ {item.variant.price.selling_price.toLocaleString('fa-IR')} تومان +
+
+
+
+ ))} +
+ )} +
+ + {/* روش‌های ارسال */} +
+
روش ارسال:
+
+ {(() => { + const shippers = sortShippers(shop.shippers || []) + const cheapestPrice = Math.min(...shippers.map(s => s.totalShippingCost)) + + return shippers.map((shipper) => { + const priceDifference = shipper.totalShippingCost - cheapestPrice + const isCheapest = priceDifference === 0 + + return ( + + ) + }) + })()} +
+ + {(!shop.shippers || shop.shippers.length === 0) && ( +
+ روش ارسالی برای این فروشگاه موجود نیست +
+ )} +
+
+ ))} +
+
+ ) +} + +export default ShippingMethodSelector diff --git a/src/app/cart/hooks/useCartData.ts b/src/app/cart/hooks/useCartData.ts index 8dfaa4f..7df38a6 100644 --- a/src/app/cart/hooks/useCartData.ts +++ b/src/app/cart/hooks/useCartData.ts @@ -97,3 +97,16 @@ export const useBulkAddCart = () => { mutationFn: api.bulkAddCart, }); }; + +export const useGetShipmentCart = () => { + return useQuery({ + queryKey: ["shipment-cart"], + queryFn: api.getShipmentCart, + }); +}; + +export const useSaveShipmentCart = () => { + return useMutation({ + mutationFn: api.saveShipmentCart, + }); +}; diff --git a/src/app/cart/payment/page.tsx b/src/app/cart/payment/page.tsx index c6a3b3f..a27720c 100644 --- a/src/app/cart/payment/page.tsx +++ b/src/app/cart/payment/page.tsx @@ -48,9 +48,15 @@ const CartPayment: NextPage = () => { ) } - // محاسبه هزینه ارسال + // بارگذاری انتخاب‌های ارسال از localStorage + const savedShipments = typeof window !== 'undefined' ? localStorage.getItem('selectedShipments') : null + const selectedShipments = savedShipments ? JSON.parse(savedShipments) : {} + + // محاسبه هزینه ارسال بر اساس انتخاب‌های کاربر const totalShippingCost = shippingData?.results?.shipping?.reduce((total, shop) => { - return total + (shop.shippers?.[0]?.totalShippingCost || 0) + const selectedShipperId = selectedShipments[shop.shopId] + const selectedShipper = shop.shippers?.find(shipper => shipper.shipperId === selectedShipperId) + return total + (selectedShipper?.totalShippingCost || shop.shippers?.[0]?.totalShippingCost || 0) }, 0) || 0 // محاسبه قیمت نهایی diff --git a/src/app/cart/service/Service.ts b/src/app/cart/service/Service.ts index 0aba998..9afe519 100644 --- a/src/app/cart/service/Service.ts +++ b/src/app/cart/service/Service.ts @@ -13,6 +13,8 @@ import { PaymentStatusType, PaymentStatusResponseType, BulkAddCartType, + ShipmentCartResponseType, + ShipmentSaveType, } from "../types/Types"; export const getCart = async (): Promise => { @@ -75,3 +77,13 @@ export const bulkAddCart = async (params: BulkAddCartType) => { const { data } = await axios.post("/cart/bulk-add", params); return data; }; + +export const getShipmentCart = async (): Promise => { + const { data } = await axios.get("/shipment/cart"); + return data; +}; + +export const saveShipmentCart = async (params: ShipmentSaveType) => { + const { data } = await axios.post("/shipment/save", params); + return data; +}; diff --git a/src/app/cart/shipping/page.tsx b/src/app/cart/shipping/page.tsx index 26bc9e8..f948780 100644 --- a/src/app/cart/shipping/page.tsx +++ b/src/app/cart/shipping/page.tsx @@ -2,19 +2,96 @@ import CartSummary from '@/components/CartSummary' import Layout from '@/hoc/Layout' import { NextPage } from 'next' -import CartItem from '../components/CartItem' +// import CartItem from '../components/CartItem' import TitleBack from '../components/TitleBack' +import ShippingMethodSelector from '../components/ShippingMethodSelector' import { Location } from 'iconsax-react' -import { useGetCart, useShippingCost } from '../hooks/useCartData' -import { CartItemType } from '../types/Types' +import { useGetCart, useSaveShipmentCart, useShippingCost } from '../hooks/useCartData' +// import { CartItemType } from '../types/Types' import { useGetProfile } from '@/app/profile/hooks/useProfileData' import Link from 'next/link' +import { useState, useEffect } from 'react' const CartShipping: NextPage = () => { const { data: cartData, isLoading: cartLoading, error: cartError } = useGetCart() const { data: shippingData, isLoading: shippingLoading, error: shippingError } = useShippingCost() const { data: profileData, isLoading: profileLoading, error: profileError } = useGetProfile() + const { mutate: saveShipmentCart } = useSaveShipmentCart() + + // State برای ذخیره انتخاب‌های روش ارسال + const [selectedShipments, setSelectedShipments] = useState>({}) + const [isSavingShipment, setIsSavingShipment] = useState(false) + + // بارگذاری انتخاب‌های قبلی از localStorage + useEffect(() => { + const savedShipments = localStorage.getItem('selectedShipments') + if (savedShipments) { + try { + setSelectedShipments(JSON.parse(savedShipments)) + } catch (error) { + console.error('Error parsing saved shipments:', error) + } + } + }, []) + + // ذخیره انتخاب‌ها در localStorage + useEffect(() => { + if (Object.keys(selectedShipments).length > 0) { + localStorage.setItem('selectedShipments', JSON.stringify(selectedShipments)) + } + }, [selectedShipments]) + + // هندلر انتخاب روش ارسال + const handleShipmentSelect = (shopId: string, shipperId: number) => { + setSelectedShipments(prev => ({ + ...prev, + [shopId]: shipperId + })) + } + + // چک کردن اینکه آیا همه فروشگاه‌ها روش ارسال انتخاب کرده‌اند + const allShipmentsSelected = shippingData?.results?.shipping?.every(shop => + selectedShipments[shop.shopId] !== undefined + ) || false + + // هندلر پاک کردن همه انتخاب‌ها + const handleClearSelections = () => { + setSelectedShipments({}) + localStorage.removeItem('selectedShipments') + } + + // هندلر ذخیره انتخاب‌های ارسال و انتقال به صفحه پرداخت + const handleProceedToPayment = async () => { + if (!allShipmentsSelected) return + + setIsSavingShipment(true) + + try { + // تبدیل selectedShipments به فرمت مورد نیاز API + const shipmentsInfo = Object.entries(selectedShipments).map(([shopId, shipperId]) => ({ + shopId, + shipmentId: shipperId + })) + + // ذخیره انتخاب‌ها در API + await new Promise((resolve, reject) => { + saveShipmentCart({ shipmentsInfo }, { + onSuccess: () => resolve(), + onError: (error) => reject(error) + }) + }) + + // انتقال به صفحه پرداخت + window.location.href = '/cart/payment' + } catch (error) { + console.error('Error saving shipment cart:', error) + // در صورت خطا همچنان به صفحه پرداخت برویم + window.location.href = '/cart/payment' + } finally { + setIsSavingShipment(false) + } + } if (cartLoading || shippingLoading || profileLoading) { return ( @@ -50,9 +127,11 @@ const CartShipping: NextPage = () => { ) } - // محاسبه هزینه ارسال + // محاسبه هزینه ارسال بر اساس انتخاب‌های کاربر const totalShippingCost = shippingData?.results?.shipping?.reduce((total, shop) => { - return total + (shop.shippers?.[0]?.totalShippingCost || 0) + const selectedShipperId = selectedShipments[shop.shopId] + const selectedShipper = shop.shippers?.find(shipper => shipper.shipperId === selectedShipperId) + return total + (selectedShipper?.totalShippingCost || shop.shippers?.[0]?.totalShippingCost || 0) }, 0) || 0 // محاسبه قیمت نهایی @@ -82,7 +161,15 @@ const CartShipping: NextPage = () => { ویرایش -
+ + + + {/*
{items.map((item: CartItemType, index: number) => { if (!item || !item.product || !item.variant) { return null @@ -95,7 +182,7 @@ const CartShipping: NextPage = () => { /> ) })} -
+
*/}
{ discount={cart?.total_discount || 0} shippingCost={totalShippingCost} total={finalTotal} - confirmHref="/cart/payment" + onConfirm={handleProceedToPayment} confirmLabel="پرداخت" className="w-full" + confirmDisabled={!allShipmentsSelected} + isLoading={isSavingShipment} /> + {!allShipmentsSelected && ( +
+ لطفا برای همه فروشگاه‌ها روش ارسال انتخاب کنید +
+ )}
diff --git a/src/app/cart/types/Types.ts b/src/app/cart/types/Types.ts index 10fa6b2..1c46bf8 100644 --- a/src/app/cart/types/Types.ts +++ b/src/app/cart/types/Types.ts @@ -413,3 +413,23 @@ export type PaymentStatusResponseType = { }; }; }; + +// Shipment Save Types +export type ShipmentInfoType = { + shopId: string; + shipmentId: number; +}; + +export type ShipmentSaveType = { + shipmentsInfo: ShipmentInfoType[]; +}; + +// Shipment Cart Response Type +export type ShipmentCartResponseType = { + status: number; + success: boolean; + results: { + cart: CartType; + recommended: RecommendedProductType[]; + }; +}; diff --git a/src/app/profile/orders/[id]/page.tsx b/src/app/profile/orders/[id]/page.tsx index dff9517..2a04ae8 100644 --- a/src/app/profile/orders/[id]/page.tsx +++ b/src/app/profile/orders/[id]/page.tsx @@ -1,6 +1,6 @@ 'use client' import { FC, useState } from 'react' -import { ArrowRight2 } from 'iconsax-react' +import { ArrowLeft, TaskSquare } from 'iconsax-react' import { useRouter, useParams } from 'next/navigation' import PageLoading from '@/components/PageLoading' import Layout from '@/hoc/Layout' @@ -10,6 +10,7 @@ import OrderMainContent from './components/OrderMainContent' import CancelOrderModal from './components/CancelOrderModal' import { useCancelOrder, useGetOrderDetail } from '../hooks/useOrdersData' import { toast } from '@/components/Toast' +import { PRIMARY_COLOR } from '@/config/const' const OrderDetail: FC = () => { @@ -66,14 +67,21 @@ const OrderDetail: FC = () => {
-
- -
جزئیات سفارش
+
+
+ +
جزئیات سفارش
+
+ +
router.push(`/profile/orders/${id}/return`)}> + +
فرم درخواست مرجوعی
+
{cancelOrderMutation.isPending ? ( diff --git a/src/app/profile/orders/[id]/return/components/ProductSelection.tsx b/src/app/profile/orders/[id]/return/components/ProductSelection.tsx new file mode 100644 index 0000000..e394990 --- /dev/null +++ b/src/app/profile/orders/[id]/return/components/ProductSelection.tsx @@ -0,0 +1,96 @@ +'use client' +import React from 'react' +import Image from 'next/image' +import { Checkbox } from '@/components/ui/checkbox' +import { ShipmentItemDetail } from '../../../types/Types' + +interface ProductSelectionProps { + items: ShipmentItemDetail[] + selectedItems: string[] + onItemSelect: (itemId: string, checked: boolean) => void +} + +const ProductSelection: React.FC = ({ + items, + selectedItems, + onItemSelect +}) => { + return ( +
+ {items.length === 0 && ( +
+ هیچ آیتمی برای نمایش وجود ندارد +
+ )} + {items.map((item, index) => { + const availableQuantity = item.quantity - item.cancelled_quantity - item.returned_quantity + const canReturn = availableQuantity > 0 + + return ( +
+
+
+
+ onItemSelect(item._id, checked as boolean)} + disabled={!canReturn} + className={`w-5 h-5 ${!canReturn ? 'opacity-70' : ''}`} + /> +
+ + {item.product.title_fa} + +
+
+ {item.product.title_fa} +
+ +
+
+ مشکی +
+ +
+ کد کالا: {item.product._id || 'نامشخص'} +
+ +
+ فروشنده: سندس کالا +
+ +
+ وضعیت: آونگ +
+ +
+ {Intl.NumberFormat('fa-IR').format(item.selling_price)} تومان +
+ + {!canReturn && ( +
+ این آیتم قابل مرجوع کردن نیست +
+ )} + +
+
+
+ {index < items.length - 1 && ( +
+ )} +
+ ) + })} +
+ ) +} + +export default ProductSelection diff --git a/src/app/profile/orders/[id]/return/components/ReturnConfirmation.tsx b/src/app/profile/orders/[id]/return/components/ReturnConfirmation.tsx new file mode 100644 index 0000000..83699ea --- /dev/null +++ b/src/app/profile/orders/[id]/return/components/ReturnConfirmation.tsx @@ -0,0 +1,134 @@ +'use client' +import React from 'react' +import Image from 'next/image' +import { Button } from '@/components/ui/button' +import { ShipmentItemDetail } from '../../../types/Types' + +interface ReturnConfirmationProps { + selectedItems: ShipmentItemDetail[] + quantities: { [key: string]: number } + reason: string + description: string + images: File[] + imagePreviewUrls: string[] + returnReasons: Array<{ _id: string; title: string }> + onSubmit: () => void + onBack: () => void + isLoading: boolean +} + +const ReturnConfirmation: React.FC = ({ + selectedItems, + quantities, + reason, + description, + // images, + imagePreviewUrls, + returnReasons, + onSubmit, + onBack, + isLoading +}) => { + const selectedReason = returnReasons.find(r => r._id === reason) + + return ( +
+

تایید اطلاعات مرجوعی

+ + {/* محصولات انتخاب شده */} +
+

محصولات انتخاب شده

+ {selectedItems.map((item) => ( +
+
+ {item.product.title_fa} +
+
{item.product.title_fa}
+
+ تعداد مرجوعی: {quantities[item._id]} +
+
+ {Intl.NumberFormat('fa-IR').format(item.selling_price)} تومان +
+
+
+
+ ))} +
+ + {/* اطلاعات مرجوعی */} +
+

اطلاعات مرجوعی

+ +
+
+ دلیل مرجوعی: + {selectedReason?.title} +
+ +
+ توضیحات: +

{description}

+
+
+
+ + {/* تصاویر آپلود شده */} + {imagePreviewUrls.length > 0 && ( +
+

تصاویر آپلود شده

+
+ {imagePreviewUrls.map((url, index) => ( +
+ {`تصویر +
+ ))} +
+
+ )} + + {/* اخطار */} +
+
+ ! +
+ + لطفاً قبل از تایید نهایی، تمامی اطلاعات را بررسی کنید. پس از ثبت درخواست مرجوعی، امکان تغییر اطلاعات وجود ندارد. + +
+ + {/* دکمه‌های عملیات */} +
+ + +
+
+ ) +} + +export default ReturnConfirmation diff --git a/src/app/profile/orders/[id]/return/components/ReturnForm.tsx b/src/app/profile/orders/[id]/return/components/ReturnForm.tsx new file mode 100644 index 0000000..fdc67ee --- /dev/null +++ b/src/app/profile/orders/[id]/return/components/ReturnForm.tsx @@ -0,0 +1,257 @@ +'use client' +import React, { useState, useRef } from 'react' +import { useForm } from 'react-hook-form' +import { yupResolver } from '@hookform/resolvers/yup' +import * as yup from 'yup' +import Select from '@/components/Select' +import { Button } from '@/components/ui/button' +import { ShipmentItemDetail } from '../../../types/Types' +import Image from 'next/image' +import { CloseCircle, DocumentUpload } from 'iconsax-react' + +interface ReturnFormData { + quantities: { [key: string]: number } + reason: string + description: string +} + +interface ReturnFormProps { + selectedItems: ShipmentItemDetail[] + onSubmit: (data: { quantities: { [key: string]: number }; reason: string; description: string; images: File[] }) => void + onBack: () => void + isLoading: boolean + returnReasons: Array<{ _id: string; title: string }> +} + +const returnFormSchema = yup.object({ + reason: yup.string().required('دلیل مرجوعی را انتخاب کنید'), + description: yup.string().required('توضیحات را وارد کنید').min(10, 'توضیحات باید حداقل ۱۰ کاراکتر باشد'), + quantities: yup.object().test('quantities', 'تعداد مرجوعی باید معتبر باشد', function (value: Record) { + if (!value) return false + const selectedItems = this.parent.selectedItems || [] + for (const item of selectedItems) { + const quantity = value[item._id] + const availableQuantity = item.quantity - item.cancelled_quantity - item.returned_quantity + if (!quantity || quantity < 1 || quantity > availableQuantity) { + return false + } + } + return true + }) +}) + +const ReturnForm: React.FC = ({ + selectedItems, + onSubmit, + onBack, + isLoading, + returnReasons +}) => { + const [images, setImages] = useState([]) + const [imagePreviewUrls, setImagePreviewUrls] = useState([]) + const fileInputRef = useRef(null) + + const { + register, + handleSubmit, + watch, + setValue, + formState: { errors }, + reset + } = useForm({ + resolver: yupResolver(returnFormSchema), + defaultValues: { + quantities: selectedItems.reduce((acc, item) => ({ + ...acc, + [item._id]: 1 + }), {}) + } + }) + + const handleFormSubmit = (data: ReturnFormData) => { + onSubmit({ + quantities: data.quantities, + reason: data.reason, + description: data.description, + images + }) + reset() + } + + const handleImageUpload = (event: React.ChangeEvent) => { + 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 + }) + + if (validFiles.length !== files.length) { + alert('فقط فایل‌های JPG و PNG با حداکثر حجم ۱۰ مگابایت مجاز هستند') + } + + const newImages = [...images, ...validFiles].slice(0, 5) // حداکثر ۵ عکس + setImages(newImages) + + const newPreviewUrls = newImages.map(file => URL.createObjectURL(file)) + setImagePreviewUrls(newPreviewUrls) + } + + const removeImage = (index: number) => { + const newImages = images.filter((_, i) => i !== index) + const newPreviewUrls = imagePreviewUrls.filter((_, i) => i !== index) + setImages(newImages) + setImagePreviewUrls(newPreviewUrls) + } + + return ( +
+

تکمیل اطلاعات مرجوعی

+ + {/* انتخاب تعداد مرجوعی برای هر آیتم */} +
+

تعداد مرجوعی

+ {selectedItems.map((item) => { + const availableQuantity = item.quantity - item.cancelled_quantity - item.returned_quantity + return ( +
+
+ {item.product.title_fa} +
+
{item.product.title_fa}
+
قابل مرجوع: {availableQuantity}
+
+
+
+ + +
+
+ ) + })} +
+ +
+ {/* انتخاب دلیل مرجوعی */} +