Files
dmenu-admin/src/pages/orders/components/CreateOrderSidebar.tsx
T
2026-07-01 12:28:51 +03:30

422 lines
22 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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