create food

This commit is contained in:
hamid zarghami
2025-11-15 12:07:50 +03:30
parent d33ad86209
commit b7562f911d
6 changed files with 117 additions and 67 deletions
@@ -90,7 +90,7 @@ const TransactionsTable: FC = () => {
id: 1, id: 1,
number: 1, number: 1,
transactionType: 'پرداخت اینترنتی', transactionType: 'پرداخت اینترنتی',
amount: '۴,۰۰۰,۰۰۰ ریال', amount: '۴,۰۰۰,۰۰۰ تومان',
paymentDate: '۱۳۶۹/۰۵/۱۱', paymentDate: '۱۳۶۹/۰۵/۱۱',
status: 'successful', status: 'successful',
}, },
@@ -98,7 +98,7 @@ const TransactionsTable: FC = () => {
id: 2, id: 2,
number: 1, number: 1,
transactionType: 'پرداخت اینترنتی', transactionType: 'پرداخت اینترنتی',
amount: '۴,۰۰۰,۰۰۰ ریال', amount: '۴,۰۰۰,۰۰۰ تومان',
paymentDate: '۱۳۶۹/۰۵/۱۱', paymentDate: '۱۳۶۹/۰۵/۱۱',
status: 'unsuccessful', status: 'unsuccessful',
}, },
@@ -106,7 +106,7 @@ const TransactionsTable: FC = () => {
id: 3, id: 3,
number: 1, number: 1,
transactionType: 'پرداخت اینترنتی', transactionType: 'پرداخت اینترنتی',
amount: '۴,۰۰۰,۰۰۰ ریال', amount: '۴,۰۰۰,۰۰۰ تومان',
paymentDate: '۱۳۶۹/۰۵/۱۱', paymentDate: '۱۳۶۹/۰۵/۱۱',
status: 'unknown', status: 'unknown',
}, },
+88 -42
View File
@@ -2,6 +2,7 @@ import { type FC, useState } from 'react'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import * as Yup from 'yup' import * as Yup from 'yup'
import { TickCircle } from 'iconsax-react' import { TickCircle } from 'iconsax-react'
import { toast } from 'react-toastify'
import Input from '@/components/Input' import Input from '@/components/Input'
import Select from '@/components/Select' import Select from '@/components/Select'
import Textarea from '@/components/Textarea' import Textarea from '@/components/Textarea'
@@ -9,7 +10,10 @@ import Button from '@/components/Button'
import { Checkbox } from '@/components/ui/checkbox' import { Checkbox } from '@/components/ui/checkbox'
import CreateFoodSidebar from './components/CreateFoodSidebar' import CreateFoodSidebar from './components/CreateFoodSidebar'
import type { CreateFoodType } from './types/Types' import type { CreateFoodType } from './types/Types'
import { useGetCategories } from './hooks/useFoodData' import { useCreateFood, useGetCategories } from './hooks/useFoodData'
import { useMultipleUpload } from '../uploader/hooks/useUploaderData'
import { Pages } from '@/config/Pages'
import type { ErrorType } from '@/helpers/types'
@@ -19,8 +23,9 @@ const CreateFood: FC = () => {
const [isActive, setIsActive] = useState<boolean>(true) const [isActive, setIsActive] = useState<boolean>(true)
const [isSpecial, setIsSpecial] = useState<boolean>(false) const [isSpecial, setIsSpecial] = useState<boolean>(false)
const [imageFiles, setImageFiles] = useState<File[]>([]) const [imageFiles, setImageFiles] = useState<File[]>([])
const { mutate: createFood } = useCreateFood()
const { mutate: multipleUpload } = useMultipleUpload()
// تبدیل داده‌های دسته‌ها به فرمت مورد نیاز Select
const categories = data?.data?.map(category => ({ const categories = data?.data?.map(category => ({
label: category.title, label: category.title,
value: category.id value: category.id
@@ -29,7 +34,8 @@ const CreateFood: FC = () => {
const formik = useFormik<CreateFoodType>({ const formik = useFormik<CreateFoodType>({
initialValues: { initialValues: {
title: '', title: '',
content: '', desc: '',
content: [],
categoryIds: [], categoryIds: [],
price: 0, price: 0,
discount: 0, discount: 0,
@@ -37,7 +43,6 @@ const CreateFood: FC = () => {
prepareTime: 0, prepareTime: 0,
stock: 0, stock: 0,
stockDefault: 0, stockDefault: 0,
rate: 0,
breakfast: false, breakfast: false,
noon: false, noon: false,
dinner: false, dinner: false,
@@ -48,7 +53,6 @@ const CreateFood: FC = () => {
fri: false, fri: false,
sat: false, sat: false,
sun: false, sun: false,
isPickup: false,
pickupServe: false, pickupServe: false,
inPlaceServe: false, inPlaceServe: false,
isActive: true, isActive: true,
@@ -56,6 +60,7 @@ const CreateFood: FC = () => {
}, },
validationSchema: Yup.object().shape({ validationSchema: Yup.object().shape({
title: Yup.string().required('نام غذا الزامی است'), title: Yup.string().required('نام غذا الزامی است'),
desc: Yup.string().required('توضیحات غذا الزامی است'),
categoryIds: Yup.array().min(1, 'حداقل یک دسته‌بندی الزامی است'), categoryIds: Yup.array().min(1, 'حداقل یک دسته‌بندی الزامی است'),
price: Yup.number().required('قیمت الزامی است').min(0, 'قیمت باید مثبت باشد'), price: Yup.number().required('قیمت الزامی است').min(0, 'قیمت باید مثبت باشد'),
prepareTime: Yup.number().required('زمان آماده‌سازی الزامی است').min(0, 'زمان آماده‌سازی باید مثبت باشد'), prepareTime: Yup.number().required('زمان آماده‌سازی الزامی است').min(0, 'زمان آماده‌سازی باید مثبت باشد'),
@@ -63,22 +68,47 @@ const CreateFood: FC = () => {
stockDefault: Yup.number().required('موجودی پیش‌فرض الزامی است').min(0, 'موجودی پیش‌فرض باید مثبت باشد'), stockDefault: Yup.number().required('موجودی پیش‌فرض الزامی است').min(0, 'موجودی پیش‌فرض باید مثبت باشد'),
points: Yup.number().min(0, 'امتیاز باید مثبت باشد'), points: Yup.number().min(0, 'امتیاز باید مثبت باشد'),
discount: Yup.number().min(0, 'تخفیف باید مثبت باشد'), discount: Yup.number().min(0, 'تخفیف باید مثبت باشد'),
rate: Yup.number().min(0, 'امتیاز باید مثبت باشد').max(5, 'امتیاز نمی‌تواند بیشتر از ۵ باشد') content: Yup.array().of(Yup.string())
}), }),
onSubmit: async (values) => { onSubmit: (values) => {
const imageUrls = imageFiles.map(file => URL.createObjectURL(file)) const submitFood = (imageUrls: string[] = []) => {
console.log('Form values:', { const foodData: CreateFoodType = {
...values, ...values,
isActive, isActive,
images: imageUrls images: imageUrls
}) }
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 ( return (
<div className='w-full mt-4'> <div className='w-full mt-4'>
<div className='flex w-full justify-between items-center'> <div className='flex w-full justify-between items-center'>
<div className='text-xl font-bold'> <div className='text-lg font-light'>
غذای جدید غذای جدید
</div> </div>
<div> <div>
@@ -125,7 +155,7 @@ const CreateFood: FC = () => {
<Input <Input
name='price' name='price'
label='قیمت' label='قیمت'
placeholder='ریال' placeholder='تومان'
type='number' type='number'
seprator={true} seprator={true}
value={formik.values.price} value={formik.values.price}
@@ -136,7 +166,7 @@ const CreateFood: FC = () => {
<Input <Input
name='discount' name='discount'
label='تخفیف' label='تخفیف'
placeholder='ریال' placeholder='تومان'
type='number' type='number'
seprator={true} seprator={true}
value={formik.values.discount} value={formik.values.discount}
@@ -188,29 +218,52 @@ const CreateFood: FC = () => {
</div> </div>
<div className='mt-5'> <div className='mt-5'>
<Input <Textarea
name='rate' name='desc'
label='امتیاز (۰ تا ۵)' label='توضیحات غذا'
placeholder='' placeholder=''
type='number' value={formik.values.desc}
min='0' onChange={formik.handleChange}
max='5' error_text={formik.touched.desc && formik.errors.desc ? formik.errors.desc : ''}
step='0.1'
value={formik.values.rate}
onChange={(e) => formik.setFieldValue('rate', Number(e.target.value))}
error_text={formik.touched.rate && formik.errors.rate ? formik.errors.rate : ''}
/> />
</div> </div>
<div className='mt-5'> <div className='mt-5'>
<Textarea <div className='text-sm mb-2'>محتوای غذا</div>
name='content' <div className='space-y-2'>
label='توضیحات تکمیلی' {formik.values.content.map((item, index) => (
placeholder='' <div key={index} className='flex gap-2 items-center'>
value={formik.values.content} <Input
onChange={formik.handleChange} placeholder='متن محتوا'
error_text={formik.touched.content && formik.errors.content ? formik.errors.content : ''} 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>
<div className='mt-6'> <div className='mt-6'>
@@ -298,19 +351,12 @@ const CreateFood: FC = () => {
<div className='mt-6'> <div className='mt-6'>
<div className='text-sm mb-3'>گزینههای سرویس</div> <div className='text-sm mb-3'>گزینههای سرویس</div>
<div className='flex gap-6 flex-wrap'> <div className='flex gap-6 flex-wrap'>
<div className='flex items-center gap-2'>
<Checkbox
checked={formik.values.isPickup}
onCheckedChange={(checked) => formik.setFieldValue('isPickup', checked)}
/>
<label className='text-xs cursor-pointer'>تحویل بیرونبر</label>
</div>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Checkbox <Checkbox
checked={formik.values.pickupServe} checked={formik.values.pickupServe}
onCheckedChange={(checked) => formik.setFieldValue('pickupServe', checked)} onCheckedChange={(checked) => formik.setFieldValue('pickupServe', checked)}
/> />
<label className='text-xs cursor-pointer'>سرو در بیرونبر</label> <label className='text-xs cursor-pointer'> بیرونبر</label>
</div> </div>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Checkbox <Checkbox
+9 -10
View File
@@ -1,29 +1,28 @@
import type { IResponse } from "@/types/response.types"; import type { IResponse } from "@/types/response.types";
export type CreateFoodType = { export type CreateFoodType = {
breakfast: false; breakfast: boolean;
tue: false; tue: boolean;
wed: false; wed: boolean;
thu: false; thu: boolean;
fri: false; fri: boolean;
title: string; title: string;
content: string; desc: string;
content: string[];
price: number; price: number;
points: number; points: number;
prepareTime: number; prepareTime: number;
sat: boolean; sat: boolean;
sun: boolean; sun: boolean;
mon: boolean; mon: boolean;
noon: false; noon: boolean;
dinner: boolean; dinner: boolean;
isPickup: false;
stock: number; stock: number;
stockDefault: number; stockDefault: number;
isActive: boolean; isActive: boolean;
images: string[]; images: string[];
inPlaceServe: false; inPlaceServe: boolean;
pickupServe: boolean; pickupServe: boolean;
rate: number;
discount: number; discount: number;
categoryIds: string[]; categoryIds: string[];
}; };
+4 -4
View File
@@ -188,25 +188,25 @@ const OrderDetails: FC = () => {
<div className=' text-description'> <div className=' text-description'>
مبلغ سفارش مبلغ سفارش
</div> </div>
<div>۴۰۰۰۰۰۰ ریال</div> <div>۴۰۰۰۰۰۰ تومان</div>
</div> </div>
<div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'> <div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className=' text-description'> <div className=' text-description'>
تخفیف تخفیف
</div> </div>
<div>۰ ریال</div> <div>۰ تومان</div>
</div> </div>
<div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'> <div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className=' text-description'> <div className=' text-description'>
هزینه ارسال هزینه ارسال
</div> </div>
<div>۲۰۰۰۰۰ ریال</div> <div>۲۰۰۰۰۰ تومان</div>
</div> </div>
<div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'> <div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className=' text-description'> <div className=' text-description'>
مالیات مالیات
</div> </div>
<div>۰ ریال</div> <div>۰ تومان</div>
</div> </div>
<div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'> <div className='flex mt-4 text-[13px] font-light justify-between items-center border-b border-border border-dashed pb-4'>
<div className=' text-description'> <div className=' text-description'>
+6 -6
View File
@@ -115,7 +115,7 @@ const OrdersList: FC = () => {
id: 1, id: 1,
orderNumber: '12345', orderNumber: '12345',
customerName: 'مهرداد مظفری', customerName: 'مهرداد مظفری',
orderAmount: '۴۲۰۰۰۰ ریال', orderAmount: '۴۲۰۰۰۰ تومان',
orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰', orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
deliveryType: 'تحویل با پیک', deliveryType: 'تحویل با پیک',
paymentType: 'پرداخت آنلاین', paymentType: 'پرداخت آنلاین',
@@ -125,7 +125,7 @@ const OrdersList: FC = () => {
id: 2, id: 2,
orderNumber: '12345', orderNumber: '12345',
customerName: 'مهرداد مظفری', customerName: 'مهرداد مظفری',
orderAmount: '۴۲۰۰۰۰ ریال', orderAmount: '۴۲۰۰۰۰ تومان',
orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰', orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
deliveryType: 'تحویل با پیک', deliveryType: 'تحویل با پیک',
paymentType: 'پرداخت آنلاین', paymentType: 'پرداخت آنلاین',
@@ -135,7 +135,7 @@ const OrdersList: FC = () => {
id: 3, id: 3,
orderNumber: '12345', orderNumber: '12345',
customerName: 'مهرداد مظفری', customerName: 'مهرداد مظفری',
orderAmount: '۴۲۰۰۰۰ ریال', orderAmount: '۴۲۰۰۰۰ تومان',
orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰', orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
deliveryType: 'تحویل با پیک', deliveryType: 'تحویل با پیک',
paymentType: 'پرداخت آنلاین', paymentType: 'پرداخت آنلاین',
@@ -145,7 +145,7 @@ const OrdersList: FC = () => {
id: 4, id: 4,
orderNumber: '12345', orderNumber: '12345',
customerName: 'مهرداد مظفری', customerName: 'مهرداد مظفری',
orderAmount: '۴۲۰۰۰۰ ریال', orderAmount: '۴۲۰۰۰۰ تومان',
orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰', orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
deliveryType: 'تحویل با پیک', deliveryType: 'تحویل با پیک',
paymentType: 'پرداخت با کیف پول', paymentType: 'پرداخت با کیف پول',
@@ -155,7 +155,7 @@ const OrdersList: FC = () => {
id: 5, id: 5,
orderNumber: '12345', orderNumber: '12345',
customerName: 'مهرداد مظفری', customerName: 'مهرداد مظفری',
orderAmount: '۴۲۰۰۰۰ ریال', orderAmount: '۴۲۰۰۰۰ تومان',
orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰', orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
deliveryType: 'سرو در رستوران', deliveryType: 'سرو در رستوران',
paymentType: 'پرداخت در محل', paymentType: 'پرداخت در محل',
@@ -165,7 +165,7 @@ const OrdersList: FC = () => {
id: 6, id: 6,
orderNumber: '12345', orderNumber: '12345',
customerName: 'مهرداد مظفری', customerName: 'مهرداد مظفری',
orderAmount: '۴۲۰۰۰۰ ریال', orderAmount: '۴۲۰۰۰۰ تومان',
orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰', orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
deliveryType: 'تحویل در ماشین', deliveryType: 'تحویل در ماشین',
paymentType: 'پرداخت آنلاین', paymentType: 'پرداخت آنلاین',
+7 -2
View File
@@ -7,13 +7,14 @@ import { Link } from 'react-router-dom'
import { Pages } from '../config/Pages' import { Pages } from '../config/Pages'
import { useSharedStore } from './store/sharedStore' import { useSharedStore } from './store/sharedStore'
import { HambergerMenu, Wallet } from 'iconsax-react' import { HambergerMenu, Wallet } from 'iconsax-react'
import { clx } from '@/helpers/utils'
const Header: FC = () => { const Header: FC = () => {
// const location = useLocation(); // const location = useLocation();
// const [popoverKey, setPopoverKey] = useState(0); // const [popoverKey, setPopoverKey] = useState(0);
const { t } = useTranslation('global') const { t } = useTranslation('global')
const { setOpenSidebar, openSidebar } = useSharedStore() const { setOpenSidebar, openSidebar, hasSubMenu } = useSharedStore()
// useEffect(() => { // useEffect(() => {
// setPopoverKey((prevKey) => prevKey + 1); // setPopoverKey((prevKey) => prevKey + 1);
@@ -26,12 +27,16 @@ const Header: FC = () => {
// } // }
return ( return (
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'> <div className={clx(
'fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]',
hasSubMenu && 'xl:w-[calc(100%-340px)] xl:right-[320px]',
)}>
<div className='min-w-[270px] hidden xl:block'> <div className='min-w-[270px] hidden xl:block'>
<Input <Input
variant='search' variant='search'
placeholder={t('header.search')} placeholder={t('header.search')}
className='bg-[#F5F5F5]'
/> />
</div> </div>
<div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'> <div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'>