This commit is contained in:
+18
-23
@@ -4,7 +4,6 @@ import Button from '@/components/Button'
|
|||||||
import { useCreateOrderForm } from './hooks/useCreateOrderForm'
|
import { useCreateOrderForm } from './hooks/useCreateOrderForm'
|
||||||
import CreateOrderCustomerSection from './components/CreateOrderCustomerSection'
|
import CreateOrderCustomerSection from './components/CreateOrderCustomerSection'
|
||||||
import CreateOrderItemsSection from './components/CreateOrderItemsSection'
|
import CreateOrderItemsSection from './components/CreateOrderItemsSection'
|
||||||
import CreateOrderDeliverySection from './components/CreateOrderDeliverySection'
|
|
||||||
import CreateOrderSidebar from './components/CreateOrderSidebar'
|
import CreateOrderSidebar from './components/CreateOrderSidebar'
|
||||||
|
|
||||||
const CreateOrder: FC = () => {
|
const CreateOrder: FC = () => {
|
||||||
@@ -35,8 +34,8 @@ const CreateOrder: FC = () => {
|
|||||||
} = useCreateOrderForm()
|
} = useCreateOrderForm()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-5'>
|
<div className='flex flex-col h-[calc(100vh-113px)] xl:overflow-hidden overflow-y-auto -mb-20'>
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center flex-shrink-0 mb-3'>
|
||||||
<h1 className='text-lg font-light'>ثبت سفارش جدید</h1>
|
<h1 className='text-lg font-light'>ثبت سفارش جدید</h1>
|
||||||
<Button
|
<Button
|
||||||
className='w-fit px-6'
|
className='w-fit px-6'
|
||||||
@@ -50,27 +49,30 @@ const CreateOrder: FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col xl:flex-row gap-7 mt-9'>
|
<CreateOrderCustomerSection
|
||||||
<div className='flex-1 min-w-0 flex flex-col gap-8'>
|
formik={formik}
|
||||||
<CreateOrderCustomerSection
|
onUserFound={handleUserFound}
|
||||||
formik={formik}
|
onUserNotFound={handleUserNotFound}
|
||||||
onUserFound={handleUserFound}
|
onClearCustomerData={clearCustomerData}
|
||||||
onUserNotFound={handleUserNotFound}
|
/>
|
||||||
onClearCustomerData={clearCustomerData}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
<div className='flex-1 min-h-0 grid grid-cols-1 xl:grid-cols-[1fr_minmax(0,320px)] gap-4 mt-3 items-stretch'>
|
||||||
|
<div className='min-h-0 h-full overflow-hidden flex flex-col'>
|
||||||
<CreateOrderItemsSection
|
<CreateOrderItemsSection
|
||||||
formik={formik}
|
formik={formik}
|
||||||
foods={foods}
|
foods={foods}
|
||||||
onAddFood={addFoodToOrder}
|
onAddFood={addFoodToOrder}
|
||||||
onUpdateQuantity={updateItemQuantity}
|
|
||||||
onRemoveItem={removeItem}
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<CreateOrderDeliverySection
|
<div className='min-h-0 h-full flex flex-col overflow-hidden'>
|
||||||
|
<CreateOrderSidebar
|
||||||
formik={formik}
|
formik={formik}
|
||||||
|
orderSummary={orderSummary}
|
||||||
deliveryMethods={deliveryMethods}
|
deliveryMethods={deliveryMethods}
|
||||||
cashPaymentMethod={cashPaymentMethod}
|
cashPaymentMethod={cashPaymentMethod}
|
||||||
|
selectedDeliveryMethod={selectedDeliveryMethod}
|
||||||
|
selectedPaymentMethod={selectedPaymentMethod}
|
||||||
isCourierDelivery={isCourierDelivery}
|
isCourierDelivery={isCourierDelivery}
|
||||||
isCarDelivery={isCarDelivery}
|
isCarDelivery={isCarDelivery}
|
||||||
isDineIn={isDineIn}
|
isDineIn={isDineIn}
|
||||||
@@ -80,15 +82,8 @@ const CreateOrder: FC = () => {
|
|||||||
onSelectManualAddress={selectManualAddress}
|
onSelectManualAddress={selectManualAddress}
|
||||||
onAddressFieldChange={handleAddressFieldChange}
|
onAddressFieldChange={handleAddressFieldChange}
|
||||||
onLocationSelect={handleLocationSelect}
|
onLocationSelect={handleLocationSelect}
|
||||||
/>
|
onUpdateQuantity={updateItemQuantity}
|
||||||
</div>
|
onRemoveItem={removeItem}
|
||||||
|
|
||||||
<div className='w-full xl:w-[330px] flex-shrink-0 xl:sticky xl:top-4 xl:self-start'>
|
|
||||||
<CreateOrderSidebar
|
|
||||||
formik={formik}
|
|
||||||
orderSummary={orderSummary}
|
|
||||||
selectedDeliveryMethod={selectedDeliveryMethod}
|
|
||||||
selectedPaymentMethod={selectedPaymentMethod}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { type FC, useState } from 'react'
|
import { type FC, useState } from 'react'
|
||||||
import { SearchNormal, TickCircle, UserAdd } from 'iconsax-react'
|
import { Profile, SearchNormal, TickCircle, UserAdd } from 'iconsax-react'
|
||||||
import type { FormikProps } from 'formik'
|
import type { FormikProps } from 'formik'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import Button from '@/components/Button'
|
import { clx } from '@/helpers/utils'
|
||||||
import Input from '@/components/Input'
|
import Input from '@/components/Input'
|
||||||
import { useSearchUserByPhone } from '@/pages/customers/hooks/useUsersData'
|
import { useSearchUserByPhone } from '@/pages/customers/hooks/useUsersData'
|
||||||
import type { UserWithAddresses } from '@/pages/customers/types/Types'
|
import type { UserWithAddresses } from '@/pages/customers/types/Types'
|
||||||
@@ -76,78 +76,134 @@ const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isCustomerLocked = searchState !== 'not_found'
|
const isCustomerLocked = searchState !== 'not_found'
|
||||||
|
const isNameDisabled = searchState === 'idle'
|
||||||
|
const customerName = [formik.values.firstName, formik.values.lastName]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
|
||||||
|
const phoneError =
|
||||||
|
formik.touched.userPhone && formik.errors.userPhone
|
||||||
|
? formik.errors.userPhone
|
||||||
|
: ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full bg-white rounded-4xl p-8'>
|
<div className='w-full bg-white rounded-4xl px-5 py-4 flex-shrink-0'>
|
||||||
<div className='text-lg font-light'>اطلاعات مشتری</div>
|
<div className='flex items-center justify-between gap-3 mb-3'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
<div className='flex flex-col sm:flex-row gap-3 mt-6 sm:items-end'>
|
<div
|
||||||
<div className='flex-1'>
|
className={clx(
|
||||||
<Input
|
'w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 transition-colors',
|
||||||
label='شماره تلفن'
|
searchState === 'found'
|
||||||
name='userPhone'
|
? 'bg-green-50'
|
||||||
placeholder='۰۹۱۲۱۲۳۴۵۶۷'
|
: searchState === 'not_found'
|
||||||
value={formik.values.userPhone}
|
? 'bg-[#EEF0F7]'
|
||||||
onChange={handlePhoneChange}
|
: 'bg-[#EEF0F7]',
|
||||||
onKeyDown={(e) => {
|
)}
|
||||||
if (e.key === 'Enter') {
|
>
|
||||||
e.preventDefault()
|
{searchState === 'found' ? (
|
||||||
handleSearch()
|
<TickCircle size={18} color='#16A34A' />
|
||||||
}
|
) : searchState === 'not_found' ? (
|
||||||
}}
|
<UserAdd size={18} color='#8C90A3' />
|
||||||
error_text={
|
) : (
|
||||||
formik.touched.userPhone && formik.errors.userPhone
|
<Profile size={18} color='#8C90A3' />
|
||||||
? formik.errors.userPhone
|
)}
|
||||||
: ''
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
className='w-full sm:w-fit px-6'
|
|
||||||
isloading={isSearching}
|
|
||||||
onClick={handleSearch}
|
|
||||||
>
|
|
||||||
<div className='flex gap-2 items-center'>
|
|
||||||
<SearchNormal size={18} color='#fff' />
|
|
||||||
<span>جستجو</span>
|
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
<div>
|
||||||
|
<div className='text-sm font-light'>اطلاعات مشتری</div>
|
||||||
|
{searchState === 'found' && customerName && (
|
||||||
|
<div className='text-[11px] text-green-600 mt-0.5'>
|
||||||
|
{customerName}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{searchState === 'not_found' && (
|
||||||
|
<div className='text-[11px] text-description mt-0.5'>
|
||||||
|
مشتری جدید
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{searchState === 'found' && (
|
||||||
|
<span className='text-[11px] text-green-600 bg-green-50 rounded-full px-3 py-1 flex-shrink-0'>
|
||||||
|
مشتری یافت شد
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{searchState === 'idle' && (
|
||||||
|
<span className='text-[11px] text-description bg-[#F8F9FC] rounded-full px-3 py-1 flex-shrink-0 hidden sm:inline'>
|
||||||
|
ابتدا شماره را جستجو کنید
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{searchState === 'found' && (
|
<div className='grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-[1.2fr_1fr_1fr] gap-3 xl:items-end'>
|
||||||
<div className='mt-4 flex items-center gap-2 text-xs text-green-600 bg-green-50 rounded-xl px-4 py-3'>
|
<div className='w-full'>
|
||||||
<TickCircle size={16} color='#16A34A' />
|
<label className='text-xs text-description'>شماره تلفن</label>
|
||||||
<span>مشتری یافت شد و اطلاعات تکمیل شد</span>
|
<div className='flex mt-1'>
|
||||||
|
<input
|
||||||
|
name='userPhone'
|
||||||
|
placeholder='۰۹۱۲۱۲۳۴۵۶۷'
|
||||||
|
value={formik.values.userPhone}
|
||||||
|
onChange={handlePhoneChange}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault()
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={clx(
|
||||||
|
'flex-1 min-w-0 h-10 text-xs px-4 border border-border bg-white',
|
||||||
|
'rounded-r-xl border-l-0 focus:outline-none',
|
||||||
|
phoneError && 'border-red-400',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
disabled={isSearching}
|
||||||
|
onClick={handleSearch}
|
||||||
|
className={clx(
|
||||||
|
'h-10 px-4 flex items-center gap-1.5 flex-shrink-0',
|
||||||
|
'bg-primary text-white text-xs rounded-l-xl',
|
||||||
|
'hover:opacity-90 transition-opacity disabled:opacity-60',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isSearching ? (
|
||||||
|
<span className='w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin' />
|
||||||
|
) : (
|
||||||
|
<SearchNormal size={16} color='#fff' />
|
||||||
|
)}
|
||||||
|
<span className='hidden sm:inline'>جستجو</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{phoneError && (
|
||||||
|
<p className='text-[11px] text-red-500 mt-1'>{phoneError}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{searchState === 'not_found' && (
|
|
||||||
<div className='mt-4 flex items-center gap-2 text-xs text-description bg-[#F8F9FC] rounded-xl px-4 py-3'>
|
|
||||||
<UserAdd size={16} color='#8C90A3' />
|
|
||||||
<span>مشتری یافت نشد. نام و نام خانوادگی را وارد کنید.</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4 mt-6'>
|
|
||||||
<Input
|
<Input
|
||||||
label='نام'
|
label='نام'
|
||||||
name='firstName'
|
name='firstName'
|
||||||
|
placeholder={isNameDisabled ? 'پس از جستجو' : 'نام'}
|
||||||
value={formik.values.firstName}
|
value={formik.values.firstName}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
readOnly={isCustomerLocked}
|
readOnly={isCustomerLocked}
|
||||||
|
disabled={isNameDisabled}
|
||||||
|
className={isNameDisabled ? 'bg-[#F8F9FC] border-0 text-description cursor-not-allowed' : ''}
|
||||||
error_text={
|
error_text={
|
||||||
formik.touched.firstName && formik.errors.firstName
|
formik.touched.firstName && formik.errors.firstName
|
||||||
? formik.errors.firstName
|
? formik.errors.firstName
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label='نام خانوادگی'
|
label='نام خانوادگی'
|
||||||
name='lastName'
|
name='lastName'
|
||||||
|
placeholder={isNameDisabled ? 'پس از جستجو' : 'نام خانوادگی'}
|
||||||
value={formik.values.lastName}
|
value={formik.values.lastName}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
readOnly={isCustomerLocked}
|
readOnly={isCustomerLocked}
|
||||||
|
disabled={isNameDisabled}
|
||||||
|
className={isNameDisabled ? 'bg-[#F8F9FC] border-0 text-description cursor-not-allowed' : ''}
|
||||||
error_text={
|
error_text={
|
||||||
formik.touched.lastName && formik.errors.lastName
|
formik.touched.lastName && formik.errors.lastName
|
||||||
? formik.errors.lastName
|
? formik.errors.lastName
|
||||||
@@ -155,12 +211,6 @@ const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{searchState === 'idle' && (
|
|
||||||
<div className='mt-4 text-xs text-description'>
|
|
||||||
ابتدا با شماره تلفن مشتری را جستجو کنید.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,335 @@
|
|||||||
|
import { type FC, useState } from 'react'
|
||||||
|
import type { FormikProps } from 'formik'
|
||||||
|
import { Edit2, Location } from 'iconsax-react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Button from '@/components/Button'
|
||||||
|
import Input from '@/components/Input'
|
||||||
|
import RadioGroup from '@/components/RadioGroup'
|
||||||
|
import Textarea from '@/components/Textarea'
|
||||||
|
import DefaulModal from '@/components/DefaulModal'
|
||||||
|
import LocationInput from '@/pages/settings/components/LocationInput'
|
||||||
|
import LocationPicker from '@/pages/settings/components/LocationPicker'
|
||||||
|
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 CreateOrderDeliveryModalProps {
|
||||||
|
open: boolean
|
||||||
|
onClose: () => void
|
||||||
|
formik: FormikProps<CreateOrderFormValues>
|
||||||
|
deliveryMethods: RestaurantShipmentMethod[]
|
||||||
|
cashPaymentMethod?: RestaurantPaymentMethod
|
||||||
|
isCourierDelivery: boolean
|
||||||
|
isCarDelivery: boolean
|
||||||
|
isDineIn: boolean
|
||||||
|
customerAddresses: UserSavedAddress[]
|
||||||
|
selectedAddressId: string | null
|
||||||
|
onSelectSavedAddress: (address: UserSavedAddress) => void
|
||||||
|
onSelectManualAddress: () => void
|
||||||
|
onAddressFieldChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void
|
||||||
|
onLocationSelect: (lat: number, lng: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateOrderDeliveryModal: FC<CreateOrderDeliveryModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
formik,
|
||||||
|
deliveryMethods,
|
||||||
|
cashPaymentMethod,
|
||||||
|
isCourierDelivery,
|
||||||
|
isCarDelivery,
|
||||||
|
isDineIn,
|
||||||
|
customerAddresses,
|
||||||
|
selectedAddressId,
|
||||||
|
onSelectSavedAddress,
|
||||||
|
onSelectManualAddress,
|
||||||
|
onAddressFieldChange,
|
||||||
|
onLocationSelect,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [isAddressModalOpen, setIsAddressModalOpen] = useState(false)
|
||||||
|
const [isLocationModalOpen, setIsLocationModalOpen] = useState(false)
|
||||||
|
|
||||||
|
const getDeliveryLabel = (method: string) => {
|
||||||
|
return t(`shipment.${method}`, { defaultValue: method })
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAddressReadOnly =
|
||||||
|
selectedAddressId !== null && customerAddresses.length > 0
|
||||||
|
|
||||||
|
const handleMapLocationSelect = (lat: number, lng: number) => {
|
||||||
|
onLocationSelect(lat, lng)
|
||||||
|
setIsLocationModalOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const latitude = formik.values.userAddress.latitude
|
||||||
|
const longitude = formik.values.userAddress.longitude
|
||||||
|
const hasAddress = Boolean(formik.values.userAddress.address?.trim())
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
formik.setFieldTouched('deliveryMethodId', true, false)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DefaulModal
|
||||||
|
open={open}
|
||||||
|
close={onClose}
|
||||||
|
isHeader={true}
|
||||||
|
title_header='تحویل و پرداخت'
|
||||||
|
width={560}
|
||||||
|
>
|
||||||
|
<div className='p-1 max-h-[70vh] overflow-y-auto'>
|
||||||
|
<div>
|
||||||
|
<div className='text-sm mb-3'>روش تحویل</div>
|
||||||
|
<RadioGroup
|
||||||
|
items={deliveryMethods.map((method) => ({
|
||||||
|
label:
|
||||||
|
method.deliveryFee > 0
|
||||||
|
? `${getDeliveryLabel(method.method)} (${formatPrice(method.deliveryFee)})`
|
||||||
|
: getDeliveryLabel(method.method),
|
||||||
|
value: method.id,
|
||||||
|
}))}
|
||||||
|
selected={formik.values.deliveryMethodId}
|
||||||
|
onChange={(value) => {
|
||||||
|
formik.setFieldValue('deliveryMethodId', value)
|
||||||
|
formik.setFieldTouched('deliveryMethodId', true, false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{formik.touched.deliveryMethodId && formik.errors.deliveryMethodId && (
|
||||||
|
<div className='text-xs text-red-500 mt-2'>
|
||||||
|
{formik.errors.deliveryMethodId}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6'>
|
||||||
|
<div className='text-sm mb-3'>روش پرداخت</div>
|
||||||
|
{cashPaymentMethod ? (
|
||||||
|
<RadioGroup
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: cashPaymentMethod.description
|
||||||
|
? `پرداخت نقدی (${cashPaymentMethod.description})`
|
||||||
|
: 'پرداخت نقدی',
|
||||||
|
value: cashPaymentMethod.id,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
selected={formik.values.paymentMethodId}
|
||||||
|
onChange={(value) => formik.setFieldValue('paymentMethodId', value)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className='text-xs text-red-500'>
|
||||||
|
روش پرداخت نقدی فعال یافت نشد
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isDineIn && (
|
||||||
|
<div className='mt-6'>
|
||||||
|
<Input
|
||||||
|
label='شماره میز'
|
||||||
|
name='tableNumber'
|
||||||
|
placeholder='مثال: ۱۲'
|
||||||
|
value={formik.values.tableNumber}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isCourierDelivery && (
|
||||||
|
<div className='mt-6 pt-5 border-t border-border border-dashed'>
|
||||||
|
<div className='text-sm mb-3'>آدرس تحویل</div>
|
||||||
|
|
||||||
|
{customerAddresses.length > 0 && (
|
||||||
|
<CreateOrderAddressSelector
|
||||||
|
addresses={customerAddresses}
|
||||||
|
selectedAddressId={selectedAddressId}
|
||||||
|
onSelectAddress={onSelectSavedAddress}
|
||||||
|
onSelectManual={onSelectManualAddress}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasAddress ? (
|
||||||
|
<div className='flex items-start gap-2 p-3 bg-[#F8F9FC] rounded-xl'>
|
||||||
|
<Location size={16} color='#8C90A3' className='flex-shrink-0 mt-0.5' />
|
||||||
|
<div className='flex-1 min-w-0 text-xs'>
|
||||||
|
{formik.values.userAddress.address}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => setIsAddressModalOpen(true)}
|
||||||
|
className='flex-shrink-0'
|
||||||
|
>
|
||||||
|
<Edit2 size={16} color='#000' />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
className='w-full'
|
||||||
|
onClick={() => setIsAddressModalOpen(true)}
|
||||||
|
>
|
||||||
|
ثبت آدرس تحویل
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isCarDelivery && (
|
||||||
|
<div className='mt-6 pt-5 border-t border-border border-dashed'>
|
||||||
|
<div className='text-sm mb-3'>اطلاعات خودرو</div>
|
||||||
|
<div className='grid grid-cols-2 gap-4'>
|
||||||
|
<Input
|
||||||
|
label='مدل خودرو'
|
||||||
|
name='carAddress.carModel'
|
||||||
|
value={formik.values.carAddress.carModel}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label='رنگ خودرو'
|
||||||
|
name='carAddress.carColor'
|
||||||
|
value={formik.values.carAddress.carColor}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label='پلاک'
|
||||||
|
name='carAddress.plateNumber'
|
||||||
|
value={formik.values.carAddress.plateNumber}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label='شماره تماس'
|
||||||
|
name='carAddress.phone'
|
||||||
|
value={formik.values.carAddress.phone}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='mt-6'>
|
||||||
|
<Textarea
|
||||||
|
label='توضیحات سفارش'
|
||||||
|
name='description'
|
||||||
|
placeholder='توضیحات اضافی...'
|
||||||
|
value={formik.values.description}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
isNotRequired
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6 flex justify-end gap-3 sticky bottom-0 bg-white pt-3'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={onClose}
|
||||||
|
className='h-10 px-6 rounded-xl border border-border text-sm hover:bg-[#F8F9FC] transition-colors'
|
||||||
|
>
|
||||||
|
انصراف
|
||||||
|
</button>
|
||||||
|
<Button type='button' className='w-fit px-6' onClick={handleConfirm}>
|
||||||
|
تأیید
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
|
||||||
|
<DefaulModal
|
||||||
|
open={isAddressModalOpen}
|
||||||
|
close={() => setIsAddressModalOpen(false)}
|
||||||
|
isHeader={true}
|
||||||
|
title_header='آدرس تحویل'
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
<div className='p-2'>
|
||||||
|
<CreateOrderAddressSelector
|
||||||
|
addresses={customerAddresses}
|
||||||
|
selectedAddressId={selectedAddressId}
|
||||||
|
onSelectAddress={onSelectSavedAddress}
|
||||||
|
onSelectManual={onSelectManualAddress}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='grid grid-cols-2 gap-3 mt-4'>
|
||||||
|
<Input
|
||||||
|
label='عنوان آدرس'
|
||||||
|
name='userAddress.title'
|
||||||
|
placeholder='مثال: منزل'
|
||||||
|
value={formik.values.userAddress.title}
|
||||||
|
onChange={onAddressFieldChange}
|
||||||
|
readOnly={isAddressReadOnly}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label='شماره تماس'
|
||||||
|
name='userAddress.phone'
|
||||||
|
value={formik.values.userAddress.phone}
|
||||||
|
onChange={onAddressFieldChange}
|
||||||
|
readOnly={isAddressReadOnly}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label='کد پستی'
|
||||||
|
name='userAddress.postalCode'
|
||||||
|
value={formik.values.userAddress.postalCode}
|
||||||
|
onChange={onAddressFieldChange}
|
||||||
|
readOnly={isAddressReadOnly}
|
||||||
|
isNotRequired
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={isAddressReadOnly ? 'pointer-events-none opacity-60' : ''}>
|
||||||
|
<LocationInput
|
||||||
|
label='موقعیت روی نقشه'
|
||||||
|
latitude={latitude || undefined}
|
||||||
|
longitude={longitude || undefined}
|
||||||
|
onClick={() => setIsLocationModalOpen(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-4'>
|
||||||
|
<Textarea
|
||||||
|
label='آدرس کامل'
|
||||||
|
name='userAddress.address'
|
||||||
|
value={formik.values.userAddress.address}
|
||||||
|
onChange={onAddressFieldChange}
|
||||||
|
readOnly={isAddressReadOnly}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-4 flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
className='w-fit px-6'
|
||||||
|
onClick={() => setIsAddressModalOpen(false)}
|
||||||
|
>
|
||||||
|
تأیید
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
|
||||||
|
<DefaulModal
|
||||||
|
open={isLocationModalOpen}
|
||||||
|
close={() => setIsLocationModalOpen(false)}
|
||||||
|
isHeader={true}
|
||||||
|
title_header='انتخاب موقعیت روی نقشه'
|
||||||
|
width={800}
|
||||||
|
>
|
||||||
|
<div className='p-4'>
|
||||||
|
<LocationPicker
|
||||||
|
initialPosition={
|
||||||
|
latitude && longitude ? [latitude, longitude] : undefined
|
||||||
|
}
|
||||||
|
onLocationSelect={handleMapLocationSelect}
|
||||||
|
/>
|
||||||
|
<div className='mt-4 text-xs text-gray-600 text-center'>
|
||||||
|
روی نقشه کلیک کنید تا موقعیت را انتخاب کنید
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateOrderDeliveryModal
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
import { type FC, useState } from 'react'
|
|
||||||
import type { FormikProps } from 'formik'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import Input from '@/components/Input'
|
|
||||||
import RadioGroup from '@/components/RadioGroup'
|
|
||||||
import Textarea from '@/components/Textarea'
|
|
||||||
import DefaulModal from '@/components/DefaulModal'
|
|
||||||
import LocationInput from '@/pages/settings/components/LocationInput'
|
|
||||||
import LocationPicker from '@/pages/settings/components/LocationPicker'
|
|
||||||
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>
|
|
||||||
deliveryMethods: RestaurantShipmentMethod[]
|
|
||||||
cashPaymentMethod?: RestaurantPaymentMethod
|
|
||||||
isCourierDelivery: boolean
|
|
||||||
isCarDelivery: boolean
|
|
||||||
isDineIn: boolean
|
|
||||||
customerAddresses: UserSavedAddress[]
|
|
||||||
selectedAddressId: string | null
|
|
||||||
onSelectSavedAddress: (address: UserSavedAddress) => void
|
|
||||||
onSelectManualAddress: () => void
|
|
||||||
onAddressFieldChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void
|
|
||||||
onLocationSelect: (lat: number, lng: number) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const CreateOrderDeliverySection: FC<CreateOrderDeliverySectionProps> = ({
|
|
||||||
formik,
|
|
||||||
deliveryMethods,
|
|
||||||
cashPaymentMethod,
|
|
||||||
isCourierDelivery,
|
|
||||||
isCarDelivery,
|
|
||||||
isDineIn,
|
|
||||||
customerAddresses,
|
|
||||||
selectedAddressId,
|
|
||||||
onSelectSavedAddress,
|
|
||||||
onSelectManualAddress,
|
|
||||||
onAddressFieldChange,
|
|
||||||
onLocationSelect,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation('global')
|
|
||||||
const [isLocationModalOpen, setIsLocationModalOpen] = useState(false)
|
|
||||||
|
|
||||||
const getDeliveryLabel = (method: string) => {
|
|
||||||
return t(`shipment.${method}`, {
|
|
||||||
defaultValue: method,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAddressReadOnly =
|
|
||||||
selectedAddressId !== null && customerAddresses.length > 0
|
|
||||||
|
|
||||||
const handleMapLocationSelect = (lat: number, lng: number) => {
|
|
||||||
onLocationSelect(lat, lng)
|
|
||||||
setIsLocationModalOpen(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const latitude = formik.values.userAddress.latitude
|
|
||||||
const longitude = formik.values.userAddress.longitude
|
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
<div className='mt-6'>
|
|
||||||
<div className='text-sm mb-3'>روش تحویل</div>
|
|
||||||
<RadioGroup
|
|
||||||
items={deliveryMethods.map((method) => ({
|
|
||||||
label:
|
|
||||||
method.deliveryFee > 0
|
|
||||||
? `${getDeliveryLabel(method.method)} (${formatPrice(method.deliveryFee)})`
|
|
||||||
: getDeliveryLabel(method.method),
|
|
||||||
value: method.id,
|
|
||||||
}))}
|
|
||||||
selected={formik.values.deliveryMethodId}
|
|
||||||
onChange={(value) => {
|
|
||||||
formik.setFieldValue('deliveryMethodId', value)
|
|
||||||
formik.setFieldTouched('deliveryMethodId', true, false)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{formik.touched.deliveryMethodId && formik.errors.deliveryMethodId && (
|
|
||||||
<div className='text-xs text-red-500 mt-2'>
|
|
||||||
{formik.errors.deliveryMethodId}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-8'>
|
|
||||||
<div className='text-sm mb-3'>روش پرداخت</div>
|
|
||||||
{cashPaymentMethod ? (
|
|
||||||
<RadioGroup
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
label: cashPaymentMethod.description
|
|
||||||
? `پرداخت نقدی (${cashPaymentMethod.description})`
|
|
||||||
: 'پرداخت نقدی',
|
|
||||||
value: cashPaymentMethod.id,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
selected={formik.values.paymentMethodId}
|
|
||||||
onChange={(value) => formik.setFieldValue('paymentMethodId', value)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className='text-xs text-red-500'>
|
|
||||||
روش پرداخت نقدی فعال یافت نشد
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isDineIn && (
|
|
||||||
<div className='mt-8'>
|
|
||||||
<Input
|
|
||||||
label='شماره میز'
|
|
||||||
name='tableNumber'
|
|
||||||
placeholder='مثال: ۱۲'
|
|
||||||
value={formik.values.tableNumber}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isCourierDelivery && (
|
|
||||||
<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.title'
|
|
||||||
placeholder='مثال: منزل'
|
|
||||||
value={formik.values.userAddress.title}
|
|
||||||
onChange={onAddressFieldChange}
|
|
||||||
readOnly={isAddressReadOnly}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label='شماره تماس'
|
|
||||||
name='userAddress.phone'
|
|
||||||
value={formik.values.userAddress.phone}
|
|
||||||
onChange={onAddressFieldChange}
|
|
||||||
readOnly={isAddressReadOnly}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label='کد پستی'
|
|
||||||
name='userAddress.postalCode'
|
|
||||||
value={formik.values.userAddress.postalCode}
|
|
||||||
onChange={onAddressFieldChange}
|
|
||||||
readOnly={isAddressReadOnly}
|
|
||||||
isNotRequired
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={isAddressReadOnly ? 'pointer-events-none opacity-60' : ''}>
|
|
||||||
<LocationInput
|
|
||||||
label='موقعیت روی نقشه'
|
|
||||||
latitude={latitude || undefined}
|
|
||||||
longitude={longitude || undefined}
|
|
||||||
onClick={() => setIsLocationModalOpen(true)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-5'>
|
|
||||||
<Textarea
|
|
||||||
label='آدرس کامل'
|
|
||||||
name='userAddress.address'
|
|
||||||
value={formik.values.userAddress.address}
|
|
||||||
onChange={onAddressFieldChange}
|
|
||||||
readOnly={isAddressReadOnly}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DefaulModal
|
|
||||||
open={isLocationModalOpen}
|
|
||||||
close={() => setIsLocationModalOpen(false)}
|
|
||||||
isHeader={true}
|
|
||||||
title_header='انتخاب موقعیت روی نقشه'
|
|
||||||
width={800}
|
|
||||||
>
|
|
||||||
<div className='p-4'>
|
|
||||||
<LocationPicker
|
|
||||||
initialPosition={
|
|
||||||
latitude && longitude
|
|
||||||
? [latitude, longitude]
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
onLocationSelect={handleMapLocationSelect}
|
|
||||||
/>
|
|
||||||
<div className='mt-4 text-xs text-gray-600 text-center'>
|
|
||||||
روی نقشه کلیک کنید تا موقعیت را انتخاب کنید
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DefaulModal>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isCarDelivery && (
|
|
||||||
<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'>
|
|
||||||
<Input
|
|
||||||
label='مدل خودرو'
|
|
||||||
name='carAddress.carModel'
|
|
||||||
value={formik.values.carAddress.carModel}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label='رنگ خودرو'
|
|
||||||
name='carAddress.carColor'
|
|
||||||
value={formik.values.carAddress.carColor}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label='پلاک'
|
|
||||||
name='carAddress.plateNumber'
|
|
||||||
value={formik.values.carAddress.plateNumber}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label='شماره تماس'
|
|
||||||
name='carAddress.phone'
|
|
||||||
value={formik.values.carAddress.phone}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='w-full bg-white rounded-4xl p-8'>
|
|
||||||
<div className='text-lg font-light'>توضیحات سفارش</div>
|
|
||||||
<div className='mt-6'>
|
|
||||||
<Textarea
|
|
||||||
label='توضیحات'
|
|
||||||
name='description'
|
|
||||||
placeholder='توضیحات اضافی برای سفارش...'
|
|
||||||
value={formik.values.description}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
isNotRequired
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CreateOrderDeliverySection
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { type FC, useMemo, useState } from 'react'
|
import { type FC, useMemo, useState } from 'react'
|
||||||
import type { FormikProps } from 'formik'
|
import type { FormikProps } from 'formik'
|
||||||
import { Add, Minus, Trash } from 'iconsax-react'
|
import { Add } from 'iconsax-react'
|
||||||
import Input from '@/components/Input'
|
import Input from '@/components/Input'
|
||||||
import type { Food } from '@/pages/food/types/Types'
|
import type { Food } from '@/pages/food/types/Types'
|
||||||
import { formatFaNumber, formatPrice } from '@/helpers/func'
|
import { formatPrice } from '@/helpers/func'
|
||||||
import { getFoodUnitPrice } from '../hooks/useCreateOrderForm'
|
import { getFoodUnitPrice } from '../hooks/useCreateOrderForm'
|
||||||
import type { CreateOrderFormValues } from '../hooks/useCreateOrderForm'
|
import type { CreateOrderFormValues } from '../hooks/useCreateOrderForm'
|
||||||
|
|
||||||
@@ -11,25 +11,16 @@ interface CreateOrderItemsSectionProps {
|
|||||||
formik: FormikProps<CreateOrderFormValues>
|
formik: FormikProps<CreateOrderFormValues>
|
||||||
foods: Food[]
|
foods: Food[]
|
||||||
onAddFood: (foodId: string) => void
|
onAddFood: (foodId: string) => void
|
||||||
onUpdateQuantity: (foodId: string, quantity: number) => void
|
|
||||||
onRemoveItem: (foodId: string) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateOrderItemsSection: FC<CreateOrderItemsSectionProps> = ({
|
const CreateOrderItemsSection: FC<CreateOrderItemsSectionProps> = ({
|
||||||
formik,
|
formik,
|
||||||
foods,
|
foods,
|
||||||
onAddFood,
|
onAddFood,
|
||||||
onUpdateQuantity,
|
|
||||||
onRemoveItem,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [selectedCategoryId, setSelectedCategoryId] = useState<string | null>(null)
|
const [selectedCategoryId, setSelectedCategoryId] = useState<string | null>(null)
|
||||||
|
|
||||||
const foodsMap = useMemo(
|
|
||||||
() => new Map(foods.map((food) => [food.id, food])),
|
|
||||||
[foods]
|
|
||||||
)
|
|
||||||
|
|
||||||
const categories = useMemo(() => {
|
const categories = useMemo(() => {
|
||||||
const categoryMap = new Map<string, string>()
|
const categoryMap = new Map<string, string>()
|
||||||
foods.forEach((food) => {
|
foods.forEach((food) => {
|
||||||
@@ -54,19 +45,11 @@ const CreateOrderItemsSection: FC<CreateOrderItemsSectionProps> = ({
|
|||||||
return result
|
return result
|
||||||
}, [foods, search, selectedCategoryId])
|
}, [foods, search, selectedCategoryId])
|
||||||
|
|
||||||
const selectedItems = formik.values.items
|
|
||||||
.map((item) => {
|
|
||||||
const food = foodsMap.get(item.foodId)
|
|
||||||
if (!food) return null
|
|
||||||
return { ...item, food }
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full bg-white rounded-4xl p-8'>
|
<div className='w-full h-full bg-white rounded-4xl p-5 flex flex-col min-h-0'>
|
||||||
<div className='text-lg font-light'>آیتمهای سفارش</div>
|
<div className='text-sm font-light flex-shrink-0'>انتخاب غذا</div>
|
||||||
|
|
||||||
<div className='mt-6 border border-border rounded-xl p-4'>
|
<div className='mt-3 flex-1 min-h-0 flex flex-col border border-border rounded-xl p-3'>
|
||||||
<Input
|
<Input
|
||||||
variant='search'
|
variant='search'
|
||||||
className='bg-[#EEF0F7]'
|
className='bg-[#EEF0F7]'
|
||||||
@@ -76,11 +59,11 @@ const CreateOrderItemsSection: FC<CreateOrderItemsSectionProps> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{categories.length > 0 && (
|
{categories.length > 0 && (
|
||||||
<div className='mt-3 flex gap-2 overflow-x-auto pb-1'>
|
<div className='mt-2 flex gap-1.5 overflow-x-auto flex-shrink-0 pb-0.5'>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
onClick={() => setSelectedCategoryId(null)}
|
onClick={() => setSelectedCategoryId(null)}
|
||||||
className={`flex-shrink-0 px-3 h-8 rounded-full text-xs transition-colors ${
|
className={`flex-shrink-0 px-2.5 h-7 rounded-full text-[11px] transition-colors ${
|
||||||
selectedCategoryId === null
|
selectedCategoryId === null
|
||||||
? 'bg-primary text-white'
|
? 'bg-primary text-white'
|
||||||
: 'bg-[#EEF0F7] text-description hover:bg-[#E4E7F0]'
|
: 'bg-[#EEF0F7] text-description hover:bg-[#E4E7F0]'
|
||||||
@@ -93,7 +76,7 @@ const CreateOrderItemsSection: FC<CreateOrderItemsSectionProps> = ({
|
|||||||
key={category.id}
|
key={category.id}
|
||||||
type='button'
|
type='button'
|
||||||
onClick={() => setSelectedCategoryId(category.id)}
|
onClick={() => setSelectedCategoryId(category.id)}
|
||||||
className={`flex-shrink-0 px-3 h-8 rounded-full text-xs transition-colors whitespace-nowrap ${
|
className={`flex-shrink-0 px-2.5 h-7 rounded-full text-[11px] transition-colors whitespace-nowrap ${
|
||||||
selectedCategoryId === category.id
|
selectedCategoryId === category.id
|
||||||
? 'bg-primary text-white'
|
? 'bg-primary text-white'
|
||||||
: 'bg-[#EEF0F7] text-description hover:bg-[#E4E7F0]'
|
: 'bg-[#EEF0F7] text-description hover:bg-[#E4E7F0]'
|
||||||
@@ -105,9 +88,9 @@ const CreateOrderItemsSection: FC<CreateOrderItemsSectionProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='mt-3 space-y-2 max-h-56 overflow-y-auto'>
|
<div className='mt-2 flex-1 min-h-0 overflow-y-auto space-y-1'>
|
||||||
{filteredFoods.length === 0 ? (
|
{filteredFoods.length === 0 ? (
|
||||||
<div className='text-center py-6 text-xs text-description'>
|
<div className='text-center py-8 text-xs text-description'>
|
||||||
غذایی یافت نشد
|
غذایی یافت نشد
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -115,37 +98,41 @@ const CreateOrderItemsSection: FC<CreateOrderItemsSectionProps> = ({
|
|||||||
const inCart = formik.values.items.some(
|
const inCart = formik.values.items.some(
|
||||||
(item) => item.foodId === food.id
|
(item) => item.foodId === food.id
|
||||||
)
|
)
|
||||||
|
const cartQty = formik.values.items.find(
|
||||||
|
(item) => item.foodId === food.id
|
||||||
|
)?.quantity
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={food.id}
|
key={food.id}
|
||||||
className='flex items-center gap-3 p-2 rounded-xl hover:bg-[#F8F9FC] transition-colors'
|
className='flex items-center gap-2 p-1.5 rounded-lg hover:bg-[#F8F9FC] transition-colors'
|
||||||
>
|
>
|
||||||
{food.images?.[0] ? (
|
{food.images?.[0] ? (
|
||||||
<img
|
<img
|
||||||
src={food.images[0]}
|
src={food.images[0]}
|
||||||
alt={food.title}
|
alt={food.title}
|
||||||
className='w-11 h-11 rounded-lg object-cover flex-shrink-0'
|
className='w-9 h-9 rounded-lg object-cover flex-shrink-0'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className='w-11 h-11 rounded-lg bg-gray-100 flex-shrink-0' />
|
<div className='w-9 h-9 rounded-lg bg-gray-100 flex-shrink-0' />
|
||||||
)}
|
)}
|
||||||
<div className='flex-1 min-w-0'>
|
<div className='flex-1 min-w-0'>
|
||||||
<div className='text-sm truncate'>{food.title}</div>
|
<div className='text-xs truncate'>{food.title}</div>
|
||||||
<div className='text-xs text-description mt-0.5'>
|
<div className='text-[11px] text-description'>
|
||||||
{formatPrice(getFoodUnitPrice(food))}
|
{formatPrice(getFoodUnitPrice(food))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
onClick={() => onAddFood(food.id)}
|
onClick={() => onAddFood(food.id)}
|
||||||
className={`flex items-center gap-1 px-3 h-8 rounded-lg text-xs transition-colors ${
|
className={`flex items-center gap-1 px-2.5 h-7 rounded-lg text-[11px] transition-colors flex-shrink-0 ${
|
||||||
inCart
|
inCart
|
||||||
? 'bg-primary/10 text-primary'
|
? 'bg-primary/10 text-primary'
|
||||||
: 'bg-primary text-white'
|
: 'bg-primary text-white'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Add size={14} color={inCart ? '#000' : '#fff'} />
|
<Add size={12} color={inCart ? '#000' : '#fff'} />
|
||||||
{inCart ? 'افزودن' : 'انتخاب'}
|
{inCart ? `×${cartQty}` : 'انتخاب'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -154,85 +141,9 @@ const CreateOrderItemsSection: FC<CreateOrderItemsSectionProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-6'>
|
{formik.touched.items && formik.errors.items && typeof formik.errors.items === 'string' && (
|
||||||
<div className='text-sm mb-3'>سبد سفارش</div>
|
<div className='text-xs text-red-500 mt-2 flex-shrink-0'>{formik.errors.items}</div>
|
||||||
|
)}
|
||||||
{selectedItems.length === 0 ? (
|
|
||||||
<div className='border border-dashed border-border rounded-xl py-10 text-center text-xs text-description'>
|
|
||||||
هنوز آیتمی انتخاب نشده است
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className='space-y-3'>
|
|
||||||
{selectedItems.map((item) => {
|
|
||||||
if (!item) return null
|
|
||||||
const unitPrice = getFoodUnitPrice(item.food)
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={item.foodId}
|
|
||||||
className='flex items-center gap-3 p-3 border border-border rounded-xl'
|
|
||||||
>
|
|
||||||
{item.food.images?.[0] ? (
|
|
||||||
<img
|
|
||||||
src={item.food.images[0]}
|
|
||||||
alt={item.food.title}
|
|
||||||
className='w-12 h-12 rounded-lg object-cover flex-shrink-0'
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className='w-12 h-12 rounded-lg bg-gray-100 flex-shrink-0' />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='flex-1 min-w-0'>
|
|
||||||
<div className='text-sm truncate'>{item.food.title}</div>
|
|
||||||
<div className='text-xs text-description mt-1'>
|
|
||||||
{formatPrice(unitPrice)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
onClick={() =>
|
|
||||||
onUpdateQuantity(item.foodId, item.quantity - 1)
|
|
||||||
}
|
|
||||||
className='w-8 h-8 rounded-lg border border-border flex items-center justify-center'
|
|
||||||
>
|
|
||||||
<Minus size={14} color='#000' />
|
|
||||||
</button>
|
|
||||||
<span className='w-6 text-center text-sm'>
|
|
||||||
{formatFaNumber(item.quantity)}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
onClick={() =>
|
|
||||||
onUpdateQuantity(item.foodId, item.quantity + 1)
|
|
||||||
}
|
|
||||||
className='w-8 h-8 rounded-lg border border-border flex items-center justify-center'
|
|
||||||
>
|
|
||||||
<Add size={14} color='#000' />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='text-sm font-medium min-w-[90px] text-left'>
|
|
||||||
{formatPrice(unitPrice * item.quantity)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
onClick={() => onRemoveItem(item.foodId)}
|
|
||||||
className='text-red-500 p-1'
|
|
||||||
>
|
|
||||||
<Trash size={18} color='#EF4444' />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{formik.touched.items && formik.errors.items && typeof formik.errors.items === 'string' && (
|
|
||||||
<div className='text-xs text-red-500 mt-2'>{formik.errors.items}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,131 +1,361 @@
|
|||||||
import { formatFaNumber, formatPrice } from "@/helpers/func";
|
import { formatFaNumber, formatPrice } from '@/helpers/func'
|
||||||
import { PaymentMethodEnum } from "@/pages/paymentMethods/enum/Enum";
|
import { PaymentMethodEnum } from '@/pages/paymentMethods/enum/Enum'
|
||||||
import { DeliveryMethodEnum } from "@/pages/shipmentMethod/enum/Enum";
|
import { DeliveryMethodEnum } from '@/pages/shipmentMethod/enum/Enum'
|
||||||
import type { FormikProps } from "formik";
|
import type { FormikProps } from 'formik'
|
||||||
import { ShoppingCart, User } from "iconsax-react";
|
import {
|
||||||
import { type FC } from "react";
|
Add,
|
||||||
import type { CreateOrderFormValues, RestaurantPaymentMethod, RestaurantShipmentMethod } from "../hooks/useCreateOrderForm";
|
ArrowLeft2,
|
||||||
|
Car,
|
||||||
|
Location,
|
||||||
|
Minus,
|
||||||
|
Moneys,
|
||||||
|
ShoppingCart,
|
||||||
|
Trash,
|
||||||
|
Truck,
|
||||||
|
} from 'iconsax-react'
|
||||||
|
import { type FC, useState } from 'react'
|
||||||
|
import type { CreateOrderFormValues, RestaurantPaymentMethod, RestaurantShipmentMethod } from '../hooks/useCreateOrderForm'
|
||||||
|
import type { UserSavedAddress } from '@/pages/customers/types/Types'
|
||||||
|
import CreateOrderDeliveryModal from './CreateOrderDeliveryModal'
|
||||||
|
|
||||||
interface OrderSummaryData {
|
interface OrderSummaryData {
|
||||||
lineItems: Array<{
|
lineItems: Array<{
|
||||||
foodId: string;
|
foodId: string
|
||||||
title: string;
|
title: string
|
||||||
image: string;
|
image: string
|
||||||
quantity: number;
|
quantity: number
|
||||||
unitPrice: number;
|
unitPrice: number
|
||||||
totalPrice: number;
|
totalPrice: number
|
||||||
} | null>;
|
} | null>
|
||||||
subTotal: number;
|
subTotal: number
|
||||||
deliveryFee: number;
|
deliveryFee: number
|
||||||
total: number;
|
total: number
|
||||||
totalItems: number;
|
totalItems: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateOrderSidebarProps {
|
interface CreateOrderSidebarProps {
|
||||||
formik: FormikProps<CreateOrderFormValues>;
|
formik: FormikProps<CreateOrderFormValues>
|
||||||
orderSummary: OrderSummaryData;
|
orderSummary: OrderSummaryData
|
||||||
selectedDeliveryMethod?: RestaurantShipmentMethod;
|
deliveryMethods: RestaurantShipmentMethod[]
|
||||||
selectedPaymentMethod?: RestaurantPaymentMethod;
|
cashPaymentMethod?: RestaurantPaymentMethod
|
||||||
|
selectedDeliveryMethod?: RestaurantShipmentMethod
|
||||||
|
selectedPaymentMethod?: RestaurantPaymentMethod
|
||||||
|
isCourierDelivery: boolean
|
||||||
|
isCarDelivery: boolean
|
||||||
|
isDineIn: boolean
|
||||||
|
customerAddresses: UserSavedAddress[]
|
||||||
|
selectedAddressId: string | null
|
||||||
|
onSelectSavedAddress: (address: UserSavedAddress) => void
|
||||||
|
onSelectManualAddress: () => void
|
||||||
|
onAddressFieldChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void
|
||||||
|
onLocationSelect: (lat: number, lng: number) => void
|
||||||
|
onUpdateQuantity: (foodId: string, quantity: number) => void
|
||||||
|
onRemoveItem: (foodId: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDeliveryMethodText = (method?: string): string => {
|
const getDeliveryMethodText = (method?: string): string => {
|
||||||
const methodMap: Record<string, string> = {
|
const methodMap: Record<string, string> = {
|
||||||
[DeliveryMethodEnum.DineIn]: "سرو در رستوران",
|
[DeliveryMethodEnum.DineIn]: 'سرو در رستوران',
|
||||||
[DeliveryMethodEnum.CustomerPickup]: "دریافت از محل",
|
[DeliveryMethodEnum.CustomerPickup]: 'دریافت از محل',
|
||||||
[DeliveryMethodEnum.DeliveryCar]: "تحویل به خودرو",
|
[DeliveryMethodEnum.DeliveryCar]: 'تحویل به خودرو',
|
||||||
[DeliveryMethodEnum.DeliveryCourier]: "بیرونبر",
|
[DeliveryMethodEnum.DeliveryCourier]: 'بیرونبر',
|
||||||
};
|
}
|
||||||
return method ? methodMap[method] || method : "-";
|
return method ? methodMap[method] || method : 'انتخاب نشده'
|
||||||
};
|
}
|
||||||
|
|
||||||
const getPaymentMethodText = (method?: string): string => {
|
const getPaymentMethodText = (method?: string): string => {
|
||||||
const methodMap: Record<string, string> = {
|
const methodMap: Record<string, string> = {
|
||||||
[PaymentMethodEnum.Cash]: "پرداخت نقدی",
|
[PaymentMethodEnum.Cash]: 'پرداخت نقدی',
|
||||||
[PaymentMethodEnum.Online]: "پرداخت آنلاین",
|
[PaymentMethodEnum.Online]: 'پرداخت آنلاین',
|
||||||
[PaymentMethodEnum.CreditCard]: "کارت به کارت",
|
[PaymentMethodEnum.CreditCard]: 'کارت به کارت',
|
||||||
[PaymentMethodEnum.Wallet]: "کیف پول",
|
[PaymentMethodEnum.Wallet]: 'کیف پول',
|
||||||
};
|
}
|
||||||
return method ? methodMap[method] || method : "-";
|
return method ? methodMap[method] || method : 'انتخاب نشده'
|
||||||
};
|
}
|
||||||
|
|
||||||
const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({ formik, orderSummary, selectedDeliveryMethod, selectedPaymentMethod }) => {
|
const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
||||||
const customerName = [formik.values.firstName, formik.values.lastName].filter(Boolean).join(" ");
|
formik,
|
||||||
|
orderSummary,
|
||||||
|
deliveryMethods,
|
||||||
|
cashPaymentMethod,
|
||||||
|
selectedDeliveryMethod,
|
||||||
|
selectedPaymentMethod,
|
||||||
|
isCourierDelivery,
|
||||||
|
isCarDelivery,
|
||||||
|
isDineIn,
|
||||||
|
customerAddresses,
|
||||||
|
selectedAddressId,
|
||||||
|
onSelectSavedAddress,
|
||||||
|
onSelectManualAddress,
|
||||||
|
onAddressFieldChange,
|
||||||
|
onLocationSelect,
|
||||||
|
onUpdateQuantity,
|
||||||
|
onRemoveItem,
|
||||||
|
}) => {
|
||||||
|
const [isDeliveryModalOpen, setIsDeliveryModalOpen] = useState(false)
|
||||||
|
|
||||||
return (
|
const customerName = [formik.values.firstName, formik.values.lastName].filter(Boolean).join(' ')
|
||||||
<div className="space-y-6">
|
const deliveryText = getDeliveryMethodText(selectedDeliveryMethod?.method)
|
||||||
<div className="bg-white rounded-4xl p-8">
|
const paymentText = getPaymentMethodText(selectedPaymentMethod?.method)
|
||||||
<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">
|
const deliveryFeeText =
|
||||||
<div className="flex justify-between items-center border-b border-border border-dashed pb-3">
|
selectedDeliveryMethod && selectedDeliveryMethod.deliveryFee > 0
|
||||||
<span className="text-description">مشتری</span>
|
? formatPrice(selectedDeliveryMethod.deliveryFee)
|
||||||
<span>{customerName || "-"}</span>
|
: null
|
||||||
</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">
|
const getDeliveryExtraInfo = (): { text: string; warning?: boolean } | null => {
|
||||||
<div className="flex items-center gap-2 text-lg font-light">
|
if (isCourierDelivery) {
|
||||||
<ShoppingCart size={20} color="#000" />
|
const address = formik.values.userAddress.address?.trim()
|
||||||
آیتمها
|
if (!address) return { text: 'آدرس تحویل ثبت نشده', warning: true }
|
||||||
{orderSummary.totalItems > 0 && <span className="text-xs text-description">({formatFaNumber(orderSummary.totalItems)})</span>}
|
return { text: address }
|
||||||
</div>
|
}
|
||||||
|
if (isCarDelivery) {
|
||||||
|
const { carModel, plateNumber } = formik.values.carAddress
|
||||||
|
if (!carModel && !plateNumber) return { text: 'اطلاعات خودرو وارد نشده', warning: true }
|
||||||
|
return { text: [carModel, plateNumber].filter(Boolean).join(' · ') }
|
||||||
|
}
|
||||||
|
if (isDineIn && formik.values.tableNumber) {
|
||||||
|
return { text: `میز ${formik.values.tableNumber}` }
|
||||||
|
}
|
||||||
|
if (formik.values.description?.trim()) {
|
||||||
|
return { text: formik.values.description }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
{orderSummary.lineItems.length === 0 ? (
|
const extraInfo = getDeliveryExtraInfo()
|
||||||
<div className="mt-6 text-center text-xs text-description py-6">سبد خالی است</div>
|
const isDeliverySelected = Boolean(selectedDeliveryMethod)
|
||||||
) : (
|
const isPaymentSelected = Boolean(selectedPaymentMethod)
|
||||||
<div className="mt-6 space-y-3">
|
|
||||||
{orderSummary.lineItems.map((item) => {
|
return (
|
||||||
if (!item) return null;
|
<>
|
||||||
return (
|
<div className='bg-white rounded-4xl p-5 flex-1 min-h-0 flex flex-col h-full'>
|
||||||
<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 items-center justify-between flex-shrink-0'>
|
||||||
<div className="flex-1">
|
<div className='flex items-center gap-2 text-sm font-light'>
|
||||||
<div>{item.title}</div>
|
<ShoppingCart size={18} color='#000' />
|
||||||
<div className="text-description mt-1">
|
خلاصه سفارش
|
||||||
{formatFaNumber(item.quantity)} × {formatPrice(item.unitPrice)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{orderSummary.totalItems > 0 && (
|
||||||
<div>{formatPrice(item.totalPrice)}</div>
|
<span className='text-[11px] text-description bg-[#F8F9FC] rounded-full px-2.5 py-0.5'>
|
||||||
|
{formatFaNumber(orderSummary.totalItems)} آیتم
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-6 space-y-3 text-[13px] font-light">
|
{(customerName || formik.values.userPhone) && (
|
||||||
<div className="flex justify-between items-center">
|
<div className='mt-3 p-3 bg-[#F8F9FC] rounded-xl flex-shrink-0'>
|
||||||
<span className="text-description">جمع آیتمها</span>
|
<div className='text-xs font-medium truncate'>
|
||||||
<span>{formatPrice(orderSummary.subTotal)}</span>
|
{customerName || 'مشتری'}
|
||||||
</div>
|
</div>
|
||||||
{orderSummary.deliveryFee > 0 && (
|
{formik.values.userPhone && (
|
||||||
<div className="flex justify-between items-center">
|
<div className='text-[11px] text-description mt-0.5'>
|
||||||
<span className="text-description">هزینه ارسال</span>
|
{formik.values.userPhone}
|
||||||
<span>{formatPrice(orderSummary.deliveryFee)}</span>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => setIsDeliveryModalOpen(true)}
|
||||||
|
className={`mt-3 w-full text-right p-3 rounded-xl border transition-colors flex-shrink-0 ${
|
||||||
|
isDeliverySelected && isPaymentSelected
|
||||||
|
? 'border-border hover:border-primary/40 hover:bg-[#F8F9FC]'
|
||||||
|
: 'border-dashed border-primary/50 bg-primary/5 hover:bg-primary/10'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className='flex items-center justify-between gap-2'>
|
||||||
|
<span className='text-xs font-medium'>تحویل و پرداخت</span>
|
||||||
|
<span className='text-[11px] text-primary flex items-center gap-1 flex-shrink-0'>
|
||||||
|
{isDeliverySelected && isPaymentSelected ? 'ویرایش' : 'انتخاب'}
|
||||||
|
<ArrowLeft2 size={12} color='#000' />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isDeliverySelected || !isPaymentSelected ? (
|
||||||
|
<p className='mt-1.5 text-[11px] text-description'>
|
||||||
|
برای تعیین روش تحویل و پرداخت کلیک کنید
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className='mt-2 space-y-2'>
|
||||||
|
<div className='flex items-center gap-2 text-xs'>
|
||||||
|
<Truck size={15} color='#8C90A3' className='flex-shrink-0' />
|
||||||
|
<span
|
||||||
|
className={`flex-1 truncate ${
|
||||||
|
!isDeliverySelected ? 'text-description italic' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{deliveryText}
|
||||||
|
</span>
|
||||||
|
{deliveryFeeText && (
|
||||||
|
<span className='text-description text-[11px] flex-shrink-0'>
|
||||||
|
{deliveryFeeText}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-2 text-xs'>
|
||||||
|
<Moneys size={15} color='#8C90A3' className='flex-shrink-0' />
|
||||||
|
<span
|
||||||
|
className={`truncate ${
|
||||||
|
!isPaymentSelected ? 'text-description italic' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{paymentText}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{extraInfo && (
|
||||||
|
<div
|
||||||
|
className={`flex items-start gap-2 text-[11px] pt-1 border-t border-border border-dashed ${
|
||||||
|
extraInfo.warning ? 'text-red-500' : 'text-description'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isCourierDelivery ? (
|
||||||
|
<Location size={14} className='flex-shrink-0 mt-0.5' color={extraInfo.warning ? '#EF4444' : '#8C90A3'} />
|
||||||
|
) : isCarDelivery ? (
|
||||||
|
<Car size={14} className='flex-shrink-0 mt-0.5' color={extraInfo.warning ? '#EF4444' : '#8C90A3'} />
|
||||||
|
) : null}
|
||||||
|
<span className='line-clamp-2'>{extraInfo.text}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className='mt-4 pt-3 border-t border-border border-dashed flex-1 min-h-0 flex flex-col'>
|
||||||
|
<div className='text-xs text-description mb-2 flex-shrink-0'>سبد سفارش</div>
|
||||||
|
|
||||||
|
<div className='flex-1 min-h-0 overflow-y-auto relative'>
|
||||||
|
{orderSummary.lineItems.length === 0 ? (
|
||||||
|
<div className='h-full flex flex-col items-center justify-center text-center py-6'>
|
||||||
|
<ShoppingCart size={32} color='#D0D0D0' />
|
||||||
|
<div className='text-xs text-description mt-2'>
|
||||||
|
هنوز آیتمی انتخاب نشده
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='space-y-2.5 pb-2'>
|
||||||
|
{orderSummary.lineItems.map((item) => {
|
||||||
|
if (!item) return null
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.foodId}
|
||||||
|
className='rounded-xl bg-[#F8F9FC] overflow-hidden'
|
||||||
|
>
|
||||||
|
<div className='flex items-start gap-2.5 p-2.5'>
|
||||||
|
{item.image ? (
|
||||||
|
<img
|
||||||
|
src={item.image}
|
||||||
|
alt={item.title}
|
||||||
|
className='w-11 h-11 rounded-lg object-cover flex-shrink-0'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className='w-11 h-11 rounded-lg bg-gray-200 flex-shrink-0' />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='flex-1 min-w-0'>
|
||||||
|
<div className='flex items-start gap-1.5'>
|
||||||
|
<div className='flex-1 min-w-0 text-xs font-medium leading-5 line-clamp-2'>
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => onRemoveItem(item.foodId)}
|
||||||
|
className='flex-shrink-0 w-7 h-7 rounded-lg flex items-center justify-center hover:bg-red-50 transition-colors'
|
||||||
|
aria-label='حذف آیتم'
|
||||||
|
>
|
||||||
|
<Trash size={15} color='#EF4444' />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='mt-1 text-[11px] text-description tabular-nums'>
|
||||||
|
{item.quantity > 1
|
||||||
|
? `${formatFaNumber(item.quantity)} × ${formatPrice(item.unitPrice)}`
|
||||||
|
: formatPrice(item.unitPrice)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between gap-3 px-2.5 py-2 border-t border-[#ECEEF5] bg-white/60'>
|
||||||
|
<div className='flex items-center gap-0.5 rounded-lg bg-white border border-border p-0.5 flex-shrink-0'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() =>
|
||||||
|
onUpdateQuantity(
|
||||||
|
item.foodId,
|
||||||
|
item.quantity - 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className='w-7 h-7 rounded-md flex items-center justify-center hover:bg-[#F8F9FC] transition-colors'
|
||||||
|
>
|
||||||
|
<Minus size={13} color='#000' />
|
||||||
|
</button>
|
||||||
|
<span className='w-6 text-center text-xs tabular-nums'>
|
||||||
|
{formatFaNumber(item.quantity)}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() =>
|
||||||
|
onUpdateQuantity(
|
||||||
|
item.foodId,
|
||||||
|
item.quantity + 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className='w-7 h-7 rounded-md flex items-center justify-center hover:bg-[#F8F9FC] transition-colors'
|
||||||
|
>
|
||||||
|
<Add size={13} color='#000' />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className='text-xs font-semibold whitespace-nowrap tabular-nums flex-shrink-0'>
|
||||||
|
{formatPrice(item.totalPrice)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{orderSummary.lineItems.length > 3 && (
|
||||||
|
<div className='pointer-events-none absolute bottom-0 left-0 right-0 h-8 bg-gradient-to-t from-white to-transparent' />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-3 space-y-2 text-xs pt-3 border-t border-border flex-shrink-0'>
|
||||||
|
<div className='flex justify-between'>
|
||||||
|
<span className='text-description'>جمع آیتمها</span>
|
||||||
|
<span>{formatPrice(orderSummary.subTotal)}</span>
|
||||||
|
</div>
|
||||||
|
{orderSummary.deliveryFee > 0 && (
|
||||||
|
<div className='flex justify-between'>
|
||||||
|
<span className='text-description'>هزینه ارسال</span>
|
||||||
|
<span>{formatPrice(orderSummary.deliveryFee)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className='flex justify-between font-bold text-sm pt-2 border-t-2 border-gray-200'>
|
||||||
|
<span>جمع کل</span>
|
||||||
|
<span>{formatPrice(orderSummary.total)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</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;
|
<CreateOrderDeliveryModal
|
||||||
|
open={isDeliveryModalOpen}
|
||||||
|
onClose={() => setIsDeliveryModalOpen(false)}
|
||||||
|
formik={formik}
|
||||||
|
deliveryMethods={deliveryMethods}
|
||||||
|
cashPaymentMethod={cashPaymentMethod}
|
||||||
|
isCourierDelivery={isCourierDelivery}
|
||||||
|
isCarDelivery={isCarDelivery}
|
||||||
|
isDineIn={isDineIn}
|
||||||
|
customerAddresses={customerAddresses}
|
||||||
|
selectedAddressId={selectedAddressId}
|
||||||
|
onSelectSavedAddress={onSelectSavedAddress}
|
||||||
|
onSelectManualAddress={onSelectManualAddress}
|
||||||
|
onAddressFieldChange={onAddressFieldChange}
|
||||||
|
onLocationSelect={onLocationSelect}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateOrderSidebar
|
||||||
|
|||||||
Reference in New Issue
Block a user