422 lines
22 KiB
TypeScript
422 lines
22 KiB
TypeScript
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 {
|
||
Add,
|
||
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 {
|
||
lineItems: Array<{
|
||
foodId: string
|
||
title: string
|
||
image: string
|
||
quantity: number
|
||
unitPrice: number
|
||
totalPrice: number
|
||
} | null>
|
||
subTotal: number
|
||
deliveryFee: number
|
||
packingFee: number
|
||
discountAmount: number
|
||
total: number
|
||
totalItems: number
|
||
}
|
||
|
||
interface CreateOrderSidebarProps {
|
||
formik: FormikProps<CreateOrderFormValues>
|
||
orderSummary: OrderSummaryData
|
||
deliveryMethods: RestaurantShipmentMethod[]
|
||
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 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 CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
||
formik,
|
||
orderSummary,
|
||
deliveryMethods,
|
||
cashPaymentMethod,
|
||
selectedDeliveryMethod,
|
||
selectedPaymentMethod,
|
||
isCourierDelivery,
|
||
isCarDelivery,
|
||
isDineIn,
|
||
customerAddresses,
|
||
selectedAddressId,
|
||
onSelectSavedAddress,
|
||
onSelectManualAddress,
|
||
onAddressFieldChange,
|
||
onLocationSelect,
|
||
onUpdateQuantity,
|
||
onRemoveItem,
|
||
}) => {
|
||
const [isDeliveryModalOpen, setIsDeliveryModalOpen] = useState(false)
|
||
|
||
const customerName = [formik.values.firstName, formik.values.lastName].filter(Boolean).join(' ')
|
||
const deliveryText = getDeliveryMethodText(selectedDeliveryMethod?.method)
|
||
const paymentText = getPaymentMethodText(selectedPaymentMethod?.method)
|
||
|
||
const deliveryFeeText =
|
||
selectedDeliveryMethod && selectedDeliveryMethod.deliveryFee > 0
|
||
? formatPrice(selectedDeliveryMethod.deliveryFee)
|
||
: null
|
||
|
||
const getDeliveryExtraInfo = (): { text: string; warning?: boolean } | null => {
|
||
if (isCourierDelivery) {
|
||
const address = formik.values.userAddress.address?.trim()
|
||
if (!address) return { text: 'آدرس تحویل ثبت نشده', warning: true }
|
||
return { text: address }
|
||
}
|
||
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
|
||
}
|
||
|
||
const extraInfo = getDeliveryExtraInfo()
|
||
const isDeliverySelected = Boolean(selectedDeliveryMethod)
|
||
const isPaymentSelected = Boolean(selectedPaymentMethod)
|
||
|
||
return (
|
||
<>
|
||
<div className='bg-white rounded-4xl p-4 flex-1 min-h-0 flex flex-col h-full'>
|
||
<div className='flex items-center justify-between flex-shrink-0'>
|
||
<div className='flex items-center gap-2 text-sm font-light'>
|
||
<ShoppingCart size={18} color='#000' />
|
||
خلاصه سفارش
|
||
</div>
|
||
{orderSummary.totalItems > 0 && (
|
||
<span className='text-[11px] text-description bg-[#F8F9FC] rounded-full px-2.5 py-0.5'>
|
||
{formatFaNumber(orderSummary.totalItems)} آیتم
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
{(customerName || formik.values.userPhone) && (
|
||
<div className='mt-2 p-2.5 bg-[#F8F9FC] rounded-xl flex-shrink-0'>
|
||
<div className='text-[11px] font-medium truncate'>
|
||
{customerName || 'مشتری'}
|
||
</div>
|
||
{formik.values.userPhone && (
|
||
<div className='text-[10px] text-description mt-0.5'>
|
||
{formik.values.userPhone}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
<button
|
||
type='button'
|
||
onClick={() => setIsDeliveryModalOpen(true)}
|
||
className={`mt-2 w-full text-right p-2.5 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-1.5 space-y-1.5'>
|
||
<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-3 pt-2.5 border-t border-border border-dashed flex-1 min-h-0 flex flex-col'>
|
||
<div className='text-xs text-description mb-1.5 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 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-2 space-y-2 text-xs pt-2.5 border-t border-border flex-shrink-0'>
|
||
<div className='grid grid-cols-2 gap-2.5 mb-2'>
|
||
<div>
|
||
<div className='text-xs mb-1.5'>
|
||
<span className='font-medium'>هزینه بستهبندی</span>
|
||
<span className='text-description mr-1'>(اختیاری)</span>
|
||
</div>
|
||
<input
|
||
name='packingFee'
|
||
type='number'
|
||
inputMode='numeric'
|
||
min={0}
|
||
placeholder='تومان'
|
||
className='w-full h-10 rounded-xl border border-border px-3 text-sm outline-none focus:border-primary'
|
||
value={formik.values.packingFee || ''}
|
||
onChange={(e) =>
|
||
formik.setFieldValue(
|
||
'packingFee',
|
||
Math.max(Number(e.target.value) || 0, 0)
|
||
)
|
||
}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<div className='text-xs mb-1.5'>
|
||
<span className='font-medium'>تخفیف مبلغی</span>
|
||
<span className='text-description mr-1'>(اختیاری)</span>
|
||
</div>
|
||
<input
|
||
name='discountAmount'
|
||
type='number'
|
||
inputMode='numeric'
|
||
min={0}
|
||
placeholder='تومان'
|
||
className='w-full h-10 rounded-xl border border-border px-3 text-sm outline-none focus:border-primary'
|
||
value={formik.values.discountAmount || ''}
|
||
onChange={(e) =>
|
||
formik.setFieldValue(
|
||
'discountAmount',
|
||
Math.max(Number(e.target.value) || 0, 0)
|
||
)
|
||
}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
)}
|
||
{orderSummary.packingFee > 0 && (
|
||
<div className='flex justify-between'>
|
||
<span className='text-description'>هزینه بستهبندی</span>
|
||
<span>{formatPrice(orderSummary.packingFee)}</span>
|
||
</div>
|
||
)}
|
||
{orderSummary.discountAmount > 0 && (
|
||
<div className='flex justify-between'>
|
||
<span className='text-description'>تخفیف</span>
|
||
<span className='text-green-600'>
|
||
-{formatPrice(orderSummary.discountAmount)}
|
||
</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>
|
||
|
||
<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
|