From 223b1b5e8f81c5e65fb0286b117850193a1f28b1 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Tue, 23 Jun 2026 10:12:56 +0330 Subject: [PATCH] base create order --- src/pages/customers/hooks/useUsersData.ts | 6 + src/pages/customers/service/UsersService.ts | 9 + src/pages/customers/types/Types.ts | 2 + src/pages/orders/Create.tsx | 81 +++++ src/pages/orders/List.tsx | 12 + .../components/CreateOrderCustomerSection.tsx | 159 +++++++++ .../components/CreateOrderDeliverySection.tsx | 223 +++++++++++++ .../components/CreateOrderItemsSection.tsx | 191 +++++++++++ .../orders/components/CreateOrderSidebar.tsx | 148 +++++++++ src/pages/orders/hooks/useCreateOrderForm.ts | 308 ++++++++++++++++++ src/pages/orders/hooks/useOrderData.ts | 10 + src/pages/orders/service/OrderService.ts | 12 + src/pages/orders/types/Types.ts | 40 +++ src/router/Main.tsx | 2 + src/shared/components/OrdersSubMenu.tsx | 4 +- 15 files changed, 1205 insertions(+), 2 deletions(-) create mode 100644 src/pages/orders/Create.tsx create mode 100644 src/pages/orders/components/CreateOrderCustomerSection.tsx create mode 100644 src/pages/orders/components/CreateOrderDeliverySection.tsx create mode 100644 src/pages/orders/components/CreateOrderItemsSection.tsx create mode 100644 src/pages/orders/components/CreateOrderSidebar.tsx create mode 100644 src/pages/orders/hooks/useCreateOrderForm.ts diff --git a/src/pages/customers/hooks/useUsersData.ts b/src/pages/customers/hooks/useUsersData.ts index b814bb5..e5b40e2 100644 --- a/src/pages/customers/hooks/useUsersData.ts +++ b/src/pages/customers/hooks/useUsersData.ts @@ -9,6 +9,12 @@ export const useGetUsers = (params?: GetUsersParams) => { }); }; +export const useSearchUserByPhone = () => { + return useMutation({ + mutationFn: api.searchUserByPhone, + }); +}; + export const useImportUsersFromExcel = () => { const queryClient = useQueryClient(); diff --git a/src/pages/customers/service/UsersService.ts b/src/pages/customers/service/UsersService.ts index 2028671..ba312b7 100644 --- a/src/pages/customers/service/UsersService.ts +++ b/src/pages/customers/service/UsersService.ts @@ -3,6 +3,7 @@ import type { GetCustomersResponse, GetUsersParams, ImportUsersResponse, + SearchUserByPhoneResponse, } from "@/pages/customers/types/Types"; export const getUsers = async (params?: GetUsersParams) => { @@ -12,6 +13,14 @@ export const getUsers = async (params?: GetUsersParams) => { return data; }; +export const searchUserByPhone = async (phone: string) => { + const { data } = await axios.get( + "/admin/users/search-by-phone", + { params: { phone } }, + ); + return data; +}; + export const importUsersFromExcel = async (file: File) => { const formData = new FormData(); formData.append("file", file); diff --git a/src/pages/customers/types/Types.ts b/src/pages/customers/types/Types.ts index fac29d2..3090a9d 100644 --- a/src/pages/customers/types/Types.ts +++ b/src/pages/customers/types/Types.ts @@ -72,3 +72,5 @@ export type ImportUsersResult = { }; export type ImportUsersResponse = IResponse; + +export type SearchUserByPhoneResponse = IResponse; diff --git a/src/pages/orders/Create.tsx b/src/pages/orders/Create.tsx new file mode 100644 index 0000000..6721dbf --- /dev/null +++ b/src/pages/orders/Create.tsx @@ -0,0 +1,81 @@ +import { type FC } from 'react' +import { TickCircle } from 'iconsax-react' +import Button from '@/components/Button' +import { useCreateOrderForm } from './hooks/useCreateOrderForm' +import CreateOrderCustomerSection from './components/CreateOrderCustomerSection' +import CreateOrderItemsSection from './components/CreateOrderItemsSection' +import CreateOrderDeliverySection from './components/CreateOrderDeliverySection' +import CreateOrderSidebar from './components/CreateOrderSidebar' + +const CreateOrder: FC = () => { + const { + formik, + foods, + deliveryMethods, + cashPaymentMethod, + selectedDeliveryMethod, + selectedPaymentMethod, + isCourierDelivery, + isCarDelivery, + isDineIn, + orderSummary, + addFoodToOrder, + updateItemQuantity, + removeItem, + isSubmitting, + } = useCreateOrderForm() + + return ( +
+
+

ثبت سفارش جدید

+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ +
+
+
+ ) +} + +export default CreateOrder diff --git a/src/pages/orders/List.tsx b/src/pages/orders/List.tsx index 7e5d172..ea00dff 100644 --- a/src/pages/orders/List.tsx +++ b/src/pages/orders/List.tsx @@ -1,7 +1,11 @@ import { type FC } from 'react' import { useTranslation } from 'react-i18next' +import { Add } from 'iconsax-react' +import { Link } from 'react-router-dom' 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 { useOrderFilters } from './hooks/useOrderFilters' import { getOrderTableColumns } from './components/OrderTableColumns' @@ -32,6 +36,14 @@ const OrdersList: FC = () => {

لیست سفارشات

+ + +
diff --git a/src/pages/orders/components/CreateOrderCustomerSection.tsx b/src/pages/orders/components/CreateOrderCustomerSection.tsx new file mode 100644 index 0000000..95a1782 --- /dev/null +++ b/src/pages/orders/components/CreateOrderCustomerSection.tsx @@ -0,0 +1,159 @@ +import { type FC, useState } from 'react' +import { SearchNormal, TickCircle, UserAdd } from 'iconsax-react' +import type { FormikProps } from 'formik' +import { toast } from 'react-toastify' +import Button from '@/components/Button' +import Input from '@/components/Input' +import { useSearchUserByPhone } from '@/pages/customers/hooks/useUsersData' +import type { CreateOrderFormValues } from '../hooks/useCreateOrderForm' +import type { ErrorType } from '@/helpers/types' +import { extractErrorMessage } from '@/config/func' +import { isAxiosError } from 'axios' + +interface CreateOrderCustomerSectionProps { + formik: FormikProps +} + +type CustomerSearchState = 'idle' | 'found' | 'not_found' + +const CreateOrderCustomerSection: FC = ({ formik }) => { + const { mutate: searchUser, isPending: isSearching } = useSearchUserByPhone() + const [searchState, setSearchState] = useState('idle') + + const handleSearch = () => { + const phone = formik.values.userPhone.trim() + if (!phone) { + toast.error('شماره تلفن را وارد کنید') + return + } + + searchUser(phone, { + onSuccess: (response) => { + const user = response?.data + if (!user) { + setSearchState('not_found') + formik.setFieldValue('firstName', '') + formik.setFieldValue('lastName', '') + toast.info('مشتری یافت نشد. اطلاعات را وارد کنید.') + return + } + + formik.setFieldValue('userPhone', user.phone || phone) + formik.setFieldValue('firstName', user.firstName || '') + formik.setFieldValue('lastName', user.lastName || '') + setSearchState('found') + toast.success('اطلاعات مشتری بارگذاری شد') + }, + onError: (error: ErrorType) => { + if (isAxiosError(error) && error.response?.status === 404) { + setSearchState('not_found') + formik.setFieldValue('firstName', '') + formik.setFieldValue('lastName', '') + toast.info('مشتری یافت نشد. اطلاعات را وارد کنید.') + return + } + + setSearchState('idle') + toast.error(extractErrorMessage(error, 'خطا در جستجوی مشتری')) + }, + }) + } + + const handlePhoneChange = (e: React.ChangeEvent) => { + formik.handleChange(e) + if (searchState !== 'idle') { + setSearchState('idle') + } + } + + const isCustomerLocked = searchState !== 'not_found' + + return ( +
+
اطلاعات مشتری
+ +
+
+ { + if (e.key === 'Enter') { + e.preventDefault() + handleSearch() + } + }} + error_text={ + formik.touched.userPhone && formik.errors.userPhone + ? formik.errors.userPhone + : '' + } + /> +
+ +
+ + {searchState === 'found' && ( +
+ + مشتری یافت شد و اطلاعات تکمیل شد +
+ )} + + {searchState === 'not_found' && ( +
+ + مشتری یافت نشد. نام و نام خانوادگی را وارد کنید. +
+ )} + +
+ + +
+ + {searchState === 'idle' && ( +
+ ابتدا با شماره تلفن مشتری را جستجو کنید. +
+ )} +
+ ) +} + +export default CreateOrderCustomerSection diff --git a/src/pages/orders/components/CreateOrderDeliverySection.tsx b/src/pages/orders/components/CreateOrderDeliverySection.tsx new file mode 100644 index 0000000..7e56b3f --- /dev/null +++ b/src/pages/orders/components/CreateOrderDeliverySection.tsx @@ -0,0 +1,223 @@ +import { type FC } from 'react' +import type { FormikProps } from 'formik' +import { useTranslation } from 'react-i18next' +import Input from '@/components/Input' +import Textarea from '@/components/Textarea' +import { formatPrice } from '@/helpers/func' +import type { CreateOrderFormValues } from '../hooks/useCreateOrderForm' +import type { RestaurantPaymentMethod, RestaurantShipmentMethod } from '../hooks/useCreateOrderForm' + +interface CreateOrderDeliverySectionProps { + formik: FormikProps + deliveryMethods: RestaurantShipmentMethod[] + cashPaymentMethod?: RestaurantPaymentMethod + isCourierDelivery: boolean + isCarDelivery: boolean + isDineIn: boolean +} + +const CreateOrderDeliverySection: FC = ({ + formik, + deliveryMethods, + cashPaymentMethod, + isCourierDelivery, + isCarDelivery, + isDineIn, +}) => { + const { t } = useTranslation('global') + + const getDeliveryLabel = (method: string) => { + return t(`shipment.${method}`, { + defaultValue: method, + }) + } + + return ( + <> +
+
تحویل و پرداخت
+ +
+
روش تحویل
+
+ {deliveryMethods.map((method) => { + const isSelected = formik.values.deliveryMethodId === method.id + return ( + + ) + })} +
+ {formik.touched.deliveryMethodId && formik.errors.deliveryMethodId && ( +
+ {formik.errors.deliveryMethodId} +
+ )} +
+ +
+
روش پرداخت
+
+
پرداخت نقدی
+ {cashPaymentMethod?.description && ( +
+ {cashPaymentMethod.description} +
+ )} +
+ {!cashPaymentMethod && ( +
+ روش پرداخت نقدی فعال یافت نشد +
+ )} +
+ + {(isDineIn || isCarDelivery) && ( +
+ +
+ )} +
+ + {isCourierDelivery && ( +
+
آدرس تحویل
+ +
+ + + + + + + +
+ +
+