user Addresses
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
VITE_TOKEN_NAME = 'dmnu_a_t'
|
||||
VITE_REFRESH_TOKEN_NAME = 'dmnu-a-rt'
|
||||
|
||||
VITE_BASE_URL = 'https://dmenu-api.danakcorp.com'
|
||||
# VITE_BASE_URL = 'http://192.168.99.131:2000'
|
||||
# VITE_BASE_URL = 'https://dmenu-api.danakcorp.com'
|
||||
VITE_BASE_URL = 'http://192.168.99.131:2000'
|
||||
|
||||
VITE_SOCKET_URL = 'https://dmenu-api.danakcorp.com'
|
||||
@@ -14,6 +14,28 @@ export type User = {
|
||||
referrer?: string | null;
|
||||
isActive?: boolean;
|
||||
gender?: boolean;
|
||||
avatarUrl?: string | null;
|
||||
};
|
||||
|
||||
export type UserSavedAddress = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
user: string;
|
||||
title: string;
|
||||
address: string;
|
||||
city: string;
|
||||
province: string;
|
||||
postalCode: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
phone: string;
|
||||
isDefault: boolean;
|
||||
};
|
||||
|
||||
export type UserWithAddresses = User & {
|
||||
addresses?: UserSavedAddress[];
|
||||
};
|
||||
|
||||
export type UserRestaurant = {
|
||||
@@ -73,4 +95,4 @@ export type ImportUsersResult = {
|
||||
|
||||
export type ImportUsersResponse = IResponse<ImportUsersResult>;
|
||||
|
||||
export type SearchUserByPhoneResponse = IResponse<User>;
|
||||
export type SearchUserByPhoneResponse = IResponse<UserWithAddresses>;
|
||||
|
||||
+27
-11
@@ -23,6 +23,14 @@ const CreateOrder: FC = () => {
|
||||
updateItemQuantity,
|
||||
removeItem,
|
||||
isSubmitting,
|
||||
customerAddresses,
|
||||
selectedAddressId,
|
||||
handleUserFound,
|
||||
handleUserNotFound,
|
||||
clearCustomerData,
|
||||
applySavedAddress,
|
||||
selectManualAddress,
|
||||
handleAddressFieldChange,
|
||||
} = useCreateOrderForm()
|
||||
|
||||
return (
|
||||
@@ -42,18 +50,21 @@ const CreateOrder: FC = () => {
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col xl:flex-row gap-7 mt-9'>
|
||||
<div className='flex-1 min-w-0'>
|
||||
<CreateOrderCustomerSection formik={formik} />
|
||||
<div className='flex-1 min-w-0 flex flex-col gap-8'>
|
||||
<CreateOrderCustomerSection
|
||||
formik={formik}
|
||||
onUserFound={handleUserFound}
|
||||
onUserNotFound={handleUserNotFound}
|
||||
onClearCustomerData={clearCustomerData}
|
||||
/>
|
||||
|
||||
<div className='mt-8'>
|
||||
<CreateOrderItemsSection
|
||||
formik={formik}
|
||||
foods={foods}
|
||||
onAddFood={addFoodToOrder}
|
||||
onUpdateQuantity={updateItemQuantity}
|
||||
onRemoveItem={removeItem}
|
||||
/>
|
||||
</div>
|
||||
<CreateOrderItemsSection
|
||||
formik={formik}
|
||||
foods={foods}
|
||||
onAddFood={addFoodToOrder}
|
||||
onUpdateQuantity={updateItemQuantity}
|
||||
onRemoveItem={removeItem}
|
||||
/>
|
||||
|
||||
<CreateOrderDeliverySection
|
||||
formik={formik}
|
||||
@@ -62,6 +73,11 @@ const CreateOrder: FC = () => {
|
||||
isCourierDelivery={isCourierDelivery}
|
||||
isCarDelivery={isCarDelivery}
|
||||
isDineIn={isDineIn}
|
||||
customerAddresses={customerAddresses}
|
||||
selectedAddressId={selectedAddressId}
|
||||
onSelectSavedAddress={applySavedAddress}
|
||||
onSelectManualAddress={selectManualAddress}
|
||||
onAddressFieldChange={handleAddressFieldChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { type FC } from 'react'
|
||||
import { Location } from 'iconsax-react'
|
||||
import type { UserSavedAddress } from '@/pages/customers/types/Types'
|
||||
|
||||
interface CreateOrderAddressSelectorProps {
|
||||
addresses: UserSavedAddress[]
|
||||
selectedAddressId: string | null
|
||||
onSelectAddress: (address: UserSavedAddress) => void
|
||||
onSelectManual: () => void
|
||||
}
|
||||
|
||||
const CreateOrderAddressSelector: FC<CreateOrderAddressSelectorProps> = ({
|
||||
addresses,
|
||||
selectedAddressId,
|
||||
onSelectAddress,
|
||||
onSelectManual,
|
||||
}) => {
|
||||
if (addresses.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isManualSelected = selectedAddressId === null
|
||||
|
||||
return (
|
||||
<div className='mb-6'>
|
||||
<div className='text-sm mb-3'>انتخاب آدرس ذخیرهشده</div>
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 gap-3'>
|
||||
{addresses.map((address) => {
|
||||
const isSelected = selectedAddressId === address.id
|
||||
const location = [address.province, address.city].filter(Boolean).join('، ')
|
||||
|
||||
return (
|
||||
<button
|
||||
key={address.id}
|
||||
type='button'
|
||||
onClick={() => onSelectAddress(address)}
|
||||
className={`text-right p-4 rounded-xl border transition-colors ${
|
||||
isSelected
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-primary/40'
|
||||
}`}
|
||||
>
|
||||
<div className='flex items-center justify-between gap-2'>
|
||||
<div className='text-sm font-medium'>{address.title || 'آدرس'}</div>
|
||||
{address.isDefault && (
|
||||
<span className='text-[10px] px-2 py-0.5 rounded-full bg-primary/10 text-primary'>
|
||||
پیشفرض
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{location && (
|
||||
<div className='text-xs text-description mt-1'>{location}</div>
|
||||
)}
|
||||
<div className='text-xs mt-2 line-clamp-2'>{address.address}</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
|
||||
<button
|
||||
type='button'
|
||||
onClick={onSelectManual}
|
||||
className={`text-right p-4 rounded-xl border transition-colors ${
|
||||
isManualSelected
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-primary/40'
|
||||
}`}
|
||||
>
|
||||
<div className='flex items-center gap-2 text-sm font-medium'>
|
||||
<Location size={16} color='#000' />
|
||||
<span>ورود آدرس جدید</span>
|
||||
</div>
|
||||
<div className='text-xs text-description mt-1'>
|
||||
آدرس را بهصورت دستی وارد کنید
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateOrderAddressSelector
|
||||
@@ -5,6 +5,7 @@ import { toast } from 'react-toastify'
|
||||
import Button from '@/components/Button'
|
||||
import Input from '@/components/Input'
|
||||
import { useSearchUserByPhone } from '@/pages/customers/hooks/useUsersData'
|
||||
import type { UserWithAddresses } from '@/pages/customers/types/Types'
|
||||
import type { CreateOrderFormValues } from '../hooks/useCreateOrderForm'
|
||||
import type { ErrorType } from '@/helpers/types'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
@@ -12,11 +13,19 @@ import { isAxiosError } from 'axios'
|
||||
|
||||
interface CreateOrderCustomerSectionProps {
|
||||
formik: FormikProps<CreateOrderFormValues>
|
||||
onUserFound: (user: UserWithAddresses) => void
|
||||
onUserNotFound: () => void
|
||||
onClearCustomerData: () => void
|
||||
}
|
||||
|
||||
type CustomerSearchState = 'idle' | 'found' | 'not_found'
|
||||
|
||||
const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({ formik }) => {
|
||||
const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({
|
||||
formik,
|
||||
onUserFound,
|
||||
onUserNotFound,
|
||||
onClearCustomerData,
|
||||
}) => {
|
||||
const { mutate: searchUser, isPending: isSearching } = useSearchUserByPhone()
|
||||
const [searchState, setSearchState] = useState<CustomerSearchState>('idle')
|
||||
|
||||
@@ -32,8 +41,7 @@ const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({ formi
|
||||
const user = response?.data
|
||||
if (!user) {
|
||||
setSearchState('not_found')
|
||||
formik.setFieldValue('firstName', '')
|
||||
formik.setFieldValue('lastName', '')
|
||||
onUserNotFound()
|
||||
toast.info('مشتری یافت نشد. اطلاعات را وارد کنید.')
|
||||
return
|
||||
}
|
||||
@@ -41,14 +49,14 @@ const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({ formi
|
||||
formik.setFieldValue('userPhone', user.phone || phone)
|
||||
formik.setFieldValue('firstName', user.firstName || '')
|
||||
formik.setFieldValue('lastName', user.lastName || '')
|
||||
onUserFound(user)
|
||||
setSearchState('found')
|
||||
toast.success('اطلاعات مشتری بارگذاری شد')
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
if (isAxiosError(error) && error.response?.status === 404) {
|
||||
setSearchState('not_found')
|
||||
formik.setFieldValue('firstName', '')
|
||||
formik.setFieldValue('lastName', '')
|
||||
onUserNotFound()
|
||||
toast.info('مشتری یافت نشد. اطلاعات را وارد کنید.')
|
||||
return
|
||||
}
|
||||
@@ -63,6 +71,7 @@ const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({ formi
|
||||
formik.handleChange(e)
|
||||
if (searchState !== 'idle') {
|
||||
setSearchState('idle')
|
||||
onClearCustomerData()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import Textarea from '@/components/Textarea'
|
||||
import { formatPrice } from '@/helpers/func'
|
||||
import type { CreateOrderFormValues } from '../hooks/useCreateOrderForm'
|
||||
import type { RestaurantPaymentMethod, RestaurantShipmentMethod } from '../hooks/useCreateOrderForm'
|
||||
import type { UserSavedAddress } from '@/pages/customers/types/Types'
|
||||
import CreateOrderAddressSelector from './CreateOrderAddressSelector'
|
||||
|
||||
interface CreateOrderDeliverySectionProps {
|
||||
formik: FormikProps<CreateOrderFormValues>
|
||||
@@ -14,6 +16,11 @@ interface CreateOrderDeliverySectionProps {
|
||||
isCourierDelivery: boolean
|
||||
isCarDelivery: boolean
|
||||
isDineIn: boolean
|
||||
customerAddresses: UserSavedAddress[]
|
||||
selectedAddressId: string | null
|
||||
onSelectSavedAddress: (address: UserSavedAddress) => void
|
||||
onSelectManualAddress: () => void
|
||||
onAddressFieldChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void
|
||||
}
|
||||
|
||||
const CreateOrderDeliverySection: FC<CreateOrderDeliverySectionProps> = ({
|
||||
@@ -23,6 +30,11 @@ const CreateOrderDeliverySection: FC<CreateOrderDeliverySectionProps> = ({
|
||||
isCourierDelivery,
|
||||
isCarDelivery,
|
||||
isDineIn,
|
||||
customerAddresses,
|
||||
selectedAddressId,
|
||||
onSelectSavedAddress,
|
||||
onSelectManualAddress,
|
||||
onAddressFieldChange,
|
||||
}) => {
|
||||
const { t } = useTranslation('global')
|
||||
|
||||
@@ -32,8 +44,11 @@ const CreateOrderDeliverySection: FC<CreateOrderDeliverySectionProps> = ({
|
||||
})
|
||||
}
|
||||
|
||||
const isAddressReadOnly =
|
||||
selectedAddressId !== null && customerAddresses.length > 0
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col gap-8'>
|
||||
<div className='w-full bg-white rounded-4xl p-8'>
|
||||
<div className='text-lg font-light'>تحویل و پرداخت</div>
|
||||
|
||||
@@ -108,46 +123,59 @@ const CreateOrderDeliverySection: FC<CreateOrderDeliverySectionProps> = ({
|
||||
</div>
|
||||
|
||||
{isCourierDelivery && (
|
||||
<div className='w-full bg-white rounded-4xl p-8 mt-8'>
|
||||
<div className='w-full bg-white rounded-4xl p-8'>
|
||||
<div className='text-lg font-light'>آدرس تحویل</div>
|
||||
|
||||
<CreateOrderAddressSelector
|
||||
addresses={customerAddresses}
|
||||
selectedAddressId={selectedAddressId}
|
||||
onSelectAddress={onSelectSavedAddress}
|
||||
onSelectManual={onSelectManualAddress}
|
||||
/>
|
||||
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4 mt-6'>
|
||||
<Input
|
||||
label='نام گیرنده'
|
||||
name='userAddress.fullName'
|
||||
value={formik.values.userAddress.fullName}
|
||||
onChange={formik.handleChange}
|
||||
onChange={onAddressFieldChange}
|
||||
readOnly={isAddressReadOnly}
|
||||
/>
|
||||
<Input
|
||||
label='شماره تماس گیرنده'
|
||||
name='userAddress.phone'
|
||||
value={formik.values.userAddress.phone}
|
||||
onChange={formik.handleChange}
|
||||
onChange={onAddressFieldChange}
|
||||
readOnly={isAddressReadOnly}
|
||||
/>
|
||||
<Input
|
||||
label='استان'
|
||||
name='userAddress.province'
|
||||
value={formik.values.userAddress.province}
|
||||
onChange={formik.handleChange}
|
||||
onChange={onAddressFieldChange}
|
||||
readOnly={isAddressReadOnly}
|
||||
/>
|
||||
<Input
|
||||
label='شهر'
|
||||
name='userAddress.city'
|
||||
value={formik.values.userAddress.city}
|
||||
onChange={formik.handleChange}
|
||||
onChange={onAddressFieldChange}
|
||||
readOnly={isAddressReadOnly}
|
||||
/>
|
||||
<Input
|
||||
label='کد پستی'
|
||||
name='userAddress.postalCode'
|
||||
value={formik.values.userAddress.postalCode}
|
||||
onChange={formik.handleChange}
|
||||
onChange={onAddressFieldChange}
|
||||
readOnly={isAddressReadOnly}
|
||||
/>
|
||||
<Input
|
||||
label='عرض جغرافیایی'
|
||||
type='number'
|
||||
name='userAddress.latitude'
|
||||
value={formik.values.userAddress.latitude || ''}
|
||||
onChange={formik.handleChange}
|
||||
onChange={onAddressFieldChange}
|
||||
readOnly={isAddressReadOnly}
|
||||
isNotRequired
|
||||
/>
|
||||
<Input
|
||||
@@ -155,7 +183,8 @@ const CreateOrderDeliverySection: FC<CreateOrderDeliverySectionProps> = ({
|
||||
type='number'
|
||||
name='userAddress.longitude'
|
||||
value={formik.values.userAddress.longitude || ''}
|
||||
onChange={formik.handleChange}
|
||||
onChange={onAddressFieldChange}
|
||||
readOnly={isAddressReadOnly}
|
||||
isNotRequired
|
||||
/>
|
||||
</div>
|
||||
@@ -165,14 +194,15 @@ const CreateOrderDeliverySection: FC<CreateOrderDeliverySectionProps> = ({
|
||||
label='آدرس کامل'
|
||||
name='userAddress.address'
|
||||
value={formik.values.userAddress.address}
|
||||
onChange={formik.handleChange}
|
||||
onChange={onAddressFieldChange}
|
||||
readOnly={isAddressReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isCarDelivery && (
|
||||
<div className='w-full bg-white rounded-4xl p-8 mt-8'>
|
||||
<div className='w-full bg-white rounded-4xl p-8'>
|
||||
<div className='text-lg font-light'>اطلاعات خودرو</div>
|
||||
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4 mt-6'>
|
||||
@@ -204,7 +234,7 @@ const CreateOrderDeliverySection: FC<CreateOrderDeliverySectionProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='w-full bg-white rounded-4xl p-8 mt-8'>
|
||||
<div className='w-full bg-white rounded-4xl p-8'>
|
||||
<div className='text-lg font-light'>توضیحات سفارش</div>
|
||||
<div className='mt-6'>
|
||||
<Textarea
|
||||
@@ -216,7 +246,7 @@ const CreateOrderDeliverySection: FC<CreateOrderDeliverySectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,148 +1,131 @@
|
||||
import { type FC } from 'react'
|
||||
import { ShoppingCart, User } from 'iconsax-react'
|
||||
import type { FormikProps } from 'formik'
|
||||
import { formatFaNumber, formatPrice } from '@/helpers/func'
|
||||
import { PaymentMethodEnum } from '@/pages/paymentMethods/enum/Enum'
|
||||
import { DeliveryMethodEnum } from '@/pages/shipmentMethod/enum/Enum'
|
||||
import type { CreateOrderFormValues } from '../hooks/useCreateOrderForm'
|
||||
import type { RestaurantPaymentMethod, RestaurantShipmentMethod } from '../hooks/useCreateOrderForm'
|
||||
import { formatFaNumber, formatPrice } from "@/helpers/func";
|
||||
import { PaymentMethodEnum } from "@/pages/paymentMethods/enum/Enum";
|
||||
import { DeliveryMethodEnum } from "@/pages/shipmentMethod/enum/Enum";
|
||||
import type { FormikProps } from "formik";
|
||||
import { ShoppingCart, User } from "iconsax-react";
|
||||
import { type FC } from "react";
|
||||
import type { CreateOrderFormValues, RestaurantPaymentMethod, RestaurantShipmentMethod } from "../hooks/useCreateOrderForm";
|
||||
|
||||
interface OrderSummaryData {
|
||||
lineItems: Array<{
|
||||
foodId: string
|
||||
title: string
|
||||
image: string
|
||||
quantity: number
|
||||
unitPrice: number
|
||||
totalPrice: number
|
||||
} | null>
|
||||
subTotal: number
|
||||
deliveryFee: number
|
||||
total: number
|
||||
totalItems: number
|
||||
lineItems: Array<{
|
||||
foodId: string;
|
||||
title: string;
|
||||
image: string;
|
||||
quantity: number;
|
||||
unitPrice: number;
|
||||
totalPrice: number;
|
||||
} | null>;
|
||||
subTotal: number;
|
||||
deliveryFee: number;
|
||||
total: number;
|
||||
totalItems: number;
|
||||
}
|
||||
|
||||
interface CreateOrderSidebarProps {
|
||||
formik: FormikProps<CreateOrderFormValues>
|
||||
orderSummary: OrderSummaryData
|
||||
selectedDeliveryMethod?: RestaurantShipmentMethod
|
||||
selectedPaymentMethod?: RestaurantPaymentMethod
|
||||
formik: FormikProps<CreateOrderFormValues>;
|
||||
orderSummary: OrderSummaryData;
|
||||
selectedDeliveryMethod?: RestaurantShipmentMethod;
|
||||
selectedPaymentMethod?: RestaurantPaymentMethod;
|
||||
}
|
||||
|
||||
const getDeliveryMethodText = (method?: string): string => {
|
||||
const methodMap: Record<string, string> = {
|
||||
[DeliveryMethodEnum.DineIn]: 'سرو در رستوران',
|
||||
[DeliveryMethodEnum.CustomerPickup]: 'دریافت از محل',
|
||||
[DeliveryMethodEnum.DeliveryCar]: 'تحویل به خودرو',
|
||||
[DeliveryMethodEnum.DeliveryCourier]: 'بیرونبر',
|
||||
}
|
||||
return method ? methodMap[method] || method : '-'
|
||||
}
|
||||
const methodMap: Record<string, string> = {
|
||||
[DeliveryMethodEnum.DineIn]: "سرو در رستوران",
|
||||
[DeliveryMethodEnum.CustomerPickup]: "دریافت از محل",
|
||||
[DeliveryMethodEnum.DeliveryCar]: "تحویل به خودرو",
|
||||
[DeliveryMethodEnum.DeliveryCourier]: "بیرونبر",
|
||||
};
|
||||
return method ? methodMap[method] || method : "-";
|
||||
};
|
||||
|
||||
const getPaymentMethodText = (method?: string): string => {
|
||||
const methodMap: Record<string, string> = {
|
||||
[PaymentMethodEnum.Cash]: 'پرداخت نقدی',
|
||||
[PaymentMethodEnum.Online]: 'پرداخت آنلاین',
|
||||
[PaymentMethodEnum.CreditCard]: 'کارت به کارت',
|
||||
[PaymentMethodEnum.Wallet]: 'کیف پول',
|
||||
}
|
||||
return method ? methodMap[method] || method : '-'
|
||||
}
|
||||
const methodMap: Record<string, string> = {
|
||||
[PaymentMethodEnum.Cash]: "پرداخت نقدی",
|
||||
[PaymentMethodEnum.Online]: "پرداخت آنلاین",
|
||||
[PaymentMethodEnum.CreditCard]: "کارت به کارت",
|
||||
[PaymentMethodEnum.Wallet]: "کیف پول",
|
||||
};
|
||||
return method ? methodMap[method] || method : "-";
|
||||
};
|
||||
|
||||
const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
||||
formik,
|
||||
orderSummary,
|
||||
selectedDeliveryMethod,
|
||||
selectedPaymentMethod,
|
||||
}) => {
|
||||
const customerName = [formik.values.firstName, formik.values.lastName]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({ formik, orderSummary, selectedDeliveryMethod, selectedPaymentMethod }) => {
|
||||
const customerName = [formik.values.firstName, formik.values.lastName].filter(Boolean).join(" ");
|
||||
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
<div className='bg-white rounded-4xl p-8'>
|
||||
<div className='flex items-center gap-2 text-lg font-light'>
|
||||
<User size={20} color='#000' />
|
||||
خلاصه سفارش
|
||||
</div>
|
||||
|
||||
<div className='mt-6 space-y-3 text-[13px] font-light'>
|
||||
<div className='flex justify-between items-center border-b border-border border-dashed pb-3'>
|
||||
<span className='text-description'>مشتری</span>
|
||||
<span>{customerName || '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between items-center border-b border-border border-dashed pb-3'>
|
||||
<span className='text-description'>شماره تماس</span>
|
||||
<span>{formik.values.userPhone || '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between items-center border-b border-border border-dashed pb-3'>
|
||||
<span className='text-description'>روش تحویل</span>
|
||||
<span>{getDeliveryMethodText(selectedDeliveryMethod?.method)}</span>
|
||||
</div>
|
||||
<div className='flex justify-between items-center border-b border-border border-dashed pb-3'>
|
||||
<span className='text-description'>روش پرداخت</span>
|
||||
<span>{getPaymentMethodText(selectedPaymentMethod?.method)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='bg-white rounded-4xl p-8'>
|
||||
<div className='flex items-center gap-2 text-lg font-light'>
|
||||
<ShoppingCart size={20} color='#000' />
|
||||
آیتمها
|
||||
{orderSummary.totalItems > 0 && (
|
||||
<span className='text-xs text-description'>
|
||||
({formatFaNumber(orderSummary.totalItems)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{orderSummary.lineItems.length === 0 ? (
|
||||
<div className='mt-6 text-center text-xs text-description py-6'>
|
||||
سبد خالی است
|
||||
</div>
|
||||
) : (
|
||||
<div className='mt-6 space-y-3'>
|
||||
{orderSummary.lineItems.map((item) => {
|
||||
if (!item) return null
|
||||
return (
|
||||
<div
|
||||
key={item.foodId}
|
||||
className='flex justify-between items-start gap-3 text-[13px] border-b border-border border-dashed pb-3'
|
||||
>
|
||||
<div className='flex-1'>
|
||||
<div>{item.title}</div>
|
||||
<div className='text-description mt-1'>
|
||||
{formatFaNumber(item.quantity)} × {formatPrice(item.unitPrice)}
|
||||
</div>
|
||||
</div>
|
||||
<div>{formatPrice(item.totalPrice)}</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='mt-6 space-y-3 text-[13px] font-light'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<span className='text-description'>جمع آیتمها</span>
|
||||
<span>{formatPrice(orderSummary.subTotal)}</span>
|
||||
</div>
|
||||
{orderSummary.deliveryFee > 0 && (
|
||||
<div className='flex justify-between items-center'>
|
||||
<span className='text-description'>هزینه ارسال</span>
|
||||
<span>{formatPrice(orderSummary.deliveryFee)}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex justify-between items-center font-bold text-base pt-3 border-t-2 border-gray-200'>
|
||||
<span>جمع کل (تقریبی)</span>
|
||||
<span>{formatPrice(orderSummary.total)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="bg-white rounded-4xl p-8">
|
||||
<div className="flex items-center gap-2 text-lg font-light">
|
||||
<User size={20} color="#000" />
|
||||
خلاصه سفارش
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateOrderSidebar
|
||||
<div className="mt-6 space-y-3 text-[13px] font-light">
|
||||
<div className="flex justify-between items-center border-b border-border border-dashed pb-3">
|
||||
<span className="text-description">مشتری</span>
|
||||
<span>{customerName || "-"}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-b border-border border-dashed pb-3">
|
||||
<span className="text-description">شماره تماس</span>
|
||||
<span>{formik.values.userPhone || "-"}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-b border-border border-dashed pb-3">
|
||||
<span className="text-description">روش تحویل</span>
|
||||
<span>{getDeliveryMethodText(selectedDeliveryMethod?.method)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-b border-border border-dashed pb-3">
|
||||
<span className="text-description">روش پرداخت</span>
|
||||
<span>{getPaymentMethodText(selectedPaymentMethod?.method)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-4xl p-8">
|
||||
<div className="flex items-center gap-2 text-lg font-light">
|
||||
<ShoppingCart size={20} color="#000" />
|
||||
آیتمها
|
||||
{orderSummary.totalItems > 0 && <span className="text-xs text-description">({formatFaNumber(orderSummary.totalItems)})</span>}
|
||||
</div>
|
||||
|
||||
{orderSummary.lineItems.length === 0 ? (
|
||||
<div className="mt-6 text-center text-xs text-description py-6">سبد خالی است</div>
|
||||
) : (
|
||||
<div className="mt-6 space-y-3">
|
||||
{orderSummary.lineItems.map((item) => {
|
||||
if (!item) return null;
|
||||
return (
|
||||
<div key={item.foodId} className="flex justify-between items-start gap-3 text-[13px] border-b border-border border-dashed pb-3">
|
||||
<div className="flex-1">
|
||||
<div>{item.title}</div>
|
||||
<div className="text-description mt-1">
|
||||
{formatFaNumber(item.quantity)} × {formatPrice(item.unitPrice)}
|
||||
</div>
|
||||
</div>
|
||||
<div>{formatPrice(item.totalPrice)}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-6 space-y-3 text-[13px] font-light">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-description">جمع آیتمها</span>
|
||||
<span>{formatPrice(orderSummary.subTotal)}</span>
|
||||
</div>
|
||||
{orderSummary.deliveryFee > 0 && (
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-description">هزینه ارسال</span>
|
||||
<span>{formatPrice(orderSummary.deliveryFee)}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center font-bold text-sm pt-3 border-t-2 border-gray-200">
|
||||
<span>جمع کل</span>
|
||||
<span>{formatPrice(orderSummary.total)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateOrderSidebar;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { toast } from 'react-toastify'
|
||||
@@ -18,6 +18,8 @@ import type {
|
||||
import type { Food } from '@/pages/food/types/Types'
|
||||
import type { RestaurantShipmentMethod } from '@/pages/shipmentMethod/types/Types'
|
||||
import type { RestaurantPaymentMethod } from '@/pages/paymentMethods/types/Types'
|
||||
import type { UserSavedAddress, UserWithAddresses } from '@/pages/customers/types/Types'
|
||||
import { mapSavedAddressToOrderAddress } from '../utils/mapSavedAddress'
|
||||
import { Pages } from '@/config/Pages'
|
||||
import type { ErrorType } from '@/helpers/types'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
@@ -62,6 +64,8 @@ export const getFoodUnitPrice = (food: Food) => {
|
||||
export const useCreateOrderForm = () => {
|
||||
const navigate = useNavigate()
|
||||
const { mutate: createOrder, isPending: isCreating } = useCreateOrder()
|
||||
const [customerAddresses, setCustomerAddresses] = useState<UserSavedAddress[]>([])
|
||||
const [selectedAddressId, setSelectedAddressId] = useState<string | null>(null)
|
||||
|
||||
const { data: foodsData } = useGetFoods({ limit: 500 })
|
||||
const { data: deliveryMethodsData } = useGetRestaurantShipmentMethods({ limit: 100 })
|
||||
@@ -194,6 +198,72 @@ export const useCreateOrderForm = () => {
|
||||
},
|
||||
})
|
||||
|
||||
const getCustomerFullName = useCallback(
|
||||
(firstName?: string, lastName?: string) =>
|
||||
[firstName ?? formik.values.firstName, lastName ?? formik.values.lastName]
|
||||
.filter(Boolean)
|
||||
.join(' '),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[formik.values.firstName, formik.values.lastName]
|
||||
)
|
||||
|
||||
const applySavedAddress = useCallback(
|
||||
(address: UserSavedAddress, fullName?: string) => {
|
||||
formik.setFieldValue(
|
||||
'userAddress',
|
||||
mapSavedAddressToOrderAddress(address, fullName ?? getCustomerFullName())
|
||||
)
|
||||
setSelectedAddressId(address.id)
|
||||
},
|
||||
[formik, getCustomerFullName]
|
||||
)
|
||||
|
||||
const selectManualAddress = useCallback(() => {
|
||||
setSelectedAddressId(null)
|
||||
formik.setFieldValue('userAddress', {
|
||||
...emptyUserAddress(),
|
||||
fullName: getCustomerFullName(),
|
||||
phone: formik.values.userPhone,
|
||||
})
|
||||
}, [formik, getCustomerFullName])
|
||||
|
||||
const handleUserFound = useCallback(
|
||||
(user: UserWithAddresses) => {
|
||||
const addresses = user.addresses ?? []
|
||||
setCustomerAddresses(addresses)
|
||||
|
||||
if (addresses.length === 0) {
|
||||
setSelectedAddressId(null)
|
||||
formik.setFieldValue('userAddress', {
|
||||
...emptyUserAddress(),
|
||||
fullName: getCustomerFullName(user.firstName, user.lastName),
|
||||
phone: user.phone || formik.values.userPhone,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const defaultAddress =
|
||||
addresses.find((address) => address.isDefault) ?? addresses[0]
|
||||
applySavedAddress(
|
||||
defaultAddress,
|
||||
[user.firstName, user.lastName].filter(Boolean).join(' ')
|
||||
)
|
||||
},
|
||||
[applySavedAddress, formik, getCustomerFullName]
|
||||
)
|
||||
|
||||
const clearCustomerData = useCallback(() => {
|
||||
setCustomerAddresses([])
|
||||
setSelectedAddressId(null)
|
||||
formik.setFieldValue('userAddress', emptyUserAddress())
|
||||
}, [formik])
|
||||
|
||||
const handleUserNotFound = useCallback(() => {
|
||||
clearCustomerData()
|
||||
formik.setFieldValue('firstName', '')
|
||||
formik.setFieldValue('lastName', '')
|
||||
}, [clearCustomerData, formik])
|
||||
|
||||
const cashPaymentMethod = paymentMethods[0]
|
||||
|
||||
useEffect(() => {
|
||||
@@ -215,6 +285,16 @@ export const useCreateOrderForm = () => {
|
||||
selectedDeliveryMethod?.method === DeliveryMethodEnum.DeliveryCar
|
||||
const isDineIn = selectedDeliveryMethod?.method === DeliveryMethodEnum.DineIn
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCourierDelivery || customerAddresses.length === 0 || selectedAddressId) {
|
||||
return
|
||||
}
|
||||
|
||||
const defaultAddress =
|
||||
customerAddresses.find((address) => address.isDefault) ?? customerAddresses[0]
|
||||
applySavedAddress(defaultAddress)
|
||||
}, [isCourierDelivery, customerAddresses, selectedAddressId, applySavedAddress])
|
||||
|
||||
const foodsMap = useMemo(
|
||||
() => new Map(foods.map((food) => [food.id, food])),
|
||||
[foods]
|
||||
@@ -280,6 +360,16 @@ export const useCreateOrderForm = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const handleAddressFieldChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
if (e.target.name?.startsWith('userAddress.')) {
|
||||
setSelectedAddressId(null)
|
||||
}
|
||||
formik.handleChange(e)
|
||||
},
|
||||
[formik]
|
||||
)
|
||||
|
||||
const removeItem = (foodId: string) => {
|
||||
formik.setFieldValue(
|
||||
'items',
|
||||
@@ -302,6 +392,14 @@ export const useCreateOrderForm = () => {
|
||||
updateItemQuantity,
|
||||
removeItem,
|
||||
isSubmitting: isCreating,
|
||||
customerAddresses,
|
||||
selectedAddressId,
|
||||
handleUserFound,
|
||||
handleUserNotFound,
|
||||
clearCustomerData,
|
||||
applySavedAddress,
|
||||
selectManualAddress,
|
||||
handleAddressFieldChange,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { UserSavedAddress } from '@/pages/customers/types/Types'
|
||||
import type { CreateOrderUserAddress } from '../types/Types'
|
||||
|
||||
export const mapSavedAddressToOrderAddress = (
|
||||
saved: UserSavedAddress,
|
||||
fullName: string
|
||||
): CreateOrderUserAddress => ({
|
||||
fullName: fullName || saved.title,
|
||||
phone: saved.phone,
|
||||
city: saved.city,
|
||||
province: saved.province,
|
||||
address: saved.address,
|
||||
postalCode: saved.postalCode,
|
||||
latitude: saved.latitude,
|
||||
longitude: saved.longitude,
|
||||
})
|
||||
Reference in New Issue
Block a user