From b979bb610a82b348b4dfd581c33c734b8e0b300b Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sat, 18 Jul 2026 20:56:48 +0330 Subject: [PATCH] edit order --- src/pages/orders/Details.tsx | 332 ++++++++++-------- .../orders/components/AddOrderItemModal.tsx | 160 +++++++++ src/pages/orders/components/OrderActions.tsx | 13 - .../orders/components/OrderItemsDetails.tsx | 85 ++++- .../orders/components/OrderTableColumns.tsx | 130 +++---- src/pages/orders/components/PaymentInfo.tsx | 167 +++++---- .../components/PaymentVerificationBox.tsx | 72 ---- src/pages/orders/components/PaymentsList.tsx | 239 +++++++++++++ .../orders/components/RefundPaymentModal.tsx | 108 ++++++ src/pages/orders/hooks/useOrderData.ts | 55 ++- src/pages/orders/service/OrderService.ts | 36 ++ src/pages/orders/types/Types.ts | 21 ++ 12 files changed, 1047 insertions(+), 371 deletions(-) create mode 100644 src/pages/orders/components/AddOrderItemModal.tsx delete mode 100644 src/pages/orders/components/PaymentVerificationBox.tsx create mode 100644 src/pages/orders/components/PaymentsList.tsx create mode 100644 src/pages/orders/components/RefundPaymentModal.tsx diff --git a/src/pages/orders/Details.tsx b/src/pages/orders/Details.tsx index 03de98d..4a6f793 100644 --- a/src/pages/orders/Details.tsx +++ b/src/pages/orders/Details.tsx @@ -5,6 +5,9 @@ import { useChangeOrderStatus, useRefundPayment, useUpdateOrderFees, + useAddOrderItem, + useUpdateOrderItemQuantity, + useRemoveOrderItem, } from './hooks/useOrderData' import { useParams } from 'react-router-dom' import { formatFaNumber } from '@/helpers/func' @@ -14,10 +17,12 @@ import { DeliveryMethodEnum } from '@/pages/shipmentMethod/enum/Enum' import ModalConfrim from '@/components/ModalConfrim' import CustomerInfo from './components/CustomerInfo' import OrderItemsDetails from './components/OrderItemsDetails' +import PaymentsList from './components/PaymentsList' import PaymentInfo from './components/PaymentInfo' -import PaymentVerificationBox from './components/PaymentVerificationBox' import OrderActions from './components/OrderActions' import UpdateOrderFeesModal from './components/UpdateOrderFeesModal' +import AddOrderItemModal from './components/AddOrderItemModal' +import RefundPaymentModal from './components/RefundPaymentModal' import { toast } from 'react-toastify' import { extractErrorMessage } from '@/config/func' import { printOrderReceipt } from './print/orderReceiptPrint' @@ -30,53 +35,32 @@ const OrderDetails: FC = () => { const { mutate: changeOrderStatus, isPending: isChangingStatus } = useChangeOrderStatus() const { mutate: refundPayment, isPending: isRefunding } = useRefundPayment() const { mutate: updateOrderFees, isPending: isUpdatingFees } = useUpdateOrderFees() + const { mutate: addOrderItem, isPending: isAddingItem } = useAddOrderItem() + const { mutate: updateOrderItemQuantity, isPending: isUpdatingItemQuantity } = useUpdateOrderItemQuantity() + const { mutate: removeOrderItem, isPending: isRemovingItem } = useRemoveOrderItem() const [showCancelModal, setShowCancelModal] = useState(false) - const [showRefundModal, setShowRefundModal] = useState(false) + const [refundModal, setRefundModal] = useState<{ defaultAmount: number; maxAmount: number } | null>(null) const [showEditFeesModal, setShowEditFeesModal] = useState(false) + const [showAddItemModal, setShowAddItemModal] = useState(false) + const [itemIdToRemove, setItemIdToRemove] = useState(null) + const [verifyingPaymentId, setVerifyingPaymentId] = useState(null) const order = orderData?.data const getStatusColor = (status: string): string => { const colorMap: Record = { - 'pendingPayment': '#FFEECC', - 'paid': '#E3F2FD', - 'preparing': '#FFF3E0', - 'ready': '#E3F2FD', - 'shipped': '#E3F2FD', - 'completed': '#E8F5E9', - 'canceled': '#FFEBEE', + pendingPayment: '#FFEECC', + paid: '#E3F2FD', + preparing: '#FFF3E0', + ready: '#E3F2FD', + shipped: '#E3F2FD', + completed: '#E8F5E9', + canceled: '#FFEBEE', } return colorMap[status] || '#FFEECC' } - const getPaymentStatusColor = (status: string): string => { - const colorMap: Record = { - [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 = { - [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 = { - [PaymentStatusEnum.Paid]: 'پرداخت شده', - [PaymentStatusEnum.Pending]: 'در انتظار پرداخت', - [PaymentStatusEnum.Failed]: 'پرداخت ناموفق', - } - return textMap[status] || status - } - if (isLoading) { return (
@@ -93,113 +77,113 @@ const OrderDetails: FC = () => { ) } - // منطق نمایش دکمه‌ها const isCashPayment = order.paymentMethod?.method === PaymentMethodEnum.Cash - const isCreditCardPayment = order.paymentMethod?.method === PaymentMethodEnum.CreditCard - const isPendingPayment = order.status === OrderStatus.NEW - const isOrderPaid = order.payments?.some(payment => payment.status === PaymentStatusEnum.Paid) + const isOrderPaid = Number(order.paidAmount ?? 0) > 0 || order.payments?.some( + (payment) => payment.status === PaymentStatusEnum.Paid + ) const isPreparing = order.status === OrderStatus.PREPARING const isCanceled = order.status === OrderStatus.CANCELED const isCompleted = order.status === OrderStatus.COMPLETED + const isPendingPayment = order.status === OrderStatus.NEW const canEditFees = !isCanceled && !isCompleted + const canEditItems = !isCanceled && !isCompleted + const isUpdatingItem = isAddingItem || isUpdatingItemQuantity || isRemovingItem const canCompleteOrder = [ OrderStatus.DELIVERED_TO_WAITER, OrderStatus.DELIVERED_TO_RECEPTIONIST, OrderStatus.SHIPPED, ].includes(order.status as OrderStatus) - // بررسی وضعیت پرداخت از payments array یا paymentStatus - // ابتدا از paymentStatus استفاده می‌کنیم، اگر نبود از اولین payment در payments array استفاده می‌کنیم - const paymentStatus = order.paymentStatus || order.payments?.[0]?.status - // بررسی وضعیت pending (هم enum و هم string literal) - const isPaymentPending = paymentStatus === PaymentStatusEnum.Pending || paymentStatus === 'pending' const isCourierDelivery = order.deliveryMethod?.method === DeliveryMethodEnum.DeliveryCourier - const isDineInOrCarDelivery = order.deliveryMethod?.method === DeliveryMethodEnum.DineIn || order.deliveryMethod?.method === DeliveryMethodEnum.DeliveryCar + const isDineInOrCarDelivery = + order.deliveryMethod?.method === DeliveryMethodEnum.DineIn || + order.deliveryMethod?.method === DeliveryMethodEnum.DeliveryCar const isCustomerPickup = order.deliveryMethod?.method === DeliveryMethodEnum.CustomerPickup - const payment = order.payments?.[0] - const hasPaymentDescription = Boolean(payment?.description) - const hasPaymentAttachments = Boolean(payment?.attachments && payment.attachments.length > 0) - const showPaymentReceiptBox = isCreditCardPayment && (hasPaymentDescription || hasPaymentAttachments) - const showVerifyCashPaymentButton = isCashPayment && isPaymentPending && !isCanceled - const showVerifyPaymentButton = (isCashPayment || isCreditCardPayment) && isPaymentPending && !isCanceled - const showPaymentVerificationBox = showPaymentReceiptBox || showVerifyCashPaymentButton - - // نمایش دکمه ارسال به آشپزخانه - // برای pendingPayment با پرداخت نقدی یا برای paid const showSendToKitchenButton = (isPendingPayment && isCashPayment) || isOrderPaid - - // نمایش دکمه کنسل - // همیشه نمایش داده می‌شود مگر اینکه سفارش قبلاً کنسل شده باشد const showCancelButton = !isCanceled - const handleVerifyPayment = () => { - const paymentId = order.paymentId || order.payments?.[0]?.id - if (paymentId) { - verifyPayment(paymentId, { - onSuccess: () => { - // Success handled by query invalidation - }, - onError: (error) => { - toast.error(extractErrorMessage(error)) - } - }) - } + const handleVerifyPayment = (paymentId: string) => { + setVerifyingPaymentId(paymentId) + verifyPayment(paymentId, { + onSuccess: () => { + setVerifyingPaymentId(null) + }, + onError: (error) => { + setVerifyingPaymentId(null) + toast.error(extractErrorMessage(error)) + }, + }) } const handleSendToKitchen = () => { - changeOrderStatus({ - orderId: order.id, - status: OrderStatus.PREPARING - }, { - onError: (error) => { - toast.error(extractErrorMessage(error)) + changeOrderStatus( + { + orderId: order.id, + status: OrderStatus.PREPARING, + }, + { + onError: (error) => { + toast.error(extractErrorMessage(error)) + }, } - }) + ) } const handleDeliverToCourier = () => { - changeOrderStatus({ - orderId: order.id, - status: OrderStatus.SHIPPED - }, { - onError: (error) => { - toast.error(extractErrorMessage(error)) + changeOrderStatus( + { + orderId: order.id, + status: OrderStatus.SHIPPED, + }, + { + onError: (error) => { + toast.error(extractErrorMessage(error)) + }, } - }) + ) } const handleDeliverToWaiter = () => { - changeOrderStatus({ - orderId: order.id, - status: OrderStatus.DELIVERED_TO_WAITER - }, { - onError: (error) => { - toast.error(extractErrorMessage(error)) + changeOrderStatus( + { + orderId: order.id, + status: OrderStatus.DELIVERED_TO_WAITER, + }, + { + onError: (error) => { + toast.error(extractErrorMessage(error)) + }, } - }) + ) } const handleDeliverToReceptionist = () => { - changeOrderStatus({ - orderId: order.id, - status: OrderStatus.DELIVERED_TO_RECEPTIONIST - }, { - onError: (error) => { - toast.error(extractErrorMessage(error)) + changeOrderStatus( + { + orderId: order.id, + status: OrderStatus.DELIVERED_TO_RECEPTIONIST, + }, + { + onError: (error) => { + toast.error(extractErrorMessage(error)) + }, } - }) + ) } const handleCancelOrder = (description?: string) => { - changeOrderStatus({ - orderId: order.id, - status: OrderStatus.CANCELED, - params: description ? { description } : undefined - }, { - onError: (error) => { - toast.error(extractErrorMessage(error)) + changeOrderStatus( + { + orderId: order.id, + status: OrderStatus.CANCELED, + params: description ? { description } : undefined, + }, + { + onError: (error) => { + toast.error(extractErrorMessage(error)) + }, } - }) + ) setShowCancelModal(false) } @@ -217,17 +201,22 @@ const OrderDetails: FC = () => { ) } - const handleRefund = (text?: string) => { - if (!text) return - refundPayment({ - orderId: order.id, - params: { description: text } - }, { - onError: (error) => { - toast.error(extractErrorMessage(error)) + const handleRefund = (amount: number, description: string) => { + refundPayment( + { + orderId: order.id, + params: { amount, description }, + }, + { + onSuccess: () => { + toast.success('بازگشت وجه با موفقیت انجام شد') + setRefundModal(null) + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + }, } - }) - setShowRefundModal(false) + ) } const handlePrintOrder = () => { @@ -249,6 +238,48 @@ const OrderDetails: FC = () => { ) } + const handleAddOrderItem = (foodId: string, quantity: number) => { + addOrderItem( + { orderId: order.id, params: { foodId, quantity } }, + { + onSuccess: () => { + toast.success('آیتم به سفارش اضافه شد') + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + }, + } + ) + } + + const handleUpdateItemQuantity = (itemId: string, quantity: number) => { + if (quantity < 1) return + updateOrderItemQuantity( + { orderId: order.id, itemId, params: { quantity } }, + { + onError: (error) => { + toast.error(extractErrorMessage(error)) + }, + } + ) + } + + const handleConfirmRemoveItem = () => { + if (!itemIdToRemove) return + removeOrderItem( + { orderId: order.id, itemId: itemIdToRemove }, + { + onSuccess: () => { + toast.success('آیتم از سفارش حذف شد') + setItemIdToRemove(null) + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + }, + } + ) + } + return (
@@ -260,29 +291,37 @@ const OrderDetails: FC = () => {
- + setShowAddItemModal(true)} + onUpdateQuantity={handleUpdateItemQuantity} + onRemoveItem={(itemId) => setItemIdToRemove(itemId)} + /> +
setShowEditFeesModal(true)} + isRefunding={isRefunding} + onRefund={(defaultAmount, maxAmount) => + setRefundModal({ defaultAmount, maxAmount }) + } /> - {showPaymentVerificationBox && ( - - )} { onDeliverToReceptionist={handleDeliverToReceptionist} onCompleteOrder={handleCompleteOrder} onCancelOrder={() => setShowCancelModal(true)} - onEditFees={() => setShowEditFeesModal(true)} />
@@ -309,7 +347,14 @@ const OrderDetails: FC = () => { onSubmit={handleUpdateOrderFees} /> - {/* مدال کنسل سفارش */} + setShowAddItemModal(false)} + existingItems={order.items} + isLoading={isAddingItem} + onAdd={handleAddOrderItem} + /> + setShowCancelModal(false)} @@ -319,17 +364,24 @@ const OrderDetails: FC = () => { isHasDescription /> - {/* مدال ریفاند */} + setRefundModal(null)} + defaultAmount={refundModal?.defaultAmount ?? 0} + maxAmount={refundModal?.maxAmount ?? 0} + isLoading={isRefunding} + onSubmit={handleRefund} + /> + setShowRefundModal(false)} - onConfrim={handleRefund} - isloading={isRefunding} - label='آیا از ریفاند این سفارش مطمئن هستید؟' - isHasDescription + isOpen={!!itemIdToRemove} + close={() => setItemIdToRemove(null)} + onConfrim={handleConfirmRemoveItem} + isloading={isRemovingItem} + label='آیا از حذف این آیتم مطمئن هستید؟' />
) } -export default OrderDetails \ No newline at end of file +export default OrderDetails diff --git a/src/pages/orders/components/AddOrderItemModal.tsx b/src/pages/orders/components/AddOrderItemModal.tsx new file mode 100644 index 0000000..cb2aa02 --- /dev/null +++ b/src/pages/orders/components/AddOrderItemModal.tsx @@ -0,0 +1,160 @@ +import { type FC, useMemo, useState } from 'react' +import DefaulModal from '@/components/DefaulModal' +import Input from '@/components/Input' +import { Add } from 'iconsax-react' +import { useGetFoods } from '@/pages/food/hooks/useFoodData' +import { formatPrice } from '@/helpers/func' +import { getFoodUnitPrice } from '../hooks/useCreateOrderForm' +import type { OrderItem } from '../types/Types' + +interface AddOrderItemModalProps { + isOpen: boolean + onClose: () => void + existingItems: OrderItem[] + isLoading?: boolean + onAdd: (foodId: string, quantity: number) => void +} + +const AddOrderItemModal: FC = ({ + isOpen, + onClose, + existingItems, + isLoading, + onAdd, +}) => { + const [search, setSearch] = useState('') + const [selectedCategoryId, setSelectedCategoryId] = useState(null) + const { data: foodsData } = useGetFoods({ limit: 500 }) + const foods = foodsData?.data ?? [] + + const categories = useMemo(() => { + const categoryMap = new Map() + foods.forEach((food) => { + if (food.category?.id) { + categoryMap.set(food.category.id, food.category.title) + } + }) + return Array.from(categoryMap.entries()) + .map(([id, title]) => ({ id, title })) + .sort((a, b) => a.title.localeCompare(b.title, 'fa')) + }, [foods]) + + const filteredFoods = useMemo(() => { + let result = foods + if (selectedCategoryId) { + result = result.filter((food) => food.category?.id === selectedCategoryId) + } + const query = search.trim().toLowerCase() + if (query) { + result = result.filter((food) => food.title.toLowerCase().includes(query)) + } + return result + }, [foods, search, selectedCategoryId]) + + const existingQtyByFoodId = useMemo(() => { + const map = new Map() + existingItems.forEach((item) => { + map.set(item.food.id, item.quantity) + }) + return map + }, [existingItems]) + + return ( + +
+ setSearch(e.target.value)} + /> + + {categories.length > 0 && ( +
+ + {categories.map((category) => ( + + ))} +
+ )} + +
+ {filteredFoods.length === 0 ? ( +
+ غذایی یافت نشد +
+ ) : ( + filteredFoods.map((food) => { + const existingQty = existingQtyByFoodId.get(food.id) + return ( +
+ {food.images?.[0] ? ( + {food.title} + ) : ( +
+ )} +
+
{food.title}
+
+ {formatPrice(getFoodUnitPrice(food))} +
+
+ +
+ ) + }) + )} +
+
+ + ) +} + +export default AddOrderItemModal diff --git a/src/pages/orders/components/OrderActions.tsx b/src/pages/orders/components/OrderActions.tsx index 26c5526..9c5e069 100644 --- a/src/pages/orders/components/OrderActions.tsx +++ b/src/pages/orders/components/OrderActions.tsx @@ -5,7 +5,6 @@ interface OrderActionsProps { onPrint: () => void showSendToKitchenButton: boolean showCancelButton: boolean - showEditFeesButton?: boolean isPreparing: boolean isCourierDelivery: boolean isDineInOrCarDelivery: boolean @@ -19,14 +18,12 @@ interface OrderActionsProps { onDeliverToReceptionist: () => void onCompleteOrder: () => void onCancelOrder: () => void - onEditFees?: () => void } const OrderActions: FC = ({ onPrint, showSendToKitchenButton, showCancelButton, - showEditFeesButton = false, isPreparing, isCourierDelivery, isDineInOrCarDelivery, @@ -40,7 +37,6 @@ const OrderActions: FC = ({ onDeliverToReceptionist, onCompleteOrder, onCancelOrder, - onEditFees, }) => { const [loadingAction, setLoadingAction] = useState(null) @@ -88,14 +84,6 @@ const OrderActions: FC = ({ className='bg-slate-700 hover:bg-slate-800' /> - {showEditFeesButton && onEditFees && ( - + + {formatFaNumber(item.quantity)} + + +
+ ) : ( + formatFaNumber(item.quantity) + ) }, { title: 'قیمت واحد', @@ -94,14 +133,43 @@ const OrderItemsDetails: FC = ({ order, getStatusColor } title: 'مبلغ کل', key: 'amount', align: 'right' - } + }, ] + if (canEditItems && onRemoveItem) { + columns.push({ + title: 'عملیات', + key: 'id', + align: 'center', + render: (item) => ( + + ), + }) + } + return (
-
- - جزییات سفارش +
+
+ + جزییات سفارش +
+ {canEditItems && onAddItem && ( +
@@ -234,4 +302,3 @@ const OrderItemsDetails: FC = ({ order, getStatusColor } } export default OrderItemsDetails - diff --git a/src/pages/orders/components/OrderTableColumns.tsx b/src/pages/orders/components/OrderTableColumns.tsx index 1b33362..fbd98ff 100644 --- a/src/pages/orders/components/OrderTableColumns.tsx +++ b/src/pages/orders/components/OrderTableColumns.tsx @@ -7,7 +7,6 @@ import { Link } from 'react-router-dom' import { Pages } from '@/config/Pages' import { formatFaNumber } from '@/helpers/func' import type { TFunction } from 'i18next' -import { PaymentStatusEnum } from '../enum/Enum' interface GetOrderTableColumnsParams { onDelete?: (id: string) => void @@ -26,35 +25,18 @@ const getStatusVariant = (status: string): 'success' | 'error' | 'warning' | 'in return variantMap[status] || 'pending' } -const getPaymentStatusVariant = (status: string): 'success' | 'error' | 'warning' | 'info' | 'pending' => { - const variantMap: Record = { - [PaymentStatusEnum.Paid]: 'success', - [PaymentStatusEnum.Pending]: 'warning', - [PaymentStatusEnum.Failed]: 'error', - [PaymentStatusEnum.Refunded]: 'info', - } - return variantMap[status] || 'pending' -} - -const getPaymentStatusLabel = (status: string): string => { - const labelMap: Record = { - [PaymentStatusEnum.Paid]: 'پرداخت شده', - [PaymentStatusEnum.Pending]: 'در انتظار پرداخت', - [PaymentStatusEnum.Failed]: 'پرداخت ناموفق', - [PaymentStatusEnum.Refunded]: 'بازگشت شده', - } - return labelMap[status] || '-' -} - -const resolvePaymentStatus = (item: Order): string | undefined => { - return item.paymentStatus || item.payments?.[0]?.status +const getShiftAdminName = (item: Order): string => { + const admin = item.cashShift?.admin + if (!admin) return '-' + const name = [admin.firstName, admin.lastName].filter(Boolean).join(' ') + return name || admin.phone || '-' } export const getOrderTableColumns = ({ t }: GetOrderTableColumnsParams): ColumnType[] => { return [ { key: 'orderNumber', - title: 'شماره سفارش', + title: 'شماره', render: (item: Order) => { return formatFaNumber(item.orderNumber) } @@ -68,50 +50,72 @@ export const getOrderTableColumns = ({ t }: GetOrderTableColumnsParams): ColumnT } }, { - key: 'total', - title: 'مبلغ سفارش', - render: (item: Order) => { - return formatPrice(item.total) - } + key: 'shiftAdmin', + title: 'شیفت', + render: (item: Order) => getShiftAdminName(item), }, { key: 'createdAt', - title: 'زمان سفارش', + title: 'تاریخ', render: (item: Order) => { - return formatOptionalDate(item.createdAt, { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - }, - { - key: 'deliveryMethod', - title: 'نوع تحویل', - render: (item: Order) => { - return item.deliveryMethod?.description || '-' - } - }, - { - key: 'paymentMethod', - title: 'نوع پرداخت', - render: (item: Order) => { - return item.paymentMethod?.description || '-' - } - }, - { - key: 'paymentStatus', - title: 'وضعیت پرداخت', - render: (item: Order) => { - const paymentStatus = resolvePaymentStatus(item) - if (!paymentStatus) return '-' + if (!item.createdAt) return '-' return ( - +
+ + {formatOptionalDate(item.createdAt, { + year: 'numeric', + month: '2-digit', + day: '2-digit', + })} + + + {formatOptionalDate(item.createdAt, { + hour: '2-digit', + minute: '2-digit', + })} + +
+ ) + } + }, + { + key: 'methods', + title: 'تحویل / پرداخت', + render: (item: Order) => { + const rows = [ + { label: 'نوع تحویل', value: item.deliveryMethod?.description || '-' }, + { label: 'نوع پرداخت', value: item.paymentMethod?.description || '-' }, + ] + return ( +
+ {rows.map((row) => ( +
+ {row.label} + {row.value} +
+ ))} +
+ ) + } + }, + { + key: 'amounts', + title: 'مبالغ', + render: (item: Order) => { + const rows = [ + { label: 'مبلغ کل', value: formatPrice(item.total) }, + { label: 'پرداخت شده', value: formatPrice(Number(item.paidAmount ?? 0)) }, + { label: 'مانده', value: formatPrice(Number(item.balance ?? 0)) }, + ] + return ( +
+ {rows.map((row) => ( +
+ {row.label} + {row.value} +
+ ))} +
) } }, diff --git a/src/pages/orders/components/PaymentInfo.tsx b/src/pages/orders/components/PaymentInfo.tsx index efbd4a6..a34f0cd 100644 --- a/src/pages/orders/components/PaymentInfo.tsx +++ b/src/pages/orders/components/PaymentInfo.tsx @@ -1,58 +1,75 @@ -import { formatFaNumber, formatPrice } from "@/helpers/func"; +import { formatPrice } from "@/helpers/func"; import { PaymentMethodEnum } from "@/pages/paymentMethods/enum/Enum"; -import { DeliveryMethodEnum } from "@/pages/shipmentMethod/enum/Enum"; +import { Edit2, Refresh } from "iconsax-react"; import type { FC } from "react"; import type { Order } from "../types/Types"; +import { OrderStatus } from "../enum/Enum"; interface PaymentInfoProps { order: Order; - getPaymentStatusColor: (status: string) => string; - getPaymentStatusTextColor: (status: string) => string; - getPaymentStatusText: (status: string) => string; + canEditFees?: boolean; + onEditFees?: () => void; + onRefund?: (defaultAmount: number, maxAmount: number) => void; + isRefunding?: boolean; } -const PaymentInfo: FC = ({ order, getPaymentStatusColor, getPaymentStatusTextColor, getPaymentStatusText }) => { - const paymentStatus = order.payments?.[0]?.status; - - const getDeliveryMethodText = (method: string): string => { - const methodMap: Record = { - [DeliveryMethodEnum.DineIn]: "سرو در رستوران", - [DeliveryMethodEnum.CustomerPickup]: "دریافت از محل", - [DeliveryMethodEnum.DeliveryCar]: "تحویل به خودرو", - [DeliveryMethodEnum.DeliveryCourier]: "بیرون بر", - }; - return methodMap[method] || method; +const getPaymentMethodText = (method: string): string => { + const methodMap: Record = { + [PaymentMethodEnum.Cash]: "پرداخت نقدی", + [PaymentMethodEnum.Online]: "پرداخت آنلاین", + [PaymentMethodEnum.CreditCard]: "پرداخت کارت به کارت", + [PaymentMethodEnum.Wallet]: "کیف پول", }; + return methodMap[method] || method; +}; - const getPaymentMethodText = (method: string): string => { - const methodMap: Record = { - [PaymentMethodEnum.Cash]: "پرداخت نقدی", - [PaymentMethodEnum.Online]: "پرداخت آنلاین", - [PaymentMethodEnum.CreditCard]: "پرداخت کارت به کارت", - [PaymentMethodEnum.Wallet]: "کیف پول", - }; - return methodMap[method] || method; - }; +const PaymentInfo: FC = ({ + order, + canEditFees = false, + onEditFees, + onRefund, + isRefunding = false, +}) => { + const paidAmount = Number(order.paidAmount ?? 0); + const balance = Number(order.balance ?? 0); + const refundedAmount = Number(order.refundedAmount ?? 0); + const isCanceled = order.status === OrderStatus.CANCELED; + const showRefundOnPaid = isCanceled && paidAmount > 0; + const showRefundOnBalance = balance < 0; + + const editFeesButton = + canEditFees && onEditFees ? ( + + ) : null; + + const refundButton = (defaultAmount: number, maxAmount: number) => + onRefund ? ( + + ) : null; return (
-
اطلاعات پرداخت
- -
-
-
{getPaymentStatusText(paymentStatus)}
-
+
خلاصه
-
-
-
روش ارسال
-
{order.deliveryMethod ? getDeliveryMethodText(order.deliveryMethod.method) : "-"}
-
- - - +
روش پرداخت
{order.paymentMethod ? getPaymentMethodText(order.paymentMethod.method) : "-"}
@@ -65,25 +82,11 @@ const PaymentInfo: FC = ({ order, getPaymentStatusColor, getPa
)} - {order.paymentId && ( -
-
شناسه پرداخت
-
{order.paymentId}
-
- )} -
جمع آیتم‌ها
{formatPrice(order.subTotal)}
- {order.itemsDiscount > 0 && ( -
-
تخفیف آیتم‌ها
-
-{formatPrice(order.itemsDiscount)}
-
- )} - {order.couponDiscount > 0 && (
تخفیف کوپن
@@ -91,25 +94,23 @@ const PaymentInfo: FC = ({ order, getPaymentStatusColor, getPa
)} -
-
هزینه ارسال
+
+ هزینه ارسال + {editFeesButton} +
{formatPrice(order.deliveryFee)}
- {(order.packingFee ?? 0) > 0 && ( -
-
هزینه بسته‌بندی
-
{formatPrice(order.packingFee)}
-
- )} +
+
هزینه بسته‌بندی
+
{formatPrice(order.packingFee ?? 0)}
+
- {order.prepareTime != null && order.prepareTime > 0 && ( -
-
زمان آماده‌سازی
-
{formatFaNumber(order.prepareTime)} دقیقه
-
- )} +
+
زمان آماده‌سازی
+
{order.prepareTime ? `${order.prepareTime} دقیقه` : "-"}
+
{order.tax > 0 && (
@@ -118,15 +119,35 @@ const PaymentInfo: FC = ({ order, getPaymentStatusColor, getPa
)} -
-
تعداد آیتم‌ها
-
{formatFaNumber(order.totalItems)}
-
- -
+
مبلغ کل
{formatPrice(order.total)}
+ +
+
پرداخت شده
+
+
{formatPrice(paidAmount)}
+ {showRefundOnPaid && refundButton(paidAmount, paidAmount)} +
+
+ + {refundedAmount > 0 && ( +
+
بازگشت شده
+
{formatPrice(refundedAmount)}
+
+ )} + +
+
مانده
+
+
0 ? "text-orange-600" : "text-green-600"}> + {formatPrice(balance)} +
+ {showRefundOnBalance && refundButton(Math.abs(balance), Math.abs(balance))} +
+
); diff --git a/src/pages/orders/components/PaymentVerificationBox.tsx b/src/pages/orders/components/PaymentVerificationBox.tsx deleted file mode 100644 index 0428390..0000000 --- a/src/pages/orders/components/PaymentVerificationBox.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import Button from '@/components/Button' -import { PaymentMethodEnum } from '@/pages/paymentMethods/enum/Enum' -import type { FC } from 'react' -import type { Order } from '../types/Types' - -interface PaymentVerificationBoxProps { - order: Order - isVerifyingPayment: boolean - onVerifyPayment: () => void - showVerifyButton: boolean -} - -const PaymentVerificationBox: FC = ({ - order, - isVerifyingPayment, - onVerifyPayment, - showVerifyButton, -}) => { - const payment = order.payments?.[0] - const isCash = order.paymentMethod?.method === PaymentMethodEnum.Cash - const isCreditCard = order.paymentMethod?.method === PaymentMethodEnum.CreditCard - const hasDescription = Boolean(payment?.description) - const hasAttachments = Boolean(payment?.attachments && payment.attachments.length > 0) - - return ( -
-
اطلاعات تایید پرداخت
- -
- {isCreditCard && hasDescription && ( -
-
توضیحات پرداخت
-
{payment?.description}
-
- )} - - {isCreditCard && hasAttachments && ( -
-
پیوست پرداخت
-
- {payment?.attachments?.map((attachment, index) => ( - - {`پیوست - - ))} -
-
- )} - - {showVerifyButton && ( -
-
- ) -} - -export default PaymentVerificationBox diff --git a/src/pages/orders/components/PaymentsList.tsx b/src/pages/orders/components/PaymentsList.tsx new file mode 100644 index 0000000..6e8dd7b --- /dev/null +++ b/src/pages/orders/components/PaymentsList.tsx @@ -0,0 +1,239 @@ +import Table from '@/components/Table' +import Status from '@/components/Status' +import Button from '@/components/Button' +import { Moneys } from 'iconsax-react' +import type { FC } from 'react' +import type { ColumnType, RowDataType } from '@/components/types/TableTypes' +import type { Order, OrderPayment } from '../types/Types' +import { formatOptionalDate, formatPrice } from '@/helpers/func' +import { PaymentStatusEnum } from '../enum/Enum' +import { PaymentMethodEnum } from '@/pages/paymentMethods/enum/Enum' + +interface PaymentRow extends RowDataType { + amount: number + method: string + status: string + referenceId: string | null + transactionId: string | null + paidAt: string | null + createdAt: string + description: string | null + attachments: string[] | null + canVerify: boolean +} + +interface PaymentsListProps { + order: Order + isVerifyingPayment: boolean + verifyingPaymentId?: string | null + onVerifyPayment: (paymentId: string) => void +} + +const getPaymentMethodText = (method: string): string => { + const methodMap: Record = { + [PaymentMethodEnum.Cash]: 'نقدی', + [PaymentMethodEnum.Online]: 'آنلاین', + [PaymentMethodEnum.CreditCard]: 'کارت به کارت', + [PaymentMethodEnum.Wallet]: 'کیف پول', + } + return methodMap[method] || method +} + +const getPaymentStatusVariant = (status: string): 'success' | 'error' | 'warning' | 'info' | 'pending' => { + const variantMap: Record = { + [PaymentStatusEnum.Paid]: 'success', + [PaymentStatusEnum.Pending]: 'warning', + [PaymentStatusEnum.Failed]: 'error', + [PaymentStatusEnum.Refunded]: 'info', + } + return variantMap[status] || 'pending' +} + +const getPaymentStatusLabel = (status: string): string => { + const labelMap: Record = { + [PaymentStatusEnum.Paid]: 'پرداخت شده', + [PaymentStatusEnum.Pending]: 'در انتظار پرداخت', + [PaymentStatusEnum.Failed]: 'پرداخت ناموفق', + [PaymentStatusEnum.Refunded]: 'بازگشت شده', + } + return labelMap[status] || status +} + +const canVerifyPayment = (payment: OrderPayment, orderCanceled: boolean): boolean => { + if (orderCanceled) return false + if (payment.status !== PaymentStatusEnum.Pending) return false + return ( + payment.method === PaymentMethodEnum.Cash || + payment.method === PaymentMethodEnum.CreditCard + ) +} + +const PaymentsList: FC = ({ + order, + isVerifyingPayment, + verifyingPaymentId, + onVerifyPayment, +}) => { + const isCanceled = order.status === 'canceled' + const payments = order.payments ?? [] + + const rows: PaymentRow[] = payments.map((payment) => ({ + id: payment.id, + amount: payment.amount, + method: payment.method, + status: payment.status, + referenceId: payment.referenceId, + transactionId: payment.transactionId, + paidAt: payment.paidAt, + createdAt: payment.createdAt, + description: payment.description, + attachments: payment.attachments, + canVerify: canVerifyPayment(payment, isCanceled), + })) + + const columns: ColumnType[] = [ + { + title: 'مبلغ', + key: 'amount', + align: 'right', + render: (item) => formatPrice(item.amount), + }, + { + title: 'روش', + key: 'method', + align: 'right', + render: (item) => getPaymentMethodText(item.method), + }, + { + title: 'وضعیت', + key: 'status', + align: 'center', + render: (item) => ( + + ), + }, + { + title: 'شناسه مرجع', + key: 'referenceId', + align: 'right', + render: (item) => + item.referenceId ? ( + {item.referenceId} + ) : ( + '-' + ), + }, + { + title: 'شناسه تراکنش', + key: 'transactionId', + align: 'right', + render: (item) => + item.transactionId ? ( + {item.transactionId} + ) : ( + '-' + ), + }, + { + title: 'زمان پرداخت', + key: 'paidAt', + align: 'right', + render: (item) => + formatOptionalDate(item.paidAt || item.createdAt, { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }), + }, + { + title: 'عملیات', + key: 'canVerify', + align: 'center', + render: (item) => { + if (!item.canVerify) return '-' + return ( +