From de8815f0d833ad9c3cc5fbaa2de764c8661daf19 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sat, 20 Dec 2025 14:33:19 +0330 Subject: [PATCH] order detail + change status in all state --- .env | 5 +- src/helpers/func.ts | 10 +- src/pages/food/utils/formatters.ts | 17 +- src/pages/orders/Details.tsx | 404 +++++++----------- src/pages/orders/components/CustomerInfo.tsx | 169 ++++++++ src/pages/orders/components/OrderActions.tsx | 100 +++++ .../orders/components/OrderItemsDetails.tsx | 217 ++++++++++ src/pages/orders/components/OrderSummary.tsx | 90 ++++ .../orders/components/OrderTableColumns.tsx | 4 +- src/pages/orders/components/PaymentInfo.tsx | 150 +++++++ src/pages/orders/hooks/useOrderData.ts | 48 ++- src/pages/orders/service/OrderService.ts | 27 ++ src/pages/orders/types/Types.ts | 124 ++++-- 13 files changed, 1065 insertions(+), 300 deletions(-) create mode 100644 src/pages/orders/components/CustomerInfo.tsx create mode 100644 src/pages/orders/components/OrderActions.tsx create mode 100644 src/pages/orders/components/OrderItemsDetails.tsx create mode 100644 src/pages/orders/components/OrderSummary.tsx create mode 100644 src/pages/orders/components/PaymentInfo.tsx diff --git a/.env b/.env index 2dc50fb..7bf4b8d 100644 --- a/.env +++ b/.env @@ -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' \ No newline at end of file diff --git a/src/helpers/func.ts b/src/helpers/func.ts index 8dc7502..14a2afc 100644 --- a/src/helpers/func.ts +++ b/src/helpers/func.ts @@ -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) + " تومان" + ); }; diff --git a/src/pages/food/utils/formatters.ts b/src/pages/food/utils/formatters.ts index 9273436..ea42625 100644 --- a/src/pages/food/utils/formatters.ts +++ b/src/pages/food/utils/formatters.ts @@ -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} دقیقه`; +}; diff --git a/src/pages/orders/Details.tsx b/src/pages/orders/Details.tsx index 19cbef3..9160985 100644 --- a/src/pages/orders/Details.tsx +++ b/src/pages/orders/Details.tsx @@ -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 = { - '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[] = [ - { - title: 'تصویر', - key: 'image', - render: (item) => ( -
- {item.image ? ( - {item.title} - ) : ( -
- بدون تصویر -
- )} -
- ) - }, - { - title: 'عنوان', - key: 'title', - align: 'right' - }, - { - title: 'تعداد', - key: 'quantity', - align: 'center', - render: (item) => formatFaNumber(item.quantity) - }, - { - title: 'مبلغ', - key: 'amount', - align: 'right' - } - ] - if (isLoading) { return (
@@ -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 (
@@ -174,164 +207,57 @@ const OrderDetails: FC = () => {
-
-
-
-
- اطلاعات مشتری -
-
- -
-
-
- شماره سفارش: -
-
{formatFaNumber(order.orderNumber)}
-
- -
-
- نام مشتری : -
-
{customerName}
-
- -
-
نوع سفارش :
-
{order.deliveryMethod?.title || '-'}
-
- -
-
زمان سفارش :
-
- {formatOptionalDate(order.createdAt, { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit' - })} -
-
-
- -
-
-
شماره تماس:
-
{order.user?.phone || '-'}
-
- -
-
آدرس:
-
{address}
- {order.address?.latitude && order.address?.longitude && ( - - -
نقشه
-
- )} -
-
-
- -
-
- جزییات سفارش -
- - - - {order.tax > 0 && ( -
-
- مالیات -
-
- {formatPrice(order.tax)} -
-
- )} - -
-
- جمع کل -
-
- {formatPrice(order.total)} -
-
- +
+ +
-
-
-
- اطلاعات پرداخت -
- -
-
-
- {getPaymentStatusText(order.paymentStatus)} -
-
-
- -
-
-
- نوع پرداخت -
-
{order.paymentMethod?.title || '-'}
-
-
-
- مبلغ سفارش -
-
{formatPrice(order.subTotal)}
-
- {order.totalDiscount > 0 && ( -
-
- تخفیف -
-
{formatPrice(order.totalDiscount)}
-
- )} -
-
- هزینه ارسال -
-
{formatPrice(order.deliveryFee)}
-
- {order.tax > 0 && ( -
-
- مالیات -
-
{formatPrice(order.tax)}
-
- )} -
-
- مبلغ کل -
-
{formatPrice(order.total)}
-
-
+
+ + setShowCancelModal(true)} + />
+ + {/* مدال کنسل سفارش */} + setShowCancelModal(false)} + onConfrim={handleCancelOrder} + isloading={isChangingStatus} + label='آیا از لغو سفارش مطمئن هستید؟' + isHasDescription + /> + + {/* مدال ریفاند */} + setShowRefundModal(false)} + onConfrim={handleRefund} + isloading={isRefunding} + label='آیا از ریفاند این سفارش مطمئن هستید؟' + isHasDescription + /> ) } diff --git a/src/pages/orders/components/CustomerInfo.tsx b/src/pages/orders/components/CustomerInfo.tsx new file mode 100644 index 0000000..5ad2560 --- /dev/null +++ b/src/pages/orders/components/CustomerInfo.tsx @@ -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 = ({ 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 ( +
+
+
+ + اطلاعات مشتری +
+
+ +
+
+
+ شماره سفارش: +
+
{formatFaNumber(order.orderNumber)}
+
+ +
+
+ نام مشتری: +
+
+ {order.user?.avatarUrl && ( + {customerName} + )} + {customerName} +
+
+ +
+
شماره تماس:
+
+ + {order.user?.phone || '-'} +
+
+ + {order.user?.birthDate && ( +
+
تاریخ تولد:
+
+ + {formatOptionalDate(order.user.birthDate, { + year: 'numeric', + month: '2-digit', + day: '2-digit' + })} +
+
+ )} + + {order.user?.marriageDate && ( +
+
تاریخ ازدواج:
+
+ + {formatOptionalDate(order.user.marriageDate, { + year: 'numeric', + month: '2-digit', + day: '2-digit' + })} +
+
+ )} + +
+
جنسیت:
+
{order.user?.gender ? 'مرد' : 'زن'}
+
+ +
+
وضعیت:
+
+ {order.user?.isActive ? 'فعال' : 'غیرفعال'} +
+
+ + {order.user?.referrer && ( +
+
معرف:
+
{order.user.referrer}
+
+ )} +
+ + {order.userAddress && ( +
+
+
+ + آدرس کامل: +
+
{fullAddress}
+
+ + {order.userAddress.fullName && ( +
+
نام گیرنده:
+
{order.userAddress.fullName}
+
+ )} + +
+
شماره تماس گیرنده:
+
{order.userAddress.phone || '-'}
+
+ + {order.userAddress?.latitude && order.userAddress?.longitude && ( + + +
مشاهده روی نقشه
+
+ )} +
+ )} + + {order.carAddress && ( +
+
اطلاعات خودرو:
+
+
+
رنگ:
+
{order.carAddress.carColor}
+
+
+
مدل:
+
{order.carAddress.carModel}
+
+
+
پلاک:
+
{order.carAddress.plateNumber}
+
+
+
شماره تماس:
+
{order.carAddress.phone}
+
+
+
+ )} +
+ ) +} + +export default CustomerInfo + diff --git a/src/pages/orders/components/OrderActions.tsx b/src/pages/orders/components/OrderActions.tsx new file mode 100644 index 0000000..dd8211c --- /dev/null +++ b/src/pages/orders/components/OrderActions.tsx @@ -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 = ({ + showVerifyCashPaymentButton, + canConfirm, + showSendToKitchenButton, + showCancelButton, + isPreparing, + isCourierDelivery, + isCanceled, + isVerifyingPayment, + isChangingStatus, + onVerifyCashPayment, + onConfirmOrder, + onSendToKitchen, + onDeliverToCourier, + onOrderReady, + onCancelOrder +}) => { + if (isCanceled) return null + + return ( +
+ {showVerifyCashPaymentButton && ( +
+ ) +} + +export default OrderActions + diff --git a/src/pages/orders/components/OrderItemsDetails.tsx b/src/pages/orders/components/OrderItemsDetails.tsx new file mode 100644 index 0000000..423e436 --- /dev/null +++ b/src/pages/orders/components/OrderItemsDetails.tsx @@ -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 = ({ 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[] = [ + { + title: 'تصویر', + key: 'image', + render: (item) => ( +
+ {item.image ? ( + {item.title} + ) : ( +
+ بدون تصویر +
+ )} +
+ ) + }, + { + 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 ( +
+
+ + جزییات سفارش +
+ +
+
+
زمان سفارش:
+
+ + {formatOptionalDate(order.createdAt, { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + })} +
+
+ +
+
زمان به‌روزرسانی:
+
+ {formatOptionalDate(order.updatedAt, { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + })} +
+
+ +
+
نوع تحویل:
+
{order.deliveryMethod?.description || '-'}
+
+ + {order.tableNumber && ( +
+
شماره میز:
+
{order.tableNumber}
+
+ )} + + {order.description && ( +
+
توضیحات سفارش:
+
{order.description}
+
+ )} +
+ +
+ + + + {order.couponDetail && ( +
+
+ +
اطلاعات کوپن:
+
+
+
+ نام کوپن: + {order.couponDetail.couponName} +
+
+ کد کوپن: + {order.couponDetail.couponCode} +
+
+ نوع: + {order.couponDetail.type === 'PERCENTAGE' ? 'درصدی' : 'مبلغی'} +
+
+ مقدار: + {order.couponDetail.value}{order.couponDetail.type === 'PERCENTAGE' ? '%' : ' تومان'} +
+
+ حداکثر تخفیف: + {formatPrice(order.couponDetail.maxDiscount)} +
+
+
+ )} + + {order.history && order.history.length > 0 && ( +
+
+ +
تاریخچه سفارش:
+
+
+ {order.history.map((historyItem, index) => ( +
+
+
+ {t(`order_status.${historyItem.status}`) || historyItem.status} +
+
+ {formatOptionalDate(historyItem.changedAt, { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + })} +
+
+ ))} +
+
+ )} + + ) +} + +export default OrderItemsDetails + diff --git a/src/pages/orders/components/OrderSummary.tsx b/src/pages/orders/components/OrderSummary.tsx new file mode 100644 index 0000000..66832b0 --- /dev/null +++ b/src/pages/orders/components/OrderSummary.tsx @@ -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 = ({ order, variant = 'default' }) => { +// const isCompact = variant === 'compact' + +// return ( +//
+//
+//
جمع آیتم‌ها
+//
{formatPrice(order.subTotal)}
+//
+ +// {order.totalDiscount > 0 && ( +//
+// {order.itemsDiscount > 0 && ( +//
+//
تخفیف‌ها:
+//
-{formatPrice(order.itemsDiscount)}
+//
+// )} + +// {order.couponDiscount > 0 && ( +//
+//
تخفیف کوپن:
+//
-{formatPrice(order.couponDiscount)}
+//
+// )} + +//
+//
+//
جمع تخفیف‌ها:
+//
-{formatPrice(order.totalDiscount)}
+//
+//
+//
+// )} + +// {order.deliveryFee > 0 && ( +//
+//
هزینه ارسال:
+//
{formatPrice(order.deliveryFee)}
+//
+// )} + +// {order.tax > 0 && ( +//
+//
مالیات:
+//
{formatPrice(order.tax)}
+//
+// )} + +// {isCompact && ( +// <> +// {order.deliveryMethod?.minOrderPrice && ( +//
+//
حداقل سفارش:
+//
{formatPrice(order.deliveryMethod.minOrderPrice)}
+//
+// )} + +//
+//
تعداد آیتم‌ها:
+//
{formatFaNumber(order.totalItems)}
+//
+// +// )} + +//
+//
جمع کل:
+//
{formatPrice(order.total)}
+//
+//
+// ) +// } + +// export default OrderSummary + + +const OrderSummary = () => { + return null +} + +export default OrderSummary \ No newline at end of file diff --git a/src/pages/orders/components/OrderTableColumns.tsx b/src/pages/orders/components/OrderTableColumns.tsx index f68994e..2ed4a2f 100644 --- a/src/pages/orders/components/OrderTableColumns.tsx +++ b/src/pages/orders/components/OrderTableColumns.tsx @@ -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 || '-' } }, { diff --git a/src/pages/orders/components/PaymentInfo.tsx b/src/pages/orders/components/PaymentInfo.tsx new file mode 100644 index 0000000..cf8d10d --- /dev/null +++ b/src/pages/orders/components/PaymentInfo.tsx @@ -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 = ({ + order, + getPaymentStatusColor, + getPaymentStatusTextColor, + getPaymentStatusText +}) => { + const paymentStatus = order.paymentStatus || (order.status === OrderStatus.PAID ? PaymentStatusEnum.Paid : PaymentStatusEnum.Pending) + + return ( +
+
+
+ اطلاعات پرداخت +
+ +
+
+
+ {getPaymentStatusText(paymentStatus)} +
+
+
+ +
+
+
+ نوع پرداخت +
+
{order.paymentMethod?.description || '-'}
+
+ + {order.paymentMethod?.gateway && ( +
+
+ درگاه پرداخت +
+
{order.paymentMethod.gateway}
+
+ )} + + {order.paymentMethod?.merchantId && ( +
+
+ شناسه مرchant +
+
{order.paymentMethod.merchantId}
+
+ )} + + {order.paymentId && ( +
+
+ شناسه پرداخت +
+
{order.paymentId}
+
+ )} + +
+
+ جمع آیتم‌ها +
+
{formatPrice(order.subTotal)}
+
+ + {order.itemsDiscount > 0 && ( +
+
+ تخفیف آیتم‌ها +
+
-{formatPrice(order.itemsDiscount)}
+
+ )} + + {order.couponDiscount > 0 && ( +
+
+ تخفیف کوپن +
+
-{formatPrice(order.couponDiscount)}
+
+ )} + + {order.totalDiscount > 0 && ( +
+
+ جمع تخفیف‌ها +
+
-{formatPrice(order.totalDiscount)}
+
+ )} + +
+
+ هزینه ارسال +
+
{formatPrice(order.deliveryFee)}
+
+ + {order.deliveryMethod?.minOrderPrice && ( +
+
+ حداقل سفارش +
+
{formatPrice(order.deliveryMethod.minOrderPrice)}
+
+ )} + + {order.tax > 0 && ( +
+
+ مالیات +
+
{formatPrice(order.tax)}
+
+ )} + +
+
+ تعداد آیتم‌ها +
+
{formatFaNumber(order.totalItems)}
+
+ +
+
+ مبلغ کل +
+
{formatPrice(order.total)}
+
+
+
+ ) +} + +export default PaymentInfo + diff --git a/src/pages/orders/hooks/useOrderData.ts b/src/pages/orders/hooks/useOrderData.ts index 5108077..7946e98 100644 --- a/src/pages/orders/hooks/useOrderData.ts +++ b/src/pages/orders/hooks/useOrderData.ts @@ -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"] }); + }, }); }; diff --git a/src/pages/orders/service/OrderService.ts b/src/pages/orders/service/OrderService.ts index 34ca6bc..d838b10 100644 --- a/src/pages/orders/service/OrderService.ts +++ b/src/pages/orders/service/OrderService.ts @@ -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 => { @@ -30,3 +38,22 @@ export const getOrderById = async ( export const confirmOrder = async (orderId: string): Promise => { await axios.patch(`/admin/orders/${orderId}/confirm`); }; + +export const verifyCashPayment = async (paymentId: string): Promise => { + await axios.post(`/admin/payments/verify-cash-method/${paymentId}`); +}; + +export const changeOrderStatus = async ( + orderId: string, + status: string, + params?: ChangeOrderStatusParams +): Promise => { + await axios.patch(`/admin/orders/${orderId}/${status}`, params); +}; + +export const refundPayment = async ( + orderId: string, + params: RefundPaymentParams +): Promise => { + await axios.post(`/admin/orders/${orderId}/refund`, params); +}; diff --git a/src/pages/orders/types/Types.ts b/src/pages/orders/types/Types.ts index 08ee521..d92fda1 100644 --- a/src/pages/orders/types/Types.ts +++ b/src/pages/orders/types/Types.ts @@ -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[]; }