order detail + change status in all state

This commit is contained in:
hamid zarghami
2025-12-20 14:33:19 +03:30
parent 566c4cb97b
commit de8815f0d8
13 changed files with 1065 additions and 300 deletions
+165 -239
View File
@@ -1,42 +1,46 @@
import Select from '@/components/Select'
import Table from '@/components/Table'
import { Location } from 'iconsax-react'
import { type FC, useMemo } from 'react'
import type { ColumnType, RowDataType } from '@/components/types/TableTypes'
import { useGetOrderById } from './hooks/useOrderData'
import { type FC, useMemo, useState } from 'react'
import {
useGetOrderById,
useVerifyCashPayment,
useChangeOrderStatus,
useRefundPayment
} from './hooks/useOrderData'
import { useParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { formatPrice, formatOptionalDate, formatFaNumber } from '@/helpers/func'
import { formatFaNumber } from '@/helpers/func'
import { OrderStatus, PaymentStatusEnum } from './enum/Enum'
import type { OrderItem as OrderItemType } from './types/Types'
interface OrderItemTable extends RowDataType {
image: string
title: string
quantity: number
amount: string
}
import { PaymentMethodEnum } from '@/pages/paymentMethods/enum/Enum'
import { DeliveryMethodEnum } from '@/pages/shipmentMethod/enum/Enum'
import ModalConfrim from '@/components/ModalConfrim'
import CustomerInfo from './components/CustomerInfo'
import OrderItemsDetails from './components/OrderItemsDetails'
import PaymentInfo from './components/PaymentInfo'
import OrderActions from './components/OrderActions'
const OrderDetails: FC = () => {
const { id } = useParams()
const { data: orderData, isLoading } = useGetOrderById(id!)
const { t } = useTranslation('global')
const { mutate: verifyCashPayment, isPending: isVerifyingPayment } = useVerifyCashPayment()
const { mutate: changeOrderStatus, isPending: isChangingStatus } = useChangeOrderStatus()
const { mutate: refundPayment, isPending: isRefunding } = useRefundPayment()
const [showCancelModal, setShowCancelModal] = useState(false)
const [showRefundModal, setShowRefundModal] = useState(false)
const order = orderData?.data
const statusItems = useMemo(() => {
const statuses: string[] = [
OrderStatus.Pending,
OrderStatus.Confirmed,
OrderStatus.Preparing,
OrderStatus.RejectedByRestaurant,
OrderStatus.CancelledByUser,
OrderStatus.CancelledBySystem,
OrderStatus.ReadyForCustomerPickup,
OrderStatus.ReadyForDineIn,
OrderStatus.ReadyForDeliveryCar,
OrderStatus.ReadyForDeliveryCourier,
OrderStatus.Delivered,
OrderStatus.PENDING_PAYMENT,
OrderStatus.PAID,
OrderStatus.CONFIRMED,
OrderStatus.PREPARING,
OrderStatus.READY,
OrderStatus.SHIPPED,
OrderStatus.COMPLETED,
OrderStatus.CANCELED,
]
return statuses.map((status) => ({
value: status,
@@ -46,17 +50,14 @@ const OrderDetails: FC = () => {
const getStatusColor = (status: string): string => {
const colorMap: Record<string, string> = {
'pending': '#FFEECC',
'pendingPayment': '#FFEECC',
'paid': '#E3F2FD',
'confirmed': '#E3F2FD',
'preparing': '#FFF3E0',
'rejectedByRestaurant': '#FFEBEE',
'cancelledByUser': '#FFEBEE',
'cancelledBySystem': '#FFEBEE',
'readyForCustomerPickup': '#E3F2FD',
'readyForDineIn': '#E3F2FD',
'readyForDeliveryCar': '#E3F2FD',
'readyForDeliveryCourier': '#E3F2FD',
'delivered': '#E8F5E9',
'ready': '#E3F2FD',
'shipped': '#E3F2FD',
'completed': '#E8F5E9',
'canceled': '#FFEBEE',
}
return colorMap[status] || '#FFEECC'
}
@@ -88,55 +89,6 @@ const OrderDetails: FC = () => {
return textMap[status] || status
}
const orderItems: OrderItemTable[] = useMemo(() => {
if (!order?.items) return []
return order.items.map((item: OrderItemType) => ({
id: item.id,
image: item.food?.images?.[0] || '',
title: item.food?.title || '-',
quantity: item.quantity,
amount: formatPrice(item.totalPrice)
}))
}, [order])
const columns: ColumnType<OrderItemTable>[] = [
{
title: 'تصویر',
key: 'image',
render: (item) => (
<div>
{item.image ? (
<img
src={item.image}
alt={item.title}
className='w-12 h-12 rounded-lg object-cover'
/>
) : (
<div className='w-12 h-12 rounded-lg bg-gray-200 flex items-center justify-center'>
<span className='text-xs text-gray-400'>بدون تصویر</span>
</div>
)}
</div>
)
},
{
title: 'عنوان',
key: 'title',
align: 'right'
},
{
title: 'تعداد',
key: 'quantity',
align: 'center',
render: (item) => formatFaNumber(item.quantity)
},
{
title: 'مبلغ',
key: 'amount',
align: 'right'
}
]
if (isLoading) {
return (
<div className='mt-5 flex items-center justify-center h-64'>
@@ -153,8 +105,89 @@ const OrderDetails: FC = () => {
)
}
const customerName = [order.user?.firstName, order.user?.lastName].filter(Boolean).join(' ') || '-'
const address = order.address ? `${order.address.city || ''} - ${order.address.address || ''}`.trim() : '-'
// منطق نمایش دکمه‌ها
const isCashPayment = order.paymentMethod?.method === PaymentMethodEnum.Cash
const isPendingPayment = order.status === OrderStatus.PENDING_PAYMENT
const isOrderPaid = order.status === OrderStatus.PAID
const isConfirmed = order.status === OrderStatus.CONFIRMED
const isPreparing = order.status === OrderStatus.PREPARING
const isCanceled = order.status === OrderStatus.CANCELED
const isOnlineOrWalletPayment = order.paymentMethod?.method === PaymentMethodEnum.Online || order.paymentMethod?.method === PaymentMethodEnum.Wallet
const isCourierDelivery = order.deliveryMethod?.method === DeliveryMethodEnum.DeliveryCourier
// نمایش دکمه تایید پرداخت نقدی
const showVerifyCashPaymentButton = isCashPayment && isPendingPayment
// نمایش دکمه تایید سفارش (confirm)
// سفارشات پرداخت آنلاین و کیف پولی فقط در صورتی می‌توانند confirm شوند که status آنها paid باشد
// سفارشات پرداخت نقدی می‌توانند تایید شوند
const canConfirm = !isConfirmed && !isPreparing && !isCanceled && (
isCashPayment ||
(isOnlineOrWalletPayment && isOrderPaid)
)
// نمایش دکمه ارسال به آشپزخانه
const showSendToKitchenButton = isConfirmed
// نمایش دکمه کنسل (ادمین همیشه می‌تواند، مگر اینکه سفارش کنسل شده باشد)
const showCancelButton = !isCanceled
const handleVerifyCashPayment = () => {
const paymentId = order.paymentId || order.paymentMethod?.id
if (paymentId) {
verifyCashPayment(paymentId, {
onSuccess: () => {
// Success handled by query invalidation
}
})
}
}
const handleConfirmOrder = () => {
changeOrderStatus({
orderId: order.id,
status: OrderStatus.CONFIRMED
})
}
const handleSendToKitchen = () => {
changeOrderStatus({
orderId: order.id,
status: OrderStatus.PREPARING
})
}
const handleDeliverToCourier = () => {
changeOrderStatus({
orderId: order.id,
status: OrderStatus.SHIPPED
})
}
const handleOrderReady = () => {
changeOrderStatus({
orderId: order.id,
status: OrderStatus.READY
})
}
const handleCancelOrder = (description?: string) => {
changeOrderStatus({
orderId: order.id,
status: OrderStatus.CANCELED,
params: description ? { description } : undefined
})
setShowCancelModal(false)
}
const handleRefund = (text?: string) => {
if (!text) return
refundPayment({
orderId: order.id,
params: { description: text }
})
setShowRefundModal(false)
}
return (
<div className='mt-5'>
@@ -174,164 +207,57 @@ const OrderDetails: FC = () => {
</div>
<div className='flex gap-7 mt-9'>
<div className='flex-1'>
<div className='w-full bg-white rounded-4xl p-8'>
<div className='flex justify-between items-center'>
<div className='text-lg font-light'>
اطلاعات مشتری
</div>
</div>
<div className='mt-8 flex flex-wrap gap-y-7 gap-20 text-[13px] font-light border-b border-dashed border-border pb-8'>
<div className='flex gap-2'>
<div className='text-description'>
شماره سفارش:
</div>
<div>{formatFaNumber(order.orderNumber)}</div>
</div>
<div className='flex gap-2'>
<div className='text-description'>
نام مشتری :
</div>
<div>{customerName}</div>
</div>
<div className='flex gap-2'>
<div className='text-description'>نوع سفارش :</div>
<div>{order.deliveryMethod?.title || '-'}</div>
</div>
<div className='flex gap-2'>
<div className='text-description'>زمان سفارش : </div>
<div>
{formatOptionalDate(order.createdAt, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})}
</div>
</div>
</div>
<div className='mt-8 flex gap-20 text-[13px] font-light'>
<div className='flex gap-2'>
<div className='text-description'>شماره تماس:</div>
<div>{order.user?.phone || '-'}</div>
</div>
<div className='flex gap-2 items-center'>
<div className='text-description'>آدرس:</div>
<div>{address}</div>
{order.address?.latitude && order.address?.longitude && (
<a
href={`https://www.google.com/maps?q=${order.address.latitude},${order.address.longitude}`}
target='_blank'
rel='noopener noreferrer'
className='flex gap-1 items-center cursor-pointer'
>
<Location size={17} color='#0047FF' />
<div className='text-[13px] text-blue-600'>نقشه</div>
</a>
)}
</div>
</div>
</div>
<div className='w-full bg-white rounded-4xl p-8 mt-8'>
<div className='text-lg font-light mb-6'>
جزییات سفارش
</div>
<Table
columns={columns}
data={orderItems}
showHeader={true}
className='mt-0'
/>
{order.tax > 0 && (
<div className='flex justify-between items-center h-14 text-[13px] mt-5 border-t border-border'>
<div>
مالیات
</div>
<div>
{formatPrice(order.tax)}
</div>
</div>
)}
<div className='flex font-bold justify-between items-center h-14 text-[13px] border-t border-border'>
<div>
جمع کل
</div>
<div>
{formatPrice(order.total)}
</div>
</div>
</div>
<div className='flex-1 space-y-8'>
<CustomerInfo order={order} />
<OrderItemsDetails order={order} getStatusColor={getStatusColor} />
</div>
<div className='w-[330px] h-fit bg-white rounded-4xl p-8'>
<div className='flex justify-between items-center'>
<div className='font-light'>
اطلاعات پرداخت
</div>
<div className='flex gap-1.5 items-center'>
<div className={`size-2 rounded-full ${getPaymentStatusColor(order.paymentStatus)}`}></div>
<div className={`text-[13px] ${getPaymentStatusTextColor(order.paymentStatus)}`}>
{getPaymentStatusText(order.paymentStatus)}
</div>
</div>
</div>
<div className='mt-10'>
<div className='flex text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className='text-description'>
نوع پرداخت
</div>
<div>{order.paymentMethod?.title || '-'}</div>
</div>
<div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className='text-description'>
مبلغ سفارش
</div>
<div>{formatPrice(order.subTotal)}</div>
</div>
{order.totalDiscount > 0 && (
<div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className='text-description'>
تخفیف
</div>
<div>{formatPrice(order.totalDiscount)}</div>
</div>
)}
<div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className='text-description'>
هزینه ارسال
</div>
<div>{formatPrice(order.deliveryFee)}</div>
</div>
{order.tax > 0 && (
<div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className='text-description'>
مالیات
</div>
<div>{formatPrice(order.tax)}</div>
</div>
)}
<div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className='text-description'>
مبلغ کل
</div>
<div>{formatPrice(order.total)}</div>
</div>
</div>
<div>
<PaymentInfo
order={order}
getPaymentStatusColor={getPaymentStatusColor}
getPaymentStatusTextColor={getPaymentStatusTextColor}
getPaymentStatusText={getPaymentStatusText}
/>
<OrderActions
showVerifyCashPaymentButton={showVerifyCashPaymentButton}
canConfirm={canConfirm}
showSendToKitchenButton={showSendToKitchenButton}
showCancelButton={showCancelButton}
isPreparing={isPreparing}
isCourierDelivery={isCourierDelivery}
isCanceled={isCanceled}
isVerifyingPayment={isVerifyingPayment}
isChangingStatus={isChangingStatus}
onVerifyCashPayment={handleVerifyCashPayment}
onConfirmOrder={handleConfirmOrder}
onSendToKitchen={handleSendToKitchen}
onDeliverToCourier={handleDeliverToCourier}
onOrderReady={handleOrderReady}
onCancelOrder={() => setShowCancelModal(true)}
/>
</div>
</div>
{/* مدال کنسل سفارش */}
<ModalConfrim
isOpen={showCancelModal}
close={() => setShowCancelModal(false)}
onConfrim={handleCancelOrder}
isloading={isChangingStatus}
label='آیا از لغو سفارش مطمئن هستید؟'
isHasDescription
/>
{/* مدال ریفاند */}
<ModalConfrim
isOpen={showRefundModal}
close={() => setShowRefundModal(false)}
onConfrim={handleRefund}
isloading={isRefunding}
label='آیا از ریفاند این سفارش مطمئن هستید؟'
isHasDescription
/>
</div>
)
}