base create order
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import { type FC, useMemo, useState } from 'react'
|
||||
import type { FormikProps } from 'formik'
|
||||
import { Add, Minus, Trash } from 'iconsax-react'
|
||||
import Input from '@/components/Input'
|
||||
import type { Food } from '@/pages/food/types/Types'
|
||||
import { formatFaNumber, formatPrice } from '@/helpers/func'
|
||||
import { getFoodUnitPrice } from '../hooks/useCreateOrderForm'
|
||||
import type { CreateOrderFormValues } from '../hooks/useCreateOrderForm'
|
||||
|
||||
interface CreateOrderItemsSectionProps {
|
||||
formik: FormikProps<CreateOrderFormValues>
|
||||
foods: Food[]
|
||||
onAddFood: (foodId: string) => void
|
||||
onUpdateQuantity: (foodId: string, quantity: number) => void
|
||||
onRemoveItem: (foodId: string) => void
|
||||
}
|
||||
|
||||
const CreateOrderItemsSection: FC<CreateOrderItemsSectionProps> = ({
|
||||
formik,
|
||||
foods,
|
||||
onAddFood,
|
||||
onUpdateQuantity,
|
||||
onRemoveItem,
|
||||
}) => {
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const foodsMap = useMemo(
|
||||
() => new Map(foods.map((food) => [food.id, food])),
|
||||
[foods]
|
||||
)
|
||||
|
||||
const filteredFoods = useMemo(() => {
|
||||
const query = search.trim().toLowerCase()
|
||||
if (!query) return foods
|
||||
return foods.filter((food) => food.title.toLowerCase().includes(query))
|
||||
}, [foods, search])
|
||||
|
||||
const selectedItems = formik.values.items
|
||||
.map((item) => {
|
||||
const food = foodsMap.get(item.foodId)
|
||||
if (!food) return null
|
||||
return { ...item, food }
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
return (
|
||||
<div className='w-full bg-white rounded-4xl p-8'>
|
||||
<div className='text-lg font-light'>آیتمهای سفارش</div>
|
||||
|
||||
<div className='mt-6 border border-border rounded-xl p-4'>
|
||||
<Input
|
||||
variant='search'
|
||||
className='bg-[#EEF0F7]'
|
||||
placeholder='جستجوی غذا...'
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className='mt-3 space-y-2 max-h-56 overflow-y-auto'>
|
||||
{filteredFoods.length === 0 ? (
|
||||
<div className='text-center py-6 text-xs text-description'>
|
||||
غذایی یافت نشد
|
||||
</div>
|
||||
) : (
|
||||
filteredFoods.map((food) => {
|
||||
const inCart = formik.values.items.some(
|
||||
(item) => item.foodId === food.id
|
||||
)
|
||||
return (
|
||||
<div
|
||||
key={food.id}
|
||||
className='flex items-center gap-3 p-2 rounded-xl hover:bg-[#F8F9FC] transition-colors'
|
||||
>
|
||||
{food.images?.[0] ? (
|
||||
<img
|
||||
src={food.images[0]}
|
||||
alt={food.title}
|
||||
className='w-11 h-11 rounded-lg object-cover flex-shrink-0'
|
||||
/>
|
||||
) : (
|
||||
<div className='w-11 h-11 rounded-lg bg-gray-100 flex-shrink-0' />
|
||||
)}
|
||||
<div className='flex-1 min-w-0'>
|
||||
<div className='text-sm truncate'>{food.title}</div>
|
||||
<div className='text-xs text-description mt-0.5'>
|
||||
{formatPrice(getFoodUnitPrice(food))}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => onAddFood(food.id)}
|
||||
className={`flex items-center gap-1 px-3 h-8 rounded-lg text-xs transition-colors ${
|
||||
inCart
|
||||
? 'bg-primary/10 text-primary'
|
||||
: 'bg-primary text-white'
|
||||
}`}
|
||||
>
|
||||
<Add size={14} color={inCart ? '#000' : '#fff'} />
|
||||
{inCart ? 'افزودن' : 'انتخاب'}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<div className='text-sm mb-3'>سبد سفارش</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>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateOrderItemsSection
|
||||
Reference in New Issue
Block a user