create order without scoll
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-07-01 10:10:15 +03:30
parent 0539d4bc48
commit 3c6d3f009a
6 changed files with 824 additions and 561 deletions
+341 -111
View File
@@ -1,131 +1,361 @@
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";
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;
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
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 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,
deliveryMethods,
cashPaymentMethod,
selectedDeliveryMethod,
selectedPaymentMethod,
isCourierDelivery,
isCarDelivery,
isDineIn,
customerAddresses,
selectedAddressId,
onSelectSavedAddress,
onSelectManualAddress,
onAddressFieldChange,
onLocationSelect,
onUpdateQuantity,
onRemoveItem,
}) => {
const [isDeliveryModalOpen, setIsDeliveryModalOpen] = useState(false)
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>
const customerName = [formik.values.firstName, formik.values.lastName].filter(Boolean).join(' ')
const deliveryText = getDeliveryMethodText(selectedDeliveryMethod?.method)
const paymentText = getPaymentMethodText(selectedPaymentMethod?.method)
<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>
const deliveryFeeText =
selectedDeliveryMethod && selectedDeliveryMethod.deliveryFee > 0
? formatPrice(selectedDeliveryMethod.deliveryFee)
: null
<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>
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
}
{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)}
const extraInfo = getDeliveryExtraInfo()
const isDeliverySelected = Boolean(selectedDeliveryMethod)
const isPaymentSelected = Boolean(selectedPaymentMethod)
return (
<>
<div className='bg-white rounded-4xl p-5 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>
</div>
<div>{formatPrice(item.totalPrice)}</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>
);
})}
</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>
{(customerName || formik.values.userPhone) && (
<div className='mt-3 p-3 bg-[#F8F9FC] rounded-xl flex-shrink-0'>
<div className='text-xs font-medium truncate'>
{customerName || 'مشتری'}
</div>
{formik.values.userPhone && (
<div className='text-[11px] text-description mt-0.5'>
{formik.values.userPhone}
</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 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