order detail

This commit is contained in:
hamid zarghami
2025-08-26 15:58:24 +03:30
parent 90bf32f8fd
commit 09a210c943
6 changed files with 634 additions and 3 deletions
+417
View File
@@ -0,0 +1,417 @@
'use client'
import { FC, useState } from 'react'
import { ArrowRight2, ShieldTick, Truck, MessageText, CloseCircle } from 'iconsax-react'
import { useRouter, useParams } from 'next/navigation'
import { useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import * as yup from 'yup'
import PageLoading from '@/components/PageLoading'
import TrackCode from './components/TrackCode'
import { Button } from '@/components/ui/button'
import Modal from '@/components/Modal'
import Select from '@/components/Select'
import Input from '@/components/Input'
import withLayout from '@/hoc/withLayout'
import Image from 'next/image'
// Mock data
const mockOrderDetail = {
payment: {
transaction_id: 'TRX123456789'
},
order: {
_id: 'ORD001',
user: {
fullName: 'احمد محمدی'
},
shipmentAddress: {
postalCode: '1234567890',
phone: '09123456789',
address: 'تهران، خیابان ولیعصر، پلاک 123'
},
createdAt: '1402/12/15',
orderItems: {
_id: 'ITEM001',
itemsCount: 3,
status: 'processing',
totalShipmentCost: '25000 تومان',
totalPaymentPrice: '450000',
shipper: {
name: 'پست ایران',
deliveryTime: 3
},
shipmentItems: [
{
_id: 'SHIP001',
product: {
_id: 'PROD001',
title_fa: 'لپ‌تاپ اپل مک‌بوک پرو 13 اینچی',
imagesUrl: {
cover: 'https://via.placeholder.com/80x80'
}
},
quantity: 1,
cancelled_quantity: 0,
returned_quantity: 0,
selling_price: 450000,
variant: {
color: {
name: 'نقره‌ای'
},
size: {
name: '13 اینچ'
},
meterages: {
name: '256GB'
},
warranty: {
duration: '2 سال',
name: 'گارانتی'
}
}
}
]
}
}
}
const mockCancelReasons = [
{ _id: 'REASON1', title: 'تغییر نظر در خرید' },
{ _id: 'REASON2', title: 'یافتن قیمت بهتر' },
{ _id: 'REASON3', title: 'مشکل در محصول' },
{ _id: 'REASON4', title: 'تأخیر در ارسال' }
]
// Form validation schema
const cancelOrderSchema = yup.object({
reason: yup.string().required('لطفا دلیل لغو سفارش را انتخاب کنید'),
description: yup.string().required('لطفا توضیحات لغو سفارش را وارد کنید'),
count: yup.string().required('تعداد لغو سفارش را وارد کنید')
})
type CancelOrderFormData = yup.InferType<typeof cancelOrderSchema>
const OrderDetail: FC = () => {
const { id } = useParams()
const [showModalCancel, setShowModalCancel] = useState<boolean>(false)
const [isLoading, setIsLoading] = useState<boolean>(false)
const router = useRouter()
// Form with react-hook-form
const {
register,
handleSubmit,
watch,
setValue,
formState: { errors },
reset
} = useForm<CancelOrderFormData>({
resolver: yupResolver(cancelOrderSchema)
})
const getStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'processing': 'در حال پردازش',
'shipped': 'ارسال شده',
'delivered': 'تحویل داده شده',
'cancelled': 'لغو شده'
}
return statusMap[status] || status
}
const onSubmit = async () => {
setIsLoading(true)
// Simulate API call
setTimeout(() => {
setIsLoading(false)
setShowModalCancel(false)
reset() // Reset form
alert('درخواست لغو سفارش با موفقیت ثبت شد')
router.push('/profile/orders')
}, 1000)
}
return (
<div className='w-full lg:px-10 px-4 mt-20'>
<div className='border p-6 rounded-2xl text-sm'>
<div className='flex gap-2 items-center border-b pb-6'>
<button
onClick={() => router.push('/profile/orders')}
className='p-2 hover:bg-gray-100 rounded-lg transition-colors'
>
<ArrowRight2 size={20} className="rotate-180" />
</button>
<div className="text-lg font-semibold">جزئیات سفارش</div>
</div>
{isLoading ? (
<PageLoading />
) : (
<div className='mt-6 lg:flex-row flex-col flex gap-6 items-start'>
{/* Sidebar */}
<div className='lg:w-[280px] w-full p-5 rounded-2xl shadow-lg'>
<div className='flex items-center gap-2'>
<div className='text-[#7F7F7F]'>شناسه تراکنش :</div>
<div className="font-mono">
{mockOrderDetail.payment.transaction_id}
</div>
</div>
<div className='flex items-center gap-2 mt-4'>
<div className='text-[#7F7F7F]'>گیرنده :</div>
<div>
{mockOrderDetail.order.user.fullName}
</div>
</div>
<div className='flex items-center gap-2 mt-4'>
<div className='text-[#7F7F7F]'>کدپستی :</div>
<div>
{mockOrderDetail.order.shipmentAddress.postalCode}
</div>
</div>
<div className='flex items-center gap-2 mt-4'>
<div className='text-[#7F7F7F] whitespace-nowrap'>تاریخ ثبت سفارش :</div>
<div>
{mockOrderDetail.order.createdAt}
</div>
</div>
<div className='flex items-center gap-2 mt-4'>
<div className='text-[#7F7F7F]'>شماره موبایل :</div>
<div>
{mockOrderDetail.order.shipmentAddress.phone}
</div>
</div>
<div className='flex items-start gap-2 mt-4'>
<div className='text-[#7F7F7F] whitespace-nowrap'>آدرس :</div>
<div>
{mockOrderDetail.order.shipmentAddress.address}
</div>
</div>
<TrackCode orderId={Number(id) || 0} />
<Button
className='mt-3 w-full'
variant="outline"
>
مشاهده فاکتور
</Button>
</div>
{/* Main Content */}
<div className='flex-1 w-full bg-white shadow-lg p-6 rounded-2xl'>
<div className='flex md:flex-row flex-col gap-4 justify-between md:items-center'>
<div className='flex md:flex-row flex-col gap-4'>
<div className='flex items-center gap-2'>
<div className='text-[#7F7F7F]'>تعداد مرسوله :</div>
<div>1</div>
</div>
<div className='flex items-center gap-2'>
<div className='text-[#7F7F7F]'>تعداد اقلام :</div>
<div>
{mockOrderDetail.order.orderItems.itemsCount}
</div>
</div>
</div>
<div className='text-primary font-semibold'>
{getStatusText(mockOrderDetail.order.orderItems.status)}
</div>
</div>
<div className='flex items-center gap-2 mt-6'>
<div className='text-[#7F7F7F]'>حداکثر مهلت ارسال :</div>
<div>
{mockOrderDetail.order.orderItems.shipper.deliveryTime} روز
</div>
</div>
<div className='flex md:flex-row flex-col gap-3 justify-between mt-6'>
<div className='flex items-center gap-2'>
<div className='text-[#7F7F7F]'>هزینه ارسال :</div>
<div>
{mockOrderDetail.order.orderItems.totalShipmentCost}
</div>
</div>
<div className='flex items-center gap-2'>
<div className='text-[#7F7F7F]'>مبلغ مرسوله :</div>
<div>
{mockOrderDetail.order.orderItems.totalPaymentPrice} تومان
</div>
</div>
<div className='flex items-center gap-2'>
<div className='text-[#7F7F7F]'>شناسه سفارش :</div>
<div className="font-mono">
{mockOrderDetail.order._id}
</div>
</div>
</div>
{/* Product Items */}
{mockOrderDetail.order.orderItems.shipmentItems.map((item) => (
<div key={item.product._id} className='mt-8 py-7 border-t items-start flex gap-6 sm:flex-row flex-col'>
<Image
src={item.product.imagesUrl.cover}
width={80}
height={80}
className='w-20 h-20 object-cover rounded-lg border'
alt={item.product.title_fa}
/>
<div className='flex-1 w-full'>
<div className='truncate max-w-[90%] font-medium'>
{item.product.title_fa}
</div>
<div className='mt-4 text-[#999999] space-y-3'>
<div className='flex gap-2'>
<div>تعداد اقلام :</div>
<div>{item.quantity}</div>
</div>
<div className='flex gap-2'>
<div>تعداد کنسلی :</div>
<div>{item.cancelled_quantity}</div>
</div>
<div className='flex gap-2'>
<div>تعداد مرجوعی :</div>
<div>{item.returned_quantity}</div>
</div>
<div className='flex gap-3 items-center'>
<div className='size-3 bg-black rounded-full'></div>
<div>
{item.variant.color?.name ||
item.variant.size?.name ||
item.variant.meterages?.name}
</div>
</div>
<div className='flex gap-3 items-center'>
<MessageText size={20} className="text-gray-400" />
<div className="font-mono">
SKP_{item.product._id}
</div>
</div>
<div className='flex gap-3 items-center'>
<ShieldTick size={20} className="text-green-500" />
<div>
گارانتی {item.variant.warranty.duration} {item.variant.warranty.name}
</div>
</div>
<div className='flex gap-3 items-center'>
<Truck size={20} className="text-blue-500" />
<div>
{mockOrderDetail.order.orderItems.shipper.name}
</div>
</div>
</div>
<div className='mt-8 flex justify-between items-center'>
<div>
<div className='text-lg font-semibold'>
{Intl.NumberFormat('fa-IR').format(item.selling_price)}
</div>
<div className='text-xs mt-1 text-gray-500'>تومان</div>
</div>
{item.quantity !== item.cancelled_quantity && (
<button
onClick={() => setShowModalCancel(true)}
className='flex cursor-pointer gap-2 text-[#B3261E] hover:bg-red-50 p-2 rounded-lg transition-colors'
>
<div>لغو سفارش</div>
<CloseCircle size={16} />
</button>
)}
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
{/* Cancel Order Modal */}
<Modal
open={showModalCancel}
close={() => setShowModalCancel(false)}
isHeader
title='درخواست لغو سفارش'
>
<form onSubmit={handleSubmit(onSubmit)} className='mt-6'>
<Select
placeholder='دلیل لغو سفارش'
items={mockCancelReasons.map((item) => ({
label: item.title,
value: item._id
}))}
className='bg-white border'
value={watch('reason')}
name='reason'
onChange={(e) => setValue('reason', e.target.value)}
error_text={errors.reason?.message}
/>
<Input
variant='floating_outlined'
label='تعداد'
className='mt-6'
{...register('count')}
error_text={errors.count?.message}
/>
<div className='mt-6'>
<div className="text-sm font-medium mb-2">توضیحات :</div>
<textarea
{...register('description')}
className='h-[123px] rounded-xl p-3 text-sm border w-full resize-none focus:outline-none focus:ring-2 focus:ring-primary/20'
placeholder='متن توضیحات خود را بنویسید'
/>
{errors.description && (
<div className='text-xs text-red-500 mt-1'>
{errors.description.message}
</div>
)}
</div>
<div className='flex gap-2 text-xs text-[#7F7F7F] mt-4 p-3 bg-blue-50 rounded-lg'>
<div className="size-5 bg-blue-100 rounded-full flex items-center justify-center">
<span className="text-blue-600 text-xs">!</span>
</div>
<span>
توجه داشته باشید که بعد از ثبت لغو سفارش طبق
</span>
<span className='text-primary font-medium'>
جدول جرائم
</span>
<span>
جریمه میشود
</span>
</div>
<div className='mt-6'>
<Button
type="submit"
className='w-full'
disabled={isLoading}
isLoading={isLoading}
>
لغو سفارش
</Button>
</div>
</form>
</Modal>
</div>
)
}
export default withLayout(OrderDetail)