order detail + change status in all state
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
VITE_TOKEN_NAME = 'dmnu_a_t'
|
||||
VITE_REFRESH_TOKEN_NAME = 'dmnu-a-rt'
|
||||
|
||||
VITE_BASE_URL = 'https://dmenuplus-api.dev.danakcorp.com'
|
||||
# VITE_BASE_URL = 'http://192.168.1.112:4001'
|
||||
# VITE_SOCKET_URL = 'https://dmenuplus-api.dev.danakcorp.com/pager'
|
||||
# VITE_BASE_URL = 'http://192.168.99.242:4000'
|
||||
|
||||
VITE_SOCKET_URL = 'https://dmenuplus-api.dev.danakcorp.com'
|
||||
+5
-5
@@ -47,9 +47,9 @@ export const formatFaNumber = (value?: number | null): string => {
|
||||
};
|
||||
|
||||
export const formatPrice = (price: number): string => {
|
||||
return new Intl.NumberFormat("fa-IR", {
|
||||
style: "currency",
|
||||
currency: "IRR",
|
||||
minimumFractionDigits: 0,
|
||||
}).format(price);
|
||||
return (
|
||||
new Intl.NumberFormat("fa-IR", {
|
||||
minimumFractionDigits: 0,
|
||||
}).format(price) + " تومان"
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
export const formatPrice = (price: number): string => {
|
||||
return new Intl.NumberFormat('fa-IR', {
|
||||
style: 'currency',
|
||||
currency: 'IRR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(price)
|
||||
}
|
||||
return (
|
||||
new Intl.NumberFormat("fa-IR", {
|
||||
minimumFractionDigits: 0,
|
||||
}).format(price) + " تومان"
|
||||
);
|
||||
};
|
||||
|
||||
export const formatTime = (minutes: number): string => {
|
||||
return `${minutes} دقیقه`
|
||||
}
|
||||
|
||||
return `${minutes} دقیقه`;
|
||||
};
|
||||
|
||||
+165
-239
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
import { Location, User, Call, Cake, Heart } from 'iconsax-react'
|
||||
import type { FC } from 'react'
|
||||
import type { Order } from '../types/Types'
|
||||
import { formatOptionalDate, formatFaNumber } from '@/helpers/func'
|
||||
|
||||
interface CustomerInfoProps {
|
||||
order: Order
|
||||
}
|
||||
|
||||
const CustomerInfo: FC<CustomerInfoProps> = ({ order }) => {
|
||||
const customerName = [order.user?.firstName, order.user?.lastName].filter(Boolean).join(' ') || '-'
|
||||
const fullAddress = order.userAddress
|
||||
? `${order.userAddress.province || ''}، ${order.userAddress.city || ''}، ${order.userAddress.address || ''}، کد پستی: ${order.userAddress.postalCode || '-'}`.trim()
|
||||
: '-'
|
||||
|
||||
return (
|
||||
<div className='w-full bg-white rounded-4xl p-8'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='text-lg font-light flex items-center gap-2'>
|
||||
<User color='#000' size={20} />
|
||||
اطلاعات مشتری
|
||||
</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 className='flex items-center gap-2'>
|
||||
{order.user?.avatarUrl && (
|
||||
<img
|
||||
src={order.user.avatarUrl}
|
||||
alt={customerName}
|
||||
className='w-8 h-8 rounded-full object-cover'
|
||||
/>
|
||||
)}
|
||||
<span>{customerName}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>شماره تماس:</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Call size={14} />
|
||||
{order.user?.phone || '-'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{order.user?.birthDate && (
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>تاریخ تولد:</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Cake size={14} />
|
||||
{formatOptionalDate(order.user.birthDate, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{order.user?.marriageDate && (
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>تاریخ ازدواج:</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Heart size={14} />
|
||||
{formatOptionalDate(order.user.marriageDate, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>جنسیت:</div>
|
||||
<div>{order.user?.gender ? 'مرد' : 'زن'}</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>وضعیت:</div>
|
||||
<div className={`px-2 py-1 rounded text-xs ${order.user?.isActive ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
|
||||
{order.user?.isActive ? 'فعال' : 'غیرفعال'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{order.user?.referrer && (
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>معرف:</div>
|
||||
<div>{order.user.referrer}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{order.userAddress && (
|
||||
<div className='mt-8 space-y-4 text-[13px] font-light'>
|
||||
<div className='flex gap-2 items-start'>
|
||||
<div className='text-description flex items-center gap-1'>
|
||||
<Location size={14} />
|
||||
آدرس کامل:
|
||||
</div>
|
||||
<div className='flex-1'>{fullAddress}</div>
|
||||
</div>
|
||||
|
||||
{order.userAddress.fullName && (
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>نام گیرنده:</div>
|
||||
<div>{order.userAddress.fullName}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='flex gap-2 items-center'>
|
||||
<div className='text-description'>شماره تماس گیرنده:</div>
|
||||
<div>{order.userAddress.phone || '-'}</div>
|
||||
</div>
|
||||
|
||||
{order.userAddress?.latitude && order.userAddress?.longitude && (
|
||||
<a
|
||||
href={`https://www.google.com/maps?q=${order.userAddress.latitude},${order.userAddress.longitude}`}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='flex gap-1 items-center cursor-pointer text-blue-600 hover:text-blue-800'
|
||||
>
|
||||
<Location size={17} color='#0047FF' />
|
||||
<div className='text-[13px]'>مشاهده روی نقشه</div>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{order.carAddress && (
|
||||
<div className={`${order.userAddress ? 'mt-6 pt-6 border-t' : 'mt-8'} border-dashed border-border`}>
|
||||
<div className='text-description mb-4'>اطلاعات خودرو:</div>
|
||||
<div className='flex flex-wrap gap-y-4 gap-8 text-[13px]'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>رنگ:</div>
|
||||
<div>{order.carAddress.carColor}</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>مدل:</div>
|
||||
<div>{order.carAddress.carModel}</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>پلاک:</div>
|
||||
<div>{order.carAddress.plateNumber}</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>شماره تماس:</div>
|
||||
<div>{order.carAddress.phone}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomerInfo
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import Button from '@/components/Button'
|
||||
import type { FC } from 'react'
|
||||
|
||||
interface OrderActionsProps {
|
||||
showVerifyCashPaymentButton: boolean
|
||||
canConfirm: boolean
|
||||
showSendToKitchenButton: boolean
|
||||
showCancelButton: boolean
|
||||
isPreparing: boolean
|
||||
isCourierDelivery: boolean
|
||||
isCanceled: boolean
|
||||
isVerifyingPayment: boolean
|
||||
isChangingStatus: boolean
|
||||
onVerifyCashPayment: () => void
|
||||
onConfirmOrder: () => void
|
||||
onSendToKitchen: () => void
|
||||
onDeliverToCourier: () => void
|
||||
onOrderReady: () => void
|
||||
onCancelOrder: () => void
|
||||
}
|
||||
|
||||
const OrderActions: FC<OrderActionsProps> = ({
|
||||
showVerifyCashPaymentButton,
|
||||
canConfirm,
|
||||
showSendToKitchenButton,
|
||||
showCancelButton,
|
||||
isPreparing,
|
||||
isCourierDelivery,
|
||||
isCanceled,
|
||||
isVerifyingPayment,
|
||||
isChangingStatus,
|
||||
onVerifyCashPayment,
|
||||
onConfirmOrder,
|
||||
onSendToKitchen,
|
||||
onDeliverToCourier,
|
||||
onOrderReady,
|
||||
onCancelOrder
|
||||
}) => {
|
||||
if (isCanceled) return null
|
||||
|
||||
return (
|
||||
<div className='mt-6 flex flex-col gap-3'>
|
||||
{showVerifyCashPaymentButton && (
|
||||
<Button
|
||||
label='تایید پرداخت'
|
||||
onClick={onVerifyCashPayment}
|
||||
isloading={isVerifyingPayment}
|
||||
className='bg-green-600 hover:bg-green-700'
|
||||
/>
|
||||
)}
|
||||
|
||||
{canConfirm && (
|
||||
<Button
|
||||
label='تایید سفارش'
|
||||
onClick={onConfirmOrder}
|
||||
isloading={isChangingStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showSendToKitchenButton && (
|
||||
<Button
|
||||
label='ارسال به آشپزخانه'
|
||||
onClick={onSendToKitchen}
|
||||
isloading={isChangingStatus}
|
||||
className='bg-orange-600 hover:bg-orange-700'
|
||||
/>
|
||||
)}
|
||||
|
||||
{isPreparing && isCourierDelivery && (
|
||||
<Button
|
||||
label='تحویل به پیک'
|
||||
onClick={onDeliverToCourier}
|
||||
isloading={isChangingStatus}
|
||||
className='bg-blue-600 hover:bg-blue-700'
|
||||
/>
|
||||
)}
|
||||
|
||||
{isPreparing && !isCourierDelivery && (
|
||||
<Button
|
||||
label='غذا آماده است'
|
||||
onClick={onOrderReady}
|
||||
isloading={isChangingStatus}
|
||||
className='bg-green-600 hover:bg-green-700'
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCancelButton && (
|
||||
<Button
|
||||
label='لغو سفارش'
|
||||
onClick={onCancelOrder}
|
||||
isloading={isChangingStatus}
|
||||
className='bg-red-600 hover:bg-red-700'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrderActions
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
import Table from '@/components/Table'
|
||||
import { ShoppingCart, Calendar, Clock, Tag } from 'iconsax-react'
|
||||
import type { FC } from 'react'
|
||||
import type { ColumnType, RowDataType } from '@/components/types/TableTypes'
|
||||
import type { Order, OrderItem as OrderItemType } from '../types/Types'
|
||||
import { formatOptionalDate, formatPrice, formatFaNumber } from '@/helpers/func'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import OrderSummary from './OrderSummary'
|
||||
|
||||
interface OrderItemTable extends RowDataType {
|
||||
image: string
|
||||
title: string
|
||||
quantity: number
|
||||
unitPrice: number
|
||||
discount: number
|
||||
amount: string
|
||||
}
|
||||
|
||||
interface OrderItemsDetailsProps {
|
||||
order: Order
|
||||
getStatusColor: (status: string) => string
|
||||
}
|
||||
|
||||
const OrderItemsDetails: FC<OrderItemsDetailsProps> = ({ order, getStatusColor }) => {
|
||||
const { t } = useTranslation('global')
|
||||
|
||||
const orderItems: OrderItemTable[] = order.items.map((item: OrderItemType) => ({
|
||||
id: item.id,
|
||||
image: item.food?.images?.[0] || '',
|
||||
title: item.food?.title || '-',
|
||||
quantity: item.quantity,
|
||||
unitPrice: item.unitPrice,
|
||||
discount: item.discount,
|
||||
amount: formatPrice(item.totalPrice)
|
||||
}))
|
||||
|
||||
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: 'unitPrice',
|
||||
align: 'right',
|
||||
render: (item) => formatPrice(item.unitPrice)
|
||||
},
|
||||
{
|
||||
title: 'تخفیف',
|
||||
key: 'discount',
|
||||
align: 'right',
|
||||
render: (item) => item.discount > 0 ? formatPrice(item.discount) : '-'
|
||||
},
|
||||
{
|
||||
title: 'مبلغ کل',
|
||||
key: 'amount',
|
||||
align: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<div className='w-full bg-white rounded-4xl p-8'>
|
||||
<div className='text-lg font-light mb-6 flex items-center gap-2'>
|
||||
<ShoppingCart color='#000' size={20} />
|
||||
جزییات سفارش
|
||||
</div>
|
||||
|
||||
<div className='mb-6 flex flex-wrap gap-4 text-[13px] font-light'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>زمان سفارش:</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Clock size={14} />
|
||||
{formatOptionalDate(order.createdAt, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>زمان بهروزرسانی:</div>
|
||||
<div>
|
||||
{formatOptionalDate(order.updatedAt, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>نوع تحویل:</div>
|
||||
<div>{order.deliveryMethod?.description || '-'}</div>
|
||||
</div>
|
||||
|
||||
{order.tableNumber && (
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>شماره میز:</div>
|
||||
<div>{order.tableNumber}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{order.description && (
|
||||
<div className='w-full'>
|
||||
<div className='text-description mb-2'>توضیحات سفارش:</div>
|
||||
<div className='text-gray-700 bg-gray-50 p-3 rounded-lg'>{order.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
data={orderItems}
|
||||
showHeader={true}
|
||||
className='mt-0'
|
||||
/>
|
||||
|
||||
<OrderSummary />
|
||||
|
||||
{order.couponDetail && (
|
||||
<div className='mt-6 pt-6 border-t border-dashed border-border'>
|
||||
<div className='flex items-center gap-2 mb-4'>
|
||||
<Tag size={16} />
|
||||
<div className='text-description'>اطلاعات کوپن:</div>
|
||||
</div>
|
||||
<div className='bg-blue-50 p-4 rounded-lg space-y-2 text-[13px]'>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-description'>نام کوپن:</span>
|
||||
<span className='font-medium'>{order.couponDetail.couponName}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-description'>کد کوپن:</span>
|
||||
<span className='font-mono bg-white px-2 py-1 rounded'>{order.couponDetail.couponCode}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-description'>نوع:</span>
|
||||
<span>{order.couponDetail.type === 'PERCENTAGE' ? 'درصدی' : 'مبلغی'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-description'>مقدار:</span>
|
||||
<span>{order.couponDetail.value}{order.couponDetail.type === 'PERCENTAGE' ? '%' : ' تومان'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-description'>حداکثر تخفیف:</span>
|
||||
<span>{formatPrice(order.couponDetail.maxDiscount)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{order.history && order.history.length > 0 && (
|
||||
<div className='mt-6 pt-6 border-t border-dashed border-border'>
|
||||
<div className='flex items-center gap-2 mb-4'>
|
||||
<Calendar color='#000' size={16} />
|
||||
<div className='text-description'>تاریخچه سفارش:</div>
|
||||
</div>
|
||||
<div className='space-y-3'>
|
||||
{order.history.map((historyItem, index) => (
|
||||
<div key={index} className='flex justify-between items-center text-[13px] bg-gray-50 p-3 rounded-lg'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div
|
||||
className='size-2 rounded-full'
|
||||
style={{ backgroundColor: getStatusColor(historyItem.status) }}
|
||||
></div>
|
||||
<span>{t(`order_status.${historyItem.status}`) || historyItem.status}</span>
|
||||
</div>
|
||||
<div className='text-description'>
|
||||
{formatOptionalDate(historyItem.changedAt, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrderItemsDetails
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// import type { FC } from 'react'
|
||||
// import type { Order } from '../types/Types'
|
||||
// import { formatPrice, formatFaNumber } from '@/helpers/func'
|
||||
|
||||
// interface OrderSummaryProps {
|
||||
// order: Order
|
||||
// variant?: 'default' | 'compact'
|
||||
// }
|
||||
|
||||
// const OrderSummary: FC<OrderSummaryProps> = ({ order, variant = 'default' }) => {
|
||||
// const isCompact = variant === 'compact'
|
||||
|
||||
// return (
|
||||
// <div className={`space-y-3 ${isCompact ? '' : 'text-[13px]'}`}>
|
||||
// <div className={`flex justify-between items-center ${isCompact ? 'border-b border-border border-dashed pb-4' : 'mt-4 border-b border-border border-dashed pb-4'}`}>
|
||||
// <div className='text-description'>جمع آیتمها</div>
|
||||
// <div>{formatPrice(order.subTotal)}</div>
|
||||
// </div>
|
||||
|
||||
// {order.totalDiscount > 0 && (
|
||||
// <div className='space-y-3'>
|
||||
// {order.itemsDiscount > 0 && (
|
||||
// <div className='flex justify-between items-center'>
|
||||
// <div className='text-description font-medium'>تخفیفها:</div>
|
||||
// <div className='text-green-600 font-medium'>-{formatPrice(order.itemsDiscount)}</div>
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
// {order.couponDiscount > 0 && (
|
||||
// <div className='flex justify-between items-center'>
|
||||
// <div className='text-description font-medium'>تخفیف کوپن:</div>
|
||||
// <div className='text-green-600 font-medium'>-{formatPrice(order.couponDiscount)}</div>
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
// <div className='border-t border-border border-dashed pt-3 mt-2'>
|
||||
// <div className='flex justify-between items-center'>
|
||||
// <div className='font-bold'>جمع تخفیفها:</div>
|
||||
// <div className='font-bold text-green-600'>-{formatPrice(order.totalDiscount)}</div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
// {order.deliveryFee > 0 && (
|
||||
// <div className='flex justify-between items-center pt-2'>
|
||||
// <div className='text-description'>هزینه ارسال:</div>
|
||||
// <div>{formatPrice(order.deliveryFee)}</div>
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
// {order.tax > 0 && (
|
||||
// <div className='flex justify-between items-center pt-2'>
|
||||
// <div className='text-description'>مالیات:</div>
|
||||
// <div>{formatPrice(order.tax)}</div>
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
// {isCompact && (
|
||||
// <>
|
||||
// {order.deliveryMethod?.minOrderPrice && (
|
||||
// <div className='flex justify-between items-center border-b border-border border-dashed pb-4'>
|
||||
// <div className='text-description'>حداقل سفارش:</div>
|
||||
// <div>{formatPrice(order.deliveryMethod.minOrderPrice)}</div>
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
// <div className='flex justify-between items-center border-b border-border border-dashed pb-4'>
|
||||
// <div className='text-description'>تعداد آیتمها:</div>
|
||||
// <div>{formatFaNumber(order.totalItems)}</div>
|
||||
// </div>
|
||||
// </>
|
||||
// )}
|
||||
|
||||
// <div className={`flex font-bold justify-between items-center ${isCompact ? '' : 'h-14 border-t-2 border-gray-300 pt-3 mt-3'}`}>
|
||||
// <div>جمع کل:</div>
|
||||
// <div className={`${isCompact ? 'text-base' : 'text-lg'}`}>{formatPrice(order.total)}</div>
|
||||
// </div>
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
|
||||
// export default OrderSummary
|
||||
|
||||
|
||||
const OrderSummary = () => {
|
||||
return null
|
||||
}
|
||||
|
||||
export default OrderSummary
|
||||
@@ -69,14 +69,14 @@ export const getOrderTableColumns = ({ t }: GetOrderTableColumnsParams): ColumnT
|
||||
key: 'deliveryMethod',
|
||||
title: 'نوع تحویل',
|
||||
render: (item: Order) => {
|
||||
return item.deliveryMethod?.title || '-'
|
||||
return item.deliveryMethod?.description || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'paymentMethod',
|
||||
title: 'نوع پرداخت',
|
||||
render: (item: Order) => {
|
||||
return item.paymentMethod?.title || '-'
|
||||
return item.paymentMethod?.description || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import type { FC } from 'react'
|
||||
import type { Order } from '../types/Types'
|
||||
import { PaymentStatusEnum } from '../enum/Enum'
|
||||
import { OrderStatus } from '../enum/Enum'
|
||||
import { formatPrice, formatFaNumber } from '@/helpers/func'
|
||||
|
||||
interface PaymentInfoProps {
|
||||
order: Order
|
||||
getPaymentStatusColor: (status: string) => string
|
||||
getPaymentStatusTextColor: (status: string) => string
|
||||
getPaymentStatusText: (status: string) => string
|
||||
}
|
||||
|
||||
const PaymentInfo: FC<PaymentInfoProps> = ({
|
||||
order,
|
||||
getPaymentStatusColor,
|
||||
getPaymentStatusTextColor,
|
||||
getPaymentStatusText
|
||||
}) => {
|
||||
const paymentStatus = order.paymentStatus || (order.status === OrderStatus.PAID ? PaymentStatusEnum.Paid : PaymentStatusEnum.Pending)
|
||||
|
||||
return (
|
||||
<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(paymentStatus)}`}></div>
|
||||
<div className={`text-[13px] ${getPaymentStatusTextColor(paymentStatus)}`}>
|
||||
{getPaymentStatusText(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?.description || '-'}</div>
|
||||
</div>
|
||||
|
||||
{order.paymentMethod?.gateway && (
|
||||
<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>{order.paymentMethod.gateway}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{order.paymentMethod?.merchantId && (
|
||||
<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'>
|
||||
شناسه مرchant
|
||||
</div>
|
||||
<div className='font-mono text-xs'>{order.paymentMethod.merchantId}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{order.paymentId && (
|
||||
<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 className='font-mono text-xs'>{order.paymentId}</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.itemsDiscount > 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 className='text-green-600'>-{formatPrice(order.itemsDiscount)}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{order.couponDiscount > 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 className='text-green-600'>-{formatPrice(order.couponDiscount)}</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 font-medium'>
|
||||
جمع تخفیفها
|
||||
</div>
|
||||
<div className='text-green-600 font-medium'>-{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.deliveryMethod?.minOrderPrice && (
|
||||
<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.deliveryMethod.minOrderPrice)}</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>{formatFaNumber(order.totalItems)}</div>
|
||||
</div>
|
||||
|
||||
<div className='flex mt-4 text-[13px] font-bold justify-between items-center'>
|
||||
<div>
|
||||
مبلغ کل
|
||||
</div>
|
||||
<div>{formatPrice(order.total)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaymentInfo
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import * as api from "../service/OrderService";
|
||||
import type { GetOrdersParams } from "../service/OrderService";
|
||||
import type { GetOrdersParams, ChangeOrderStatusParams, RefundPaymentParams } from "../service/OrderService";
|
||||
|
||||
export const useGetOrders = (params?: GetOrdersParams) => {
|
||||
return useQuery({
|
||||
@@ -17,8 +17,48 @@ export const useGetOrderById = (orderId: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useConfirmOrder = (orderId: string) => {
|
||||
export const useConfirmOrder = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: () => api.confirmOrder(orderId),
|
||||
mutationFn: (orderId: string) => api.confirmOrder(orderId),
|
||||
onSuccess: (_, orderId) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["order", orderId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useVerifyCashPayment = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (paymentId: string) => api.verifyCashPayment(paymentId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["order"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useChangeOrderStatus = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ orderId, status, params }: { orderId: string; status: string; params?: ChangeOrderStatusParams }) =>
|
||||
api.changeOrderStatus(orderId, status, params),
|
||||
onSuccess: (_, { orderId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["order", orderId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useRefundPayment = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ orderId, params }: { orderId: string; params: RefundPaymentParams }) =>
|
||||
api.refundPayment(orderId, params),
|
||||
onSuccess: (_, { orderId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["order", orderId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,6 +9,14 @@ export interface GetOrdersParams {
|
||||
deliveryType?: string;
|
||||
}
|
||||
|
||||
export interface ChangeOrderStatusParams {
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface RefundPaymentParams {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const getOrders = async (
|
||||
params?: GetOrdersParams
|
||||
): Promise<GetOrdersResponse> => {
|
||||
@@ -30,3 +38,22 @@ export const getOrderById = async (
|
||||
export const confirmOrder = async (orderId: string): Promise<void> => {
|
||||
await axios.patch(`/admin/orders/${orderId}/confirm`);
|
||||
};
|
||||
|
||||
export const verifyCashPayment = async (paymentId: string): Promise<void> => {
|
||||
await axios.post(`/admin/payments/verify-cash-method/${paymentId}`);
|
||||
};
|
||||
|
||||
export const changeOrderStatus = async (
|
||||
orderId: string,
|
||||
status: string,
|
||||
params?: ChangeOrderStatusParams
|
||||
): Promise<void> => {
|
||||
await axios.patch(`/admin/orders/${orderId}/${status}`, params);
|
||||
};
|
||||
|
||||
export const refundPayment = async (
|
||||
orderId: string,
|
||||
params: RefundPaymentParams
|
||||
): Promise<void> => {
|
||||
await axios.post(`/admin/orders/${orderId}/refund`, params);
|
||||
};
|
||||
|
||||
@@ -13,8 +13,7 @@ export interface OrderUser {
|
||||
referrer: string | null;
|
||||
isActive: boolean;
|
||||
gender: boolean;
|
||||
wallet: number;
|
||||
points: number;
|
||||
avatarUrl: string | null;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
@@ -23,6 +22,17 @@ export interface ServiceArea {
|
||||
coordinates: number[][][];
|
||||
}
|
||||
|
||||
export interface RestaurantScore {
|
||||
scoreAmount: string;
|
||||
scoreCredit: string;
|
||||
birthdayScore: string;
|
||||
purchaseScore: string;
|
||||
referrerScore: string;
|
||||
registerScore: string;
|
||||
purchaseAmount: string;
|
||||
marriageDateScore: string;
|
||||
}
|
||||
|
||||
export interface OrderRestaurant {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
@@ -30,26 +40,27 @@ export interface OrderRestaurant {
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
slug: string;
|
||||
logo: string | null;
|
||||
address: string | null;
|
||||
menuColor: string | null;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
logo: string;
|
||||
address: string;
|
||||
menuColor: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
serviceArea: ServiceArea;
|
||||
isActive: boolean;
|
||||
establishedYear: number | null;
|
||||
phoneNumber: string | null;
|
||||
phone: string;
|
||||
instagram: string | null;
|
||||
telegram: string | null;
|
||||
whatsapp: string | null;
|
||||
description: string | null;
|
||||
description: string;
|
||||
seoTitle: string | null;
|
||||
seoDescription: string | null;
|
||||
tagNames: string | null;
|
||||
images: string[] | null;
|
||||
vat: number;
|
||||
domain: string;
|
||||
score: RestaurantScore;
|
||||
plan: string;
|
||||
}
|
||||
|
||||
export interface OrderDeliveryMethod {
|
||||
@@ -57,31 +68,34 @@ export interface OrderDeliveryMethod {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
title: string;
|
||||
method: string;
|
||||
restaurant: string;
|
||||
deliveryFee: number;
|
||||
deliveryFeeType: string;
|
||||
kilometerNumber: number | null;
|
||||
deliveryFeeForKilometer: number | null;
|
||||
minOrderPrice: number;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface OrderAddress {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
user: string;
|
||||
title: string;
|
||||
address: string;
|
||||
export interface UserAddress {
|
||||
city: string;
|
||||
province: string;
|
||||
postalCode: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
phone: string;
|
||||
isDefault: boolean;
|
||||
address: string;
|
||||
fullName: string;
|
||||
latitude: number;
|
||||
province: string;
|
||||
longitude: number;
|
||||
postalCode: string;
|
||||
}
|
||||
|
||||
export interface CarAddress {
|
||||
phone: string;
|
||||
carColor: string;
|
||||
carModel: string;
|
||||
plateNumber: string;
|
||||
}
|
||||
|
||||
export interface OrderPaymentMethod {
|
||||
@@ -90,7 +104,6 @@ export interface OrderPaymentMethod {
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
restaurant: string;
|
||||
title: string;
|
||||
method: string;
|
||||
gateway: string | null;
|
||||
description: string;
|
||||
@@ -108,24 +121,17 @@ export interface OrderFood {
|
||||
category: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
content: string | null;
|
||||
content: string[];
|
||||
price: number;
|
||||
points: number | null;
|
||||
order: number | null;
|
||||
prepareTime: number | null;
|
||||
sat: boolean;
|
||||
sun: boolean;
|
||||
mon: boolean;
|
||||
breakfast: boolean;
|
||||
noon: boolean;
|
||||
dinner: boolean;
|
||||
stock: number;
|
||||
stockDefault: number;
|
||||
prepareTime: number;
|
||||
weekDays: number[];
|
||||
mealTypes: string[];
|
||||
isActive: boolean;
|
||||
images: string[];
|
||||
inPlaceServe: boolean;
|
||||
pickupServe: boolean;
|
||||
rate: number;
|
||||
score: number;
|
||||
discount: number;
|
||||
isSpecialOffer: boolean;
|
||||
}
|
||||
@@ -143,6 +149,40 @@ export interface OrderItem {
|
||||
totalPrice: number;
|
||||
}
|
||||
|
||||
export interface OrderHistory {
|
||||
status: string;
|
||||
changedAt: string;
|
||||
desc: string | null;
|
||||
}
|
||||
|
||||
export interface OrderPayment {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
order: string;
|
||||
amount: number;
|
||||
referenceId: string | null;
|
||||
method: string;
|
||||
gateway: string | null;
|
||||
transactionId: string | null;
|
||||
status: string;
|
||||
cardPan: string | null;
|
||||
verifyResponse: unknown | null;
|
||||
paidAt: string | null;
|
||||
failedAt: string | null;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export interface CouponDetail {
|
||||
type: string;
|
||||
value: number;
|
||||
couponId: string;
|
||||
couponCode: string;
|
||||
couponName: string;
|
||||
maxDiscount: number;
|
||||
}
|
||||
|
||||
export interface Order extends RowDataType {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
@@ -151,11 +191,13 @@ export interface Order extends RowDataType {
|
||||
user: OrderUser;
|
||||
restaurant: OrderRestaurant;
|
||||
deliveryMethod: OrderDeliveryMethod;
|
||||
address: OrderAddress;
|
||||
userAddress: UserAddress | null;
|
||||
carAddress: CarAddress | null;
|
||||
paymentMethod: OrderPaymentMethod;
|
||||
paymentId?: string;
|
||||
orderNumber: number;
|
||||
couponDiscount: number;
|
||||
couponDetail: unknown | null;
|
||||
couponDetail: CouponDetail | null;
|
||||
itemsDiscount: number;
|
||||
totalDiscount: number;
|
||||
subTotal: number;
|
||||
@@ -163,8 +205,12 @@ export interface Order extends RowDataType {
|
||||
deliveryFee: number;
|
||||
total: number;
|
||||
totalItems: number;
|
||||
description: string;
|
||||
tableNumber: string | null;
|
||||
status: string;
|
||||
paymentStatus: string;
|
||||
paymentStatus?: string;
|
||||
history: OrderHistory[];
|
||||
payments: OrderPayment[];
|
||||
items: OrderItem[];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user