387 lines
20 KiB
TypeScript
387 lines
20 KiB
TypeScript
import { type FC, useState } from 'react'
|
||
import { useFormik } from 'formik'
|
||
import * as Yup from 'yup'
|
||
import { TickCircle } from 'iconsax-react'
|
||
import { toast } from 'react-toastify'
|
||
import Input from '@/components/Input'
|
||
import Select from '@/components/Select'
|
||
import Textarea from '@/components/Textarea'
|
||
import Button from '@/components/Button'
|
||
import { Checkbox } from '@/components/ui/checkbox'
|
||
import CreateFoodSidebar from './components/CreateFoodSidebar'
|
||
import type { CreateFoodType } from './types/Types'
|
||
import { useCreateFood, useGetCategories } from './hooks/useFoodData'
|
||
import { useMultipleUpload } from '../uploader/hooks/useUploaderData'
|
||
import { Pages } from '@/config/Pages'
|
||
import type { ErrorType } from '@/helpers/types'
|
||
|
||
|
||
|
||
const CreateFood: FC = () => {
|
||
|
||
const { data } = useGetCategories();
|
||
const [isActive, setIsActive] = useState<boolean>(true)
|
||
const [isSpecial, setIsSpecial] = useState<boolean>(false)
|
||
const [imageFiles, setImageFiles] = useState<File[]>([])
|
||
const { mutate: createFood, isPending } = useCreateFood()
|
||
const { mutate: multipleUpload, isPending: isUploading } = useMultipleUpload()
|
||
|
||
const categories = data?.data?.map(category => ({
|
||
label: category.title,
|
||
value: category.id
|
||
})) || []
|
||
|
||
const formik = useFormik<CreateFoodType>({
|
||
initialValues: {
|
||
title: '',
|
||
desc: '',
|
||
content: [],
|
||
categoryId: '',
|
||
price: 0,
|
||
discount: 0,
|
||
points: 0,
|
||
prepareTime: 0,
|
||
stock: 0,
|
||
stockDefault: 0,
|
||
breakfast: false,
|
||
noon: false,
|
||
dinner: false,
|
||
mon: false,
|
||
tue: false,
|
||
wed: false,
|
||
thu: false,
|
||
fri: false,
|
||
sat: false,
|
||
sun: false,
|
||
pickupServe: false,
|
||
inPlaceServe: false,
|
||
isActive: true,
|
||
images: []
|
||
},
|
||
validationSchema: Yup.object().shape({
|
||
title: Yup.string().required('نام غذا الزامی است'),
|
||
desc: Yup.string().required('توضیحات غذا الزامی است'),
|
||
categoryId: Yup.string().required('دستهبندی الزامی است'),
|
||
price: Yup.number().required('قیمت الزامی است').min(0, 'قیمت باید مثبت باشد'),
|
||
prepareTime: Yup.number().required('زمان آمادهسازی الزامی است').min(0, 'زمان آمادهسازی باید مثبت باشد'),
|
||
stock: Yup.number().required('موجودی الزامی است').min(0, 'موجودی باید مثبت باشد'),
|
||
stockDefault: Yup.number().required('موجودی پیشفرض الزامی است').min(0, 'موجودی پیشفرض باید مثبت باشد'),
|
||
points: Yup.number().min(0, 'امتیاز باید مثبت باشد'),
|
||
discount: Yup.number().min(0, 'تخفیف باید مثبت باشد'),
|
||
content: Yup.array().of(Yup.string())
|
||
}),
|
||
onSubmit: (values) => {
|
||
const submitFood = (imageUrls: string[] = []) => {
|
||
const foodData: CreateFoodType = {
|
||
...values,
|
||
isActive,
|
||
images: imageUrls,
|
||
content: values.content.filter(item => item && item.trim().length > 0)
|
||
}
|
||
|
||
createFood(foodData, {
|
||
onSuccess: () => {
|
||
toast.success('غذا با موفقیت ایجاد شد')
|
||
window.location.href = Pages.foods.list
|
||
},
|
||
onError: (error: ErrorType) => {
|
||
toast.error(error?.response?.data?.error?.message[0] || 'خطا در ایجاد غذا')
|
||
}
|
||
})
|
||
}
|
||
|
||
if (imageFiles.length > 0) {
|
||
multipleUpload(imageFiles, {
|
||
onSuccess: (response) => {
|
||
const imageUrls = response?.data?.map(item => item.url) || []
|
||
submitFood(imageUrls)
|
||
},
|
||
onError: (error: ErrorType) => {
|
||
toast.error(error?.response?.data?.error?.message[0] || 'خطا در آپلود تصاویر')
|
||
}
|
||
})
|
||
} else {
|
||
submitFood()
|
||
}
|
||
}
|
||
})
|
||
|
||
return (
|
||
<div className='w-full mt-4'>
|
||
<div className='flex w-full justify-between items-center'>
|
||
<div className='text-lg font-light'>
|
||
غذای جدید
|
||
</div>
|
||
<div>
|
||
<Button
|
||
className='px-5'
|
||
onClick={() => formik.handleSubmit()}
|
||
isloading={isPending || isUploading}
|
||
>
|
||
<div className='flex gap-2 items-center'>
|
||
<TickCircle className='size-5' color='white' />
|
||
<div>ثبت غذا</div>
|
||
</div>
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className='flex gap-6 mt-6'>
|
||
<div className='flex-1'>
|
||
<div className='bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||
<Input
|
||
label='نام غذا'
|
||
name='title'
|
||
placeholder=''
|
||
value={formik.values.title}
|
||
onChange={formik.handleChange}
|
||
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
|
||
/>
|
||
|
||
<div className='mt-5'>
|
||
<Select
|
||
items={categories}
|
||
label='دسته بندی'
|
||
placeholder='انتخاب کنید'
|
||
name='categoryId'
|
||
value={formik.values.categoryId}
|
||
onChange={(e) => {
|
||
const value = e.target.value
|
||
formik.setFieldValue('categoryId', value)
|
||
}}
|
||
error_text={formik.touched.categoryId && formik.errors.categoryId ? String(formik.errors.categoryId) : ''}
|
||
/>
|
||
</div>
|
||
|
||
<div className='grid grid-cols-3 gap-4 mt-5'>
|
||
<Input
|
||
name='price'
|
||
label='قیمت'
|
||
placeholder='تومان'
|
||
type='number'
|
||
seprator={true}
|
||
value={formik.values.price}
|
||
onChange={(e) => formik.setFieldValue('price', Number(e.target.value))}
|
||
error_text={formik.touched.price && formik.errors.price ? formik.errors.price : ''}
|
||
/>
|
||
|
||
<Input
|
||
name='discount'
|
||
label='تخفیف'
|
||
placeholder='تومان'
|
||
type='number'
|
||
seprator={true}
|
||
value={formik.values.discount}
|
||
onChange={(e) => formik.setFieldValue('discount', Number(e.target.value))}
|
||
error_text={formik.touched.discount && formik.errors.discount ? formik.errors.discount : ''}
|
||
/>
|
||
|
||
<Input
|
||
name='points'
|
||
label='امتیاز'
|
||
placeholder=''
|
||
type='number'
|
||
value={formik.values.points}
|
||
onChange={(e) => formik.setFieldValue('points', Number(e.target.value))}
|
||
error_text={formik.touched.points && formik.errors.points ? formik.errors.points : ''}
|
||
/>
|
||
</div>
|
||
|
||
<div className='grid grid-cols-3 gap-4 mt-5'>
|
||
<Input
|
||
name='prepareTime'
|
||
label='زمان آماده سازی'
|
||
placeholder='دقیقه'
|
||
type='number'
|
||
value={formik.values.prepareTime}
|
||
onChange={(e) => formik.setFieldValue('prepareTime', Number(e.target.value))}
|
||
error_text={formik.touched.prepareTime && formik.errors.prepareTime ? formik.errors.prepareTime : ''}
|
||
/>
|
||
|
||
<Input
|
||
name='stock'
|
||
label='موجودی'
|
||
placeholder=''
|
||
type='number'
|
||
value={formik.values.stock}
|
||
onChange={(e) => formik.setFieldValue('stock', Number(e.target.value))}
|
||
error_text={formik.touched.stock && formik.errors.stock ? formik.errors.stock : ''}
|
||
/>
|
||
|
||
<Input
|
||
name='stockDefault'
|
||
label='موجودی پیشفرض'
|
||
placeholder=''
|
||
type='number'
|
||
value={formik.values.stockDefault}
|
||
onChange={(e) => formik.setFieldValue('stockDefault', Number(e.target.value))}
|
||
error_text={formik.touched.stockDefault && formik.errors.stockDefault ? formik.errors.stockDefault : ''}
|
||
/>
|
||
</div>
|
||
|
||
<div className='mt-5'>
|
||
<Textarea
|
||
name='desc'
|
||
label='توضیحات غذا'
|
||
placeholder=''
|
||
value={formik.values.desc}
|
||
onChange={formik.handleChange}
|
||
error_text={formik.touched.desc && formik.errors.desc ? formik.errors.desc : ''}
|
||
/>
|
||
</div>
|
||
|
||
<div className='mt-5'>
|
||
<div className='text-sm mb-2'>محتوای غذا</div>
|
||
<div className='space-y-2'>
|
||
{formik.values.content.map((item, index) => (
|
||
<div key={index} className='flex gap-2 items-center'>
|
||
<Input
|
||
placeholder='متن محتوا'
|
||
value={item}
|
||
onChange={(e) => {
|
||
const newContent = [...formik.values.content]
|
||
newContent[index] = e.target.value
|
||
formik.setFieldValue('content', newContent)
|
||
}}
|
||
/>
|
||
<Button
|
||
type='button'
|
||
className='px-3 bg-red-500 hover:bg-red-600 w-auto'
|
||
onClick={() => {
|
||
const newContent = formik.values.content.filter((_, i) => i !== index)
|
||
formik.setFieldValue('content', newContent)
|
||
}}
|
||
>
|
||
حذف
|
||
</Button>
|
||
</div>
|
||
))}
|
||
<Button
|
||
type='button'
|
||
className='w-full bg-gray-200 text-gray-700 hover:bg-gray-300'
|
||
onClick={() => {
|
||
formik.setFieldValue('content', [...formik.values.content, ''])
|
||
}}
|
||
>
|
||
افزودن محتوا
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className='mt-6'>
|
||
<div className='text-sm mb-3'>وعدههای غذایی</div>
|
||
<div className='flex gap-6 flex-wrap'>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.breakfast}
|
||
onCheckedChange={(checked) => formik.setFieldValue('breakfast', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>صبحانه</label>
|
||
</div>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.noon}
|
||
onCheckedChange={(checked) => formik.setFieldValue('noon', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>ناهار</label>
|
||
</div>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.dinner}
|
||
onCheckedChange={(checked) => formik.setFieldValue('dinner', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>شام</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className='mt-6'>
|
||
<div className='text-sm mb-3'>روزهای هفته</div>
|
||
<div className='flex gap-6 flex-wrap'>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.sat}
|
||
onCheckedChange={(checked) => formik.setFieldValue('sat', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>شنبه</label>
|
||
</div>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.sun}
|
||
onCheckedChange={(checked) => formik.setFieldValue('sun', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>یکشنبه</label>
|
||
</div>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.mon}
|
||
onCheckedChange={(checked) => formik.setFieldValue('mon', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>دوشنبه</label>
|
||
</div>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.tue}
|
||
onCheckedChange={(checked) => formik.setFieldValue('tue', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>سهشنبه</label>
|
||
</div>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.wed}
|
||
onCheckedChange={(checked) => formik.setFieldValue('wed', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>چهارشنبه</label>
|
||
</div>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.thu}
|
||
onCheckedChange={(checked) => formik.setFieldValue('thu', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>پنجشنبه</label>
|
||
</div>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.fri}
|
||
onCheckedChange={(checked) => formik.setFieldValue('fri', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>جمعه</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className='mt-6'>
|
||
<div className='text-sm mb-3'>گزینههای سرویس</div>
|
||
<div className='flex gap-6 flex-wrap'>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.pickupServe}
|
||
onCheckedChange={(checked) => formik.setFieldValue('pickupServe', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'> بیرونبر</label>
|
||
</div>
|
||
<div className='flex items-center gap-2'>
|
||
<Checkbox
|
||
checked={formik.values.inPlaceServe}
|
||
onCheckedChange={(checked) => formik.setFieldValue('inPlaceServe', checked)}
|
||
/>
|
||
<label className='text-xs cursor-pointer'>سرو در محل</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<CreateFoodSidebar
|
||
isActive={isActive}
|
||
isSpecial={isSpecial}
|
||
onChangeActive={setIsActive}
|
||
onChangeSpecial={setIsSpecial}
|
||
onChangeImages={setImageFiles}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default CreateFood |