diff --git a/src/pages/orders/Details.tsx b/src/pages/orders/Details.tsx
index b36e671..569bc06 100644
--- a/src/pages/orders/Details.tsx
+++ b/src/pages/orders/Details.tsx
@@ -9,12 +9,16 @@ import {
useAddOrderItem,
useUpdateOrderItemQuantity,
useRemoveOrderItem,
+ useSendPleaseComeSms,
} from './hooks/useOrderData'
-import { useParams } from 'react-router-dom'
+import { useNavigate, useParams } from 'react-router-dom'
+import { ArrowRight2 } from 'iconsax-react'
import { formatFaNumber } from '@/helpers/func'
+import { Pages } from '@/config/Pages'
import { OrderStatus, PaymentStatusEnum } from './enum/Enum'
import { PaymentMethodEnum } from '@/pages/paymentMethods/enum/Enum'
import { DeliveryMethodEnum } from '@/pages/shipmentMethod/enum/Enum'
+import Button from '@/components/Button'
import ModalConfrim from '@/components/ModalConfrim'
import CustomerInfo from './components/CustomerInfo'
import OrderItemsDetails from './components/OrderItemsDetails'
@@ -25,6 +29,7 @@ import UpdateOrderFeesModal from './components/UpdateOrderFeesModal'
import AddOrderItemModal from './components/AddOrderItemModal'
import RefundPaymentModal from './components/RefundPaymentModal'
import AddPaymentModal from './components/AddPaymentModal'
+import SendPleaseComeSmsModal from './components/SendPleaseComeSmsModal'
import { toast } from 'react-toastify'
import { extractErrorMessage } from '@/config/func'
import { printOrderReceipt } from './print/orderReceiptPrint'
@@ -32,6 +37,7 @@ import type { UpdateOrderFeesType } from './types/Types'
const OrderDetails: FC = () => {
const { id } = useParams()
+ const navigate = useNavigate()
const { data: orderData, isLoading } = useGetOrderById(id!)
const { mutate: verifyPayment, isPending: isVerifyingPayment } = useVerifyPayment()
const { mutate: changeOrderStatus, isPending: isChangingStatus } = useChangeOrderStatus()
@@ -41,8 +47,10 @@ const OrderDetails: FC = () => {
const { mutate: addOrderItem, isPending: isAddingItem } = useAddOrderItem()
const { mutate: updateOrderItemQuantity, isPending: isUpdatingItemQuantity } = useUpdateOrderItemQuantity()
const { mutate: removeOrderItem, isPending: isRemovingItem } = useRemoveOrderItem()
+ const { mutate: sendPleaseComeSms, isPending: isSendingPleaseComeSms } = useSendPleaseComeSms()
const [showCancelModal, setShowCancelModal] = useState(false)
+ const [showPleaseComeSmsModal, setShowPleaseComeSmsModal] = useState(false)
const [refundModal, setRefundModal] = useState<{ defaultAmount: number; maxAmount: number } | null>(null)
const [showAddPaymentModal, setShowAddPaymentModal] = useState(false)
const [showEditFeesModal, setShowEditFeesModal] = useState(false)
@@ -98,6 +106,7 @@ const OrderDetails: FC = () => {
OrderStatus.DELIVERED_TO_RECEPTIONIST,
OrderStatus.SHIPPED,
].includes(order.status as OrderStatus)
+ const canSendPleaseComeSms = Boolean(order.user?.phone)
const isCourierDelivery = order.deliveryMethod?.method === DeliveryMethodEnum.DeliveryCourier
const isDineInOrCarDelivery =
order.deliveryMethod?.method === DeliveryMethodEnum.DineIn ||
@@ -306,12 +315,33 @@ const OrderDetails: FC = () => {
)
}
+ const handleConfirmPleaseComeSms = () => {
+ sendPleaseComeSms(order.id, {
+ onSuccess: (response) => {
+ toast.success(response.message || 'پیامک در صف ارسال قرار گرفت')
+ setShowPleaseComeSmsModal(false)
+ },
+ onError: (error) => {
+ toast.error(extractErrorMessage(error))
+ },
+ })
+ }
+
return (
سفارش {formatFaNumber(order.orderNumber)}
+
@@ -356,13 +386,16 @@ const OrderDetails: FC = () => {
isCustomerPickup={isCustomerPickup}
isCanceled={isCanceled}
canCompleteOrder={canCompleteOrder}
+ canSendPleaseComeSms={canSendPleaseComeSms}
isChangingStatus={isChangingStatus}
+ isSendingPleaseComeSms={isSendingPleaseComeSms}
onSendToKitchen={handleSendToKitchen}
onDeliverToCourier={handleDeliverToCourier}
onDeliverToWaiter={handleDeliverToWaiter}
onDeliverToReceptionist={handleDeliverToReceptionist}
onCompleteOrder={handleCompleteOrder}
onCancelOrder={() => setShowCancelModal(true)}
+ onSendPleaseComeSms={() => setShowPleaseComeSmsModal(true)}
/>
@@ -416,6 +449,15 @@ const OrderDetails: FC = () => {
isloading={isRemovingItem}
label='آیا از حذف این آیتم مطمئن هستید؟'
/>
+
+ setShowPleaseComeSmsModal(false)}
+ isLoading={isSendingPleaseComeSms}
+ customerName={order.user?.firstName || ''}
+ restaurantName={order.restaurant?.name || ''}
+ onConfirm={handleConfirmPleaseComeSms}
+ />
)
}
diff --git a/src/pages/orders/List.tsx b/src/pages/orders/List.tsx
index d69abf0..e91b174 100644
--- a/src/pages/orders/List.tsx
+++ b/src/pages/orders/List.tsx
@@ -1,4 +1,4 @@
-import { type FC } from 'react'
+import { type FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Add } from 'iconsax-react'
import { Link } from 'react-router-dom'
@@ -6,12 +6,15 @@ import Table from '@/components/Table'
import Filters from '@/components/Filters'
import Button from '@/components/Button'
import { Pages } from '@/config/Pages'
-import { useGetOrders } from './hooks/useOrderData'
+import { useGetOrders, useSendPleaseComeSms } from './hooks/useOrderData'
import { useOrderFilters } from './hooks/useOrderFilters'
import { getOrderTableColumns } from './components/OrderTableColumns'
import { useOrderFiltersFields } from './components/OrderFiltersFields'
+import SendPleaseComeSmsModal from './components/SendPleaseComeSmsModal'
import type { Order } from './types/Types'
import type { RowDataType } from '@/components/types/TableTypes'
+import { toast } from 'react-toastify'
+import { extractErrorMessage } from '@/config/func'
const OrdersList: FC = () => {
const { t } = useTranslation('global')
@@ -24,13 +27,32 @@ const OrdersList: FC = () => {
} = useOrderFilters()
const { data: ordersData, isLoading } = useGetOrders(apiParams)
+ const { mutate: sendPleaseComeSms, isPending: isSendingSms } = useSendPleaseComeSms()
+ const [smsOrder, setSmsOrder] = useState(null)
const orders = ordersData?.data || []
- const columns = getOrderTableColumns({ t })
+ const columns = getOrderTableColumns({
+ t,
+ onSendPleaseComeSms: (order) => setSmsOrder(order),
+ })
const filterFields = useOrderFiltersFields()
const totalPages = ordersData?.meta?.totalPages || 1
+ const handleConfirmSendSms = () => {
+ if (!smsOrder) return
+
+ sendPleaseComeSms(smsOrder.id, {
+ onSuccess: (response) => {
+ toast.success(response.message || 'پیامک در صف ارسال قرار گرفت')
+ setSmsOrder(null)
+ },
+ onError: (error) => {
+ toast.error(extractErrorMessage(error))
+ },
+ })
+ }
+
return (
@@ -64,8 +86,17 @@ const OrdersList: FC = () => {
onPageChange: handlePageChange,
}}
/>
+
+ setSmsOrder(null)}
+ isLoading={isSendingSms}
+ customerName={smsOrder?.user?.firstName || ''}
+ restaurantName={smsOrder?.restaurant?.name || ''}
+ onConfirm={handleConfirmSendSms}
+ />
)
}
-export default OrdersList
\ No newline at end of file
+export default OrdersList
diff --git a/src/pages/orders/components/OrderActions.tsx b/src/pages/orders/components/OrderActions.tsx
index 9746800..bd7009e 100644
--- a/src/pages/orders/components/OrderActions.tsx
+++ b/src/pages/orders/components/OrderActions.tsx
@@ -1,144 +1,162 @@
-import Button from '@/components/Button'
-import { type FC, useState, useEffect } from 'react'
+import Button from "@/components/Button";
+import { type FC, useState, useEffect } from "react";
interface OrderActionsProps {
- onPrint: () => void
- showSendToKitchenButton: boolean
- showCancelButton: boolean
- isPreparing: boolean
- isCourierDelivery: boolean
- isDineInOrCarDelivery: boolean
- isCustomerPickup: boolean
- isCanceled: boolean
- canCompleteOrder: boolean
- isChangingStatus: boolean
- onSendToKitchen: () => void
- onDeliverToCourier: () => void
- onDeliverToWaiter: () => void
- onDeliverToReceptionist: () => void
- onCompleteOrder: () => void
- onCancelOrder: () => void
+ onPrint: () => void;
+ showSendToKitchenButton: boolean;
+ showCancelButton: boolean;
+ isPreparing: boolean;
+ isCourierDelivery: boolean;
+ isDineInOrCarDelivery: boolean;
+ isCustomerPickup: boolean;
+ isCanceled: boolean;
+ canCompleteOrder: boolean;
+ canSendPleaseComeSms: boolean;
+ isChangingStatus: boolean;
+ isSendingPleaseComeSms?: boolean;
+ onSendToKitchen: () => void;
+ onDeliverToCourier: () => void;
+ onDeliverToWaiter: () => void;
+ onDeliverToReceptionist: () => void;
+ onCompleteOrder: () => void;
+ onCancelOrder: () => void;
+ onSendPleaseComeSms: () => void;
}
const OrderActions: FC
= ({
- onPrint,
- showSendToKitchenButton,
- showCancelButton,
- isPreparing,
- isCourierDelivery,
- isDineInOrCarDelivery,
- isCustomerPickup,
- isCanceled,
- canCompleteOrder,
- isChangingStatus,
- onSendToKitchen,
- onDeliverToCourier,
- onDeliverToWaiter,
- onDeliverToReceptionist,
- onCompleteOrder,
- onCancelOrder,
+ onPrint,
+ showSendToKitchenButton,
+ showCancelButton,
+ isPreparing,
+ isCourierDelivery,
+ isDineInOrCarDelivery,
+ isCustomerPickup,
+ isCanceled,
+ canCompleteOrder,
+ canSendPleaseComeSms,
+ isChangingStatus,
+ isSendingPleaseComeSms,
+ onSendToKitchen,
+ onDeliverToCourier,
+ onDeliverToWaiter,
+ onDeliverToReceptionist,
+ onCompleteOrder,
+ onCancelOrder,
+ onSendPleaseComeSms,
}) => {
- const [loadingAction, setLoadingAction] = useState(null)
+ const [loadingAction, setLoadingAction] = useState(null);
- useEffect(() => {
- if (!isChangingStatus) {
- setLoadingAction(null)
- }
- }, [isChangingStatus])
-
- const handleSendToKitchen = () => {
- setLoadingAction('sendToKitchen')
- onSendToKitchen()
+ useEffect(() => {
+ if (!isChangingStatus) {
+ setLoadingAction(null);
}
+ }, [isChangingStatus]);
- const handleDeliverToCourier = () => {
- setLoadingAction('deliverToCourier')
- onDeliverToCourier()
- }
+ const handleSendToKitchen = () => {
+ setLoadingAction("sendToKitchen");
+ onSendToKitchen();
+ };
- const handleDeliverToWaiter = () => {
- setLoadingAction('deliverToWaiter')
- onDeliverToWaiter()
- }
+ const handleDeliverToCourier = () => {
+ setLoadingAction("deliverToCourier");
+ onDeliverToCourier();
+ };
- const handleDeliverToReceptionist = () => {
- setLoadingAction('deliverToReceptionist')
- onDeliverToReceptionist()
- }
+ const handleDeliverToWaiter = () => {
+ setLoadingAction("deliverToWaiter");
+ onDeliverToWaiter();
+ };
- const handleCancelOrder = () => {
- setLoadingAction('cancelOrder')
- onCancelOrder()
- }
+ const handleDeliverToReceptionist = () => {
+ setLoadingAction("deliverToReceptionist");
+ onDeliverToReceptionist();
+ };
- const handleCompleteOrder = () => {
- setLoadingAction('completeOrder')
- onCompleteOrder()
- }
+ const handleCancelOrder = () => {
+ setLoadingAction("cancelOrder");
+ onCancelOrder();
+ };
- return (
-
-
+ const handleCompleteOrder = () => {
+ setLoadingAction("completeOrder");
+ onCompleteOrder();
+ };
- {!isCanceled && showSendToKitchenButton && (
-
- )}
+ return (
+
+
+
- {!isCanceled && isPreparing && isCourierDelivery && (
-
- )}
+ {canSendPleaseComeSms && (
+
+ )}
+ {!isCanceled && showCancelButton && (
+
+ )}
+
- {!isCanceled && isPreparing && isDineInOrCarDelivery && (
-
- )}
+ {!isCanceled && showSendToKitchenButton && (
+
+ )}
- {!isCanceled && isPreparing && isCustomerPickup && (
-
- )}
+ {!isCanceled && isPreparing && isCourierDelivery && (
+
+ )}
- {!isCanceled && canCompleteOrder && (
-
- )}
+ {!isCanceled && isPreparing && isDineInOrCarDelivery && (
+
+ )}
- {!isCanceled && showCancelButton && (
-
- )}
-
- )
-}
+ {!isCanceled && isPreparing && isCustomerPickup && (
+
+ )}
-export default OrderActions
+ {!isCanceled && canCompleteOrder && (
+
+ )}
+
+ );
+};
+
+export default OrderActions;
diff --git a/src/pages/orders/components/OrderTableColumns.tsx b/src/pages/orders/components/OrderTableColumns.tsx
index fbd98ff..a03d479 100644
--- a/src/pages/orders/components/OrderTableColumns.tsx
+++ b/src/pages/orders/components/OrderTableColumns.tsx
@@ -1,146 +1,189 @@
-import { Eye } from 'iconsax-react'
-import type { ColumnType } from '@/components/types/TableTypes'
-import type { Order } from '../types/Types'
-import { formatPrice, formatOptionalDate } from '@/helpers/func'
-import Status from '@/components/Status'
-import { Link } from 'react-router-dom'
-import { Pages } from '@/config/Pages'
-import { formatFaNumber } from '@/helpers/func'
-import type { TFunction } from 'i18next'
+import { Eye, MessageText } from "iconsax-react";
+import type { ColumnType } from "@/components/types/TableTypes";
+import type { Order } from "../types/Types";
+import { formatPrice, formatOptionalDate } from "@/helpers/func";
+import Status from "@/components/Status";
+import { Link } from "react-router-dom";
+import { Pages } from "@/config/Pages";
+import { formatFaNumber } from "@/helpers/func";
+import type { TFunction } from "i18next";
interface GetOrderTableColumnsParams {
- onDelete?: (id: string) => void
- isDeleting?: boolean
- t: TFunction
+ onDelete?: (id: string) => void;
+ isDeleting?: boolean;
+ onSendPleaseComeSms?: (order: Order) => void;
+ t: TFunction;
}
-const getStatusVariant = (status: string): 'success' | 'error' | 'warning' | 'info' | 'pending' => {
- const variantMap: Record = {
- 'new': 'pending',
- 'preparing': 'warning',
- 'shipped': 'info',
- 'completed': 'success',
- 'canceled': 'error',
- }
- return variantMap[status] || 'pending'
-}
+const getStatusVariant = (
+ status: string,
+): "success" | "error" | "warning" | "info" | "pending" => {
+ const variantMap: Record<
+ string,
+ "success" | "error" | "warning" | "info" | "pending"
+ > = {
+ new: "pending",
+ preparing: "warning",
+ shipped: "info",
+ completed: "success",
+ canceled: "error",
+ };
+ return variantMap[status] || "pending";
+};
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 || '-'
-}
+ 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: 'شماره',
- render: (item: Order) => {
- return formatFaNumber(item.orderNumber)
- }
- },
- {
- key: 'customerName',
- title: 'نام مشتری',
- render: (item: Order) => {
- const name = [item.user?.firstName, item.user?.lastName].filter(Boolean).join(' ')
- return name || '-'
- }
- },
- {
- key: 'shiftAdmin',
- title: 'شیفت',
- render: (item: Order) => getShiftAdminName(item),
- },
- {
- key: 'createdAt',
- title: 'تاریخ',
- render: (item: Order) => {
- 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}
-
- ))}
-
- )
- }
- },
- {
- key: 'status',
- title: 'وضعیت',
- render: (item: Order) => {
- return (
-
- )
- }
- },
- {
- key: 'details',
- title: '',
- render: (item: Order) => {
- return (
-
-
-
- )
- }
- },
- ]
-}
+export const getOrderTableColumns = ({
+ t,
+ onSendPleaseComeSms,
+}: GetOrderTableColumnsParams): ColumnType[] => {
+ return [
+ {
+ key: "orderNumber",
+ title: "شماره",
+ render: (item: Order) => {
+ return formatFaNumber(item.orderNumber);
+ },
+ },
+ {
+ key: "customerName",
+ title: "نام مشتری",
+ render: (item: Order) => {
+ const name = [item.user?.firstName, item.user?.lastName]
+ .filter(Boolean)
+ .join(" ");
+ return name || "-";
+ },
+ },
+ {
+ key: "shiftAdmin",
+ title: "شیفت",
+ render: (item: Order) => getShiftAdminName(item),
+ },
+ {
+ key: "createdAt",
+ title: "تاریخ",
+ render: (item: Order) => {
+ 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}
+
+ ))}
+
+ );
+ },
+ },
+ {
+ key: "status",
+ title: "وضعیت",
+ render: (item: Order) => {
+ return (
+
+ );
+ },
+ },
+ {
+ key: "actions",
+ title: "",
+ render: (item: Order) => {
+ const canSendSms = Boolean(item.user?.phone);
+ return (
+
+ {canSendSms && onSendPleaseComeSms && (
+
+ )}
+
+
+
+
+ );
+ },
+ },
+ ];
+};
diff --git a/src/pages/orders/components/SendPleaseComeSmsModal.tsx b/src/pages/orders/components/SendPleaseComeSmsModal.tsx
new file mode 100644
index 0000000..7dd6f20
--- /dev/null
+++ b/src/pages/orders/components/SendPleaseComeSmsModal.tsx
@@ -0,0 +1,57 @@
+import { type FC } from 'react'
+import DefaulModal from '@/components/DefaulModal'
+import Button from '@/components/Button'
+
+interface SendPleaseComeSmsModalProps {
+ isOpen: boolean
+ onClose: () => void
+ isLoading?: boolean
+ customerName: string
+ restaurantName: string
+ onConfirm: () => void
+}
+
+const SendPleaseComeSmsModal: FC = ({
+ isOpen,
+ onClose,
+ isLoading,
+ customerName,
+ restaurantName,
+ onConfirm,
+}) => {
+ const previewMessage = `${customerName || '#NAME#'} عزیز لطفا به پیشخوان ${restaurantName || '#REST#'} مراجعه کنید.`
+
+ return (
+
+
+
+ متن پیامک:
+
+
+ {previewMessage}
+
+
+
+
+
+
+
+
+ )
+}
+
+export default SendPleaseComeSmsModal
diff --git a/src/pages/orders/hooks/useOrderData.ts b/src/pages/orders/hooks/useOrderData.ts
index b2018d9..72cb15e 100644
--- a/src/pages/orders/hooks/useOrderData.ts
+++ b/src/pages/orders/hooks/useOrderData.ts
@@ -155,3 +155,9 @@ export const useRemoveOrderItem = () => {
},
});
};
+
+export const useSendPleaseComeSms = () => {
+ return useMutation({
+ mutationFn: (orderId: string) => api.sendPleaseComeSms(orderId),
+ });
+};
diff --git a/src/pages/orders/service/OrderService.ts b/src/pages/orders/service/OrderService.ts
index 7eeb1ef..188e15d 100644
--- a/src/pages/orders/service/OrderService.ts
+++ b/src/pages/orders/service/OrderService.ts
@@ -191,3 +191,10 @@ export const getDailyOrderReport = async (
);
return data;
};
+
+export const sendPleaseComeSms = async (orderId: string): Promise<{ message: string }> => {
+ const { data } = await axios.post<{ message: string }>(
+ `/admin/orders/${orderId}/please-come`
+ );
+ return data;
+};