detail of order

This commit is contained in:
hamid zarghami
2025-12-07 16:12:54 +03:30
parent 8240a8170f
commit 10e5143d78
5 changed files with 220 additions and 83 deletions
+179 -66
View File
@@ -1,11 +1,16 @@
import Select from '@/components/Select' import Select from '@/components/Select'
import Table from '@/components/Table' import Table from '@/components/Table'
import { Location } from 'iconsax-react' import { Location } from 'iconsax-react'
import { type FC } from 'react' import { type FC, useMemo } from 'react'
import type { ColumnType, RowDataType } from '@/components/types/TableTypes' import type { ColumnType, RowDataType } from '@/components/types/TableTypes'
import AppImage from '@/assets/images/appimage.png' import { useGetOrderById } from './hooks/useGetOrders'
import { useParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { formatPrice, formatOptionalDate, formatFaNumber } from '@/helpers/func'
import { OrderStatus, PaymentStatusEnum } from './enum/Enum'
import type { OrderItem as OrderItemType } from './types/Types'
interface OrderItem extends RowDataType { interface OrderItemTable extends RowDataType {
image: string image: string
title: string title: string
quantity: number quantity: number
@@ -13,27 +18,104 @@ interface OrderItem extends RowDataType {
} }
const OrderDetails: FC = () => { const OrderDetails: FC = () => {
const orderItems: OrderItem[] = [ const { id } = useParams()
{ const { data: orderData, isLoading } = useGetOrderById(id!)
id: 1, const { t } = useTranslation('global')
image: AppImage,
title: 'پیتزا پپرونی',
quantity: 1,
amount: '۴۲,۰۰۰ تومان'
}
]
const columns: ColumnType<OrderItem>[] = [ 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,
]
return statuses.map((status) => ({
value: status,
label: t(`order_status.${status}`)
}))
}, [t])
const getStatusColor = (status: string): string => {
const colorMap: Record<string, string> = {
'pending': '#FFEECC',
'confirmed': '#E3F2FD',
'preparing': '#FFF3E0',
'rejectedByRestaurant': '#FFEBEE',
'cancelledByUser': '#FFEBEE',
'cancelledBySystem': '#FFEBEE',
'readyForCustomerPickup': '#E3F2FD',
'readyForDineIn': '#E3F2FD',
'readyForDeliveryCar': '#E3F2FD',
'readyForDeliveryCourier': '#E3F2FD',
'delivered': '#E8F5E9',
}
return colorMap[status] || '#FFEECC'
}
const getPaymentStatusColor = (status: string): string => {
const colorMap: Record<string, string> = {
[PaymentStatusEnum.Paid]: 'bg-green-500',
[PaymentStatusEnum.Pending]: 'bg-yellow-500',
[PaymentStatusEnum.Failed]: 'bg-red-500',
}
return colorMap[status] || 'bg-gray-500'
}
const getPaymentStatusTextColor = (status: string): string => {
const colorMap: Record<string, string> = {
[PaymentStatusEnum.Paid]: 'text-green-500',
[PaymentStatusEnum.Pending]: 'text-yellow-500',
[PaymentStatusEnum.Failed]: 'text-red-500',
}
return colorMap[status] || 'text-gray-500'
}
const getPaymentStatusText = (status: string): string => {
const textMap: Record<string, string> = {
[PaymentStatusEnum.Paid]: 'پرداخت شده',
[PaymentStatusEnum.Pending]: 'در انتظار پرداخت',
[PaymentStatusEnum.Failed]: 'پرداخت ناموفق',
}
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: 'تصویر', title: 'تصویر',
key: 'image', key: 'image',
render: (item) => ( render: (item) => (
<div className=''> <div>
{item.image ? (
<img <img
src={item.image} src={item.image}
alt={item.title} alt={item.title}
className='w-12 h-12 rounded-lg' 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> </div>
) )
}, },
@@ -45,7 +127,8 @@ const OrderDetails: FC = () => {
{ {
title: 'تعداد', title: 'تعداد',
key: 'quantity', key: 'quantity',
align: 'center' align: 'center',
render: (item) => formatFaNumber(item.quantity)
}, },
{ {
title: 'مبلغ', title: 'مبلغ',
@@ -53,15 +136,39 @@ const OrderDetails: FC = () => {
align: 'right' align: 'right'
} }
] ]
if (isLoading) {
return (
<div className='mt-5 flex items-center justify-center h-64'>
<div className='text-lg font-light'>در حال بارگذاری...</div>
</div>
)
}
if (!order) {
return (
<div className='mt-5 flex items-center justify-center h-64'>
<div className='text-lg font-light'>سفارش یافت نشد</div>
</div>
)
}
const customerName = [order.user?.firstName, order.user?.lastName].filter(Boolean).join(' ') || '-'
const address = order.address ? `${order.address.city || ''} - ${order.address.address || ''}`.trim() : '-'
return ( return (
<div className='mt-5'> <div className='mt-5'>
<div className='flex justify-between items-center'> <div className='flex justify-between items-center'>
<h1 className='text-lg font-light'> سفارش ۱۲۳۴۵</h1> <h1 className='text-lg font-light'>
سفارش {formatFaNumber(order.orderNumber)}
</h1>
<div> <div>
<Select <Select
items={[]} items={statusItems}
placeholder='درحال آماده سازی' value={order.status}
className='w-[200px] bg-[#FFEECC]' placeholder={t(`order_status.${order.status}`)}
className='w-[200px]'
style={{ backgroundColor: getStatusColor(order.status) }}
/> />
</div> </div>
</div> </div>
@@ -80,40 +187,55 @@ const OrderDetails: FC = () => {
<div className='text-description'> <div className='text-description'>
شماره سفارش: شماره سفارش:
</div> </div>
<div>۱۲۳۴۵</div> <div>{formatFaNumber(order.orderNumber)}</div>
</div> </div>
<div className='flex gap-2'> <div className='flex gap-2'>
<div className='text-description'> <div className='text-description'>
نام مشتری : نام مشتری :
</div> </div>
<div>مهرداد مظفری</div> <div>{customerName}</div>
</div> </div>
<div className='flex gap-2'> <div className='flex gap-2'>
<div className='text-description'>نوع سفارش :</div> <div className='text-description'>نوع سفارش :</div>
<div>تحویل در محل</div> <div>{order.deliveryMethod?.title || '-'}</div>
</div> </div>
<div className='flex gap-2'> <div className='flex gap-2'>
<div className='text-description'>زمان سفارش : </div> <div className='text-description'>زمان سفارش : </div>
<div>۱۴۰۳/۱۱/۲۵ - ۱۳:۰۰:۰۰</div> <div>
{formatOptionalDate(order.createdAt, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})}
</div>
</div> </div>
</div> </div>
<div className='mt-8 flex gap-20 text-[13px] font-light'> <div className='mt-8 flex gap-20 text-[13px] font-light'>
<div className='flex gap-2'> <div className='flex gap-2'>
<div className='text-description'>شماره تماس:</div> <div className='text-description'>شماره تماس:</div>
<div>۰۸۶۹۱۰۰۹۱۰۰۱</div> <div>{order.user?.phone || '-'}</div>
</div> </div>
<div className='flex gap-2'> <div className='flex gap-2 items-center'>
<div className='text-description'>آدرس::</div> <div className='text-description'>آدرس:</div>
<div>اراک -خیابان شریعتی-خیابان جنت</div> <div>{address}</div>
<div className='flex gap-1'> {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' /> <Location size={17} color='#0047FF' />
<div className='text-[13px] text-blue-600'>نقشه</div> <div className='text-[13px] text-blue-600'>نقشه</div>
</div> </a>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -130,21 +252,23 @@ const OrderDetails: FC = () => {
className='mt-0' className='mt-0'
/> />
{order.tax > 0 && (
<div className='flex justify-between items-center h-14 text-[13px] mt-5 border-t border-border'> <div className='flex justify-between items-center h-14 text-[13px] mt-5 border-t border-border'>
<div className=''> <div>
۱۰٪ ارزش افزوده مالیات
</div> </div>
<div> <div>
۴۲۰,۰۰۰ تومان {formatPrice(order.tax)}
</div> </div>
</div> </div>
)}
<div className='flex font-bold justify-between items-center h-14 text-[13px] border-t border-border'> <div className='flex font-bold justify-between items-center h-14 text-[13px] border-t border-border'>
<div> <div>
جمع کل جمع کل
</div> </div>
<div> <div>
۴۲۰,۰۰۰ تومان {formatPrice(order.total)}
</div> </div>
</div> </div>
</div> </div>
@@ -157,69 +281,58 @@ const OrderDetails: FC = () => {
</div> </div>
<div className='flex gap-1.5 items-center'> <div className='flex gap-1.5 items-center'>
<div className='size-2 rounded-full bg-green-500'></div> <div className={`size-2 rounded-full ${getPaymentStatusColor(order.paymentStatus)}`}></div>
<div className='text-[13px] text-green-500'> <div className={`text-[13px] ${getPaymentStatusTextColor(order.paymentStatus)}`}>
پرداخت شده {getPaymentStatusText(order.paymentStatus)}
</div> </div>
</div> </div>
</div> </div>
<div className='mt-10'> <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='flex text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className=' text-description'> <div className='text-description'>
شماره تراکنش نوع پرداخت
</div> </div>
<div>۱۲۳۴۵۶۷۸۹۰</div> <div>{order.paymentMethod?.title || '-'}</div>
</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='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className=' text-description'> <div className='text-description'>
شماره پیگیری
</div>
<div>42343546</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>پرداخت آنلاین</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>
<div>۴۰۰۰۰۰۰ تومان</div> <div>{formatPrice(order.subTotal)}</div>
</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='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className=' text-description'> <div className='text-description'>
تخفیف تخفیف
</div> </div>
<div>۰ تومان</div> <div>{formatPrice(order.totalDiscount)}</div>
</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='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className=' text-description'> <div className='text-description'>
هزینه ارسال هزینه ارسال
</div> </div>
<div>۲۰۰۰۰۰ تومان</div> <div>{formatPrice(order.deliveryFee)}</div>
</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='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className=' text-description'> <div className='text-description'>
مالیات مالیات
</div> </div>
<div>۰ تومان</div> <div>{formatPrice(order.tax)}</div>
</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='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className=' text-description'> <div className='text-description'>
مبلغ کل مبلغ کل
</div> </div>
<div>۴۲۰,۰۰۰ تومان</div> <div>{formatPrice(order.total)}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div >
) )
} }
+6
View File
@@ -15,3 +15,9 @@ export const enum OrderStatus {
Delivered = "delivered", Delivered = "delivered",
} }
export const enum PaymentStatusEnum {
Pending = "pending",
Paid = "paid",
Failed = "failed",
}
+8
View File
@@ -8,3 +8,11 @@ export const useGetOrders = (params?: GetOrdersParams) => {
queryFn: () => api.getOrders(params), queryFn: () => api.getOrders(params),
}); });
}; };
export const useGetOrderById = (orderId: string) => {
return useQuery({
queryKey: ["order", orderId],
queryFn: () => api.getOrderById(orderId),
enabled: !!orderId,
});
};
+10 -1
View File
@@ -1,5 +1,5 @@
import axios from "@/config/axios"; import axios from "@/config/axios";
import type { GetOrdersResponse } from "../types/Types"; import type { GetOrdersResponse, GetOrderByIdResponse } from "../types/Types";
export interface GetOrdersParams { export interface GetOrdersParams {
page?: number; page?: number;
@@ -17,3 +17,12 @@ export const getOrders = async (
}); });
return data; return data;
}; };
export const getOrderById = async (
orderId: string
): Promise<GetOrderByIdResponse> => {
const { data } = await axios.get<GetOrderByIdResponse>(
`/admin/orders/${orderId}`
);
return data;
};
+1
View File
@@ -169,3 +169,4 @@ export interface Order extends RowDataType {
} }
export type GetOrdersResponse = IResponse<Order[]>; export type GetOrdersResponse = IResponse<Order[]>;
export type GetOrderByIdResponse = IResponse<Order>;