score setting
This commit is contained in:
@@ -49,13 +49,16 @@ const Input: FC<Props> = (props: Props) => {
|
|||||||
setFormattedValue(formatted);
|
setFormattedValue(formatted);
|
||||||
|
|
||||||
// ارسال مقدار خام به `onChange` والد
|
// ارسال مقدار خام به `onChange` والد
|
||||||
props.onChange?.({
|
const syntheticEvent = {
|
||||||
...event,
|
...event,
|
||||||
target: {
|
target: {
|
||||||
...event.target,
|
...event.target,
|
||||||
|
name: event.target.name,
|
||||||
value: inputValue,
|
value: inputValue,
|
||||||
},
|
},
|
||||||
});
|
} as React.ChangeEvent<HTMLInputElement>;
|
||||||
|
|
||||||
|
props.onChange?.(syntheticEvent);
|
||||||
} else {
|
} else {
|
||||||
props.onChange?.(event);
|
props.onChange?.(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import Tabs from '@/components/Tabs'
|
import Tabs from '@/components/Tabs'
|
||||||
import { Card, KeySquare, Setting4, TruckFast } from 'iconsax-react'
|
import { KeySquare, Money2, Setting4, TruckFast } from 'iconsax-react'
|
||||||
import { useState, type FC } from 'react'
|
import { useState, type FC } from 'react'
|
||||||
import { SettingTabEnum } from './enum/Enum'
|
import { SettingTabEnum } from './enum/Enum'
|
||||||
import GeneralSettings from './GeneralSettings'
|
import GeneralSettings from './GeneralSettings'
|
||||||
|
import ScoreSetting from './components/ScoreSetting'
|
||||||
|
|
||||||
const Setting: FC = () => {
|
const Setting: FC = () => {
|
||||||
|
|
||||||
@@ -26,8 +27,8 @@ const Setting: FC = () => {
|
|||||||
value: SettingTabEnum.PASSWORD,
|
value: SettingTabEnum.PASSWORD,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Card color={activeTab === SettingTabEnum.FINANCIAL ? 'black' : '#8C90A3'} size={24} />,
|
icon: <Money2 color={activeTab === SettingTabEnum.FINANCIAL ? 'black' : '#8C90A3'} size={24} />,
|
||||||
label: 'مالی',
|
label: 'تنظیمات امتیاز',
|
||||||
value: SettingTabEnum.FINANCIAL,
|
value: SettingTabEnum.FINANCIAL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -45,6 +46,9 @@ const Setting: FC = () => {
|
|||||||
{
|
{
|
||||||
activeTab === SettingTabEnum.GENERAL && <GeneralSettings />
|
activeTab === SettingTabEnum.GENERAL && <GeneralSettings />
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
activeTab === SettingTabEnum.FINANCIAL && <ScoreSetting />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import { useEffect, useState, type FC } from 'react'
|
||||||
|
import { useGetRestaurant, useUpdateRestaurant } from '../hooks/useSettingData'
|
||||||
|
import type { UpdateRestaurantType } from '../types/Types'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import Input from '@/components/Input'
|
||||||
|
import Button from '@/components/Button'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
interface FlatFormValues {
|
||||||
|
purchaseAmount: string
|
||||||
|
purchaseScore: string
|
||||||
|
scoreAmount: string
|
||||||
|
scoreCredit: string
|
||||||
|
birthdayScore: string
|
||||||
|
registerScore: string
|
||||||
|
marriageDateScore: string
|
||||||
|
referrerScore: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScoreSetting: FC = () => {
|
||||||
|
|
||||||
|
const { data: restaurant } = useGetRestaurant()
|
||||||
|
const { mutate: updateRestaurant, isPending } = useUpdateRestaurant()
|
||||||
|
const [isInitialized, setIsInitialized] = useState(false)
|
||||||
|
|
||||||
|
const formik = useFormik<FlatFormValues>({
|
||||||
|
initialValues: {
|
||||||
|
purchaseAmount: '',
|
||||||
|
purchaseScore: '',
|
||||||
|
scoreAmount: '',
|
||||||
|
scoreCredit: '',
|
||||||
|
birthdayScore: '',
|
||||||
|
registerScore: '',
|
||||||
|
marriageDateScore: '',
|
||||||
|
referrerScore: '',
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object().shape({
|
||||||
|
purchaseAmount: Yup.string().required('مبلغ خرید الزامی است'),
|
||||||
|
purchaseScore: Yup.string().required('امتیاز خرید الزامی است'),
|
||||||
|
scoreAmount: Yup.string().required('مبلغ امتیاز الزامی است'),
|
||||||
|
scoreCredit: Yup.string().required('مبلغ اعتبار الزامی است'),
|
||||||
|
birthdayScore: Yup.string().required('امتیاز تولد الزامی است'),
|
||||||
|
registerScore: Yup.string().required('امتیاز ثبت نام الزامی است'),
|
||||||
|
marriageDateScore: Yup.string().required('امتیاز سالگرد ازدواج الزامی است'),
|
||||||
|
referrerScore: Yup.string().required('امتیاز معرف الزامی است'),
|
||||||
|
}),
|
||||||
|
validateOnMount: false,
|
||||||
|
onSubmit: (values) => {
|
||||||
|
const payload: UpdateRestaurantType = {
|
||||||
|
score: {
|
||||||
|
purchaseAmount: values.purchaseAmount,
|
||||||
|
purchaseScore: values.purchaseScore,
|
||||||
|
scoreAmount: values.scoreAmount,
|
||||||
|
scoreCredit: values.scoreCredit,
|
||||||
|
birthdayScore: values.birthdayScore,
|
||||||
|
registerScore: values.registerScore,
|
||||||
|
marriageDateScore: values.marriageDateScore,
|
||||||
|
referrerScore: values.referrerScore,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
updateRestaurant(payload, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('تنظیمات امتیاز با موفقیت بهروزرسانی شد')
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error('خطا در بهروزرسانی تنظیمات امتیاز')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const getFieldError = (fieldName: string): string | undefined => {
|
||||||
|
const meta = formik.getFieldMeta(fieldName)
|
||||||
|
return meta.touched && meta.error ? meta.error : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (restaurant?.data?.score && !isInitialized) {
|
||||||
|
const scorePrice = restaurant.data.score
|
||||||
|
formik.setValues({
|
||||||
|
purchaseAmount: scorePrice.purchaseAmount != null ? String(scorePrice.purchaseAmount) : '',
|
||||||
|
purchaseScore: scorePrice.purchaseScore != null ? String(scorePrice.purchaseScore) : '',
|
||||||
|
scoreAmount: scorePrice.scoreAmount != null ? String(scorePrice.scoreAmount) : '',
|
||||||
|
scoreCredit: scorePrice.scoreCredit != null ? String(scorePrice.scoreCredit) : '',
|
||||||
|
birthdayScore: scorePrice.birthdayScore != null ? String(scorePrice.birthdayScore) : '',
|
||||||
|
registerScore: scorePrice.registerScore != null ? String(scorePrice.registerScore) : '',
|
||||||
|
marriageDateScore: scorePrice.marriageDateScore != null ? String(scorePrice.marriageDateScore) : '',
|
||||||
|
referrerScore: scorePrice.referrerScore != null ? String(scorePrice.referrerScore) : '',
|
||||||
|
}, false)
|
||||||
|
formik.setTouched({}, false)
|
||||||
|
setIsInitialized(true)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [restaurant, isInitialized])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={formik.handleSubmit} className='bg-white rounded-2xl p-6'>
|
||||||
|
{/* بخش امتیاز خرید */}
|
||||||
|
<div className='mb-8'>
|
||||||
|
<h2 className='text-base font-medium mb-6 text-right'>امتیاز خرید</h2>
|
||||||
|
|
||||||
|
{/* خط اول */}
|
||||||
|
<div className='flex items-center gap-3 mb-4'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<span className='text-sm text-gray-700 whitespace-nowrap'>به ازای هر</span>
|
||||||
|
<Input
|
||||||
|
name='purchaseAmount'
|
||||||
|
value={formik.values.purchaseAmount}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
error_text={getFieldError('purchaseAmount')}
|
||||||
|
placeholder='0'
|
||||||
|
seprator
|
||||||
|
className='w-[200px]'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<span className='text-sm text-gray-700 whitespace-nowrap'>تومان خرید مقدار</span>
|
||||||
|
<Input
|
||||||
|
name='purchaseScore'
|
||||||
|
value={formik.values.purchaseScore}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
error_text={getFieldError('purchaseScore')}
|
||||||
|
placeholder='0'
|
||||||
|
seprator
|
||||||
|
className='w-[200px]'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className='text-sm text-gray-700 whitespace-nowrap'>امتیاز به مشتری داده میشود.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* خط دوم */}
|
||||||
|
<div className='flex items-center gap-3'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<span className='text-sm text-gray-700 whitespace-nowrap'>هر</span>
|
||||||
|
<Input
|
||||||
|
name='scoreAmount'
|
||||||
|
value={formik.values.scoreAmount}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
error_text={getFieldError('scoreAmount')}
|
||||||
|
placeholder='0'
|
||||||
|
seprator
|
||||||
|
className='w-[200px]'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<span className='text-sm text-gray-700 whitespace-nowrap'>امتیاز معادل</span>
|
||||||
|
<Input
|
||||||
|
name='scoreCredit'
|
||||||
|
value={formik.values.scoreCredit}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
error_text={getFieldError('scoreCredit')}
|
||||||
|
placeholder='0'
|
||||||
|
seprator
|
||||||
|
className='w-[200px]'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className='text-sm text-gray-700 whitespace-nowrap'>تومان اعتبار خرید است.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* بخش امتیاز ثابت */}
|
||||||
|
<div className='mb-8'>
|
||||||
|
<h2 className='text-base font-medium mb-6 text-right'>امتیاز ثابت</h2>
|
||||||
|
|
||||||
|
<div className='grid grid-cols-1 md:grid-cols-2 gap-6 gap-x-8'>
|
||||||
|
<div className='space-y-4'>
|
||||||
|
<Input
|
||||||
|
label='امتیاز ثبت نام'
|
||||||
|
name='registerScore'
|
||||||
|
value={formik.values.registerScore}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
error_text={getFieldError('registerScore')}
|
||||||
|
placeholder='0'
|
||||||
|
seprator
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label='امتیاز تاریخ تولد'
|
||||||
|
name='birthdayScore'
|
||||||
|
value={formik.values.birthdayScore}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
error_text={getFieldError('birthdayScore')}
|
||||||
|
placeholder='0'
|
||||||
|
seprator
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='space-y-4'>
|
||||||
|
<Input
|
||||||
|
label='امتیاز تاریخ ازدواج'
|
||||||
|
name='marriageDateScore'
|
||||||
|
value={formik.values.marriageDateScore}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
error_text={getFieldError('marriageDateScore')}
|
||||||
|
placeholder='0'
|
||||||
|
seprator
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label='امتیاز دعوت'
|
||||||
|
name='referrerScore'
|
||||||
|
value={formik.values.referrerScore}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
error_text={getFieldError('referrerScore')}
|
||||||
|
placeholder='0'
|
||||||
|
seprator
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
isloading={isPending}
|
||||||
|
className='w-full md:w-48'
|
||||||
|
>
|
||||||
|
ذخیره تغییرات
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ScoreSetting
|
||||||
@@ -8,16 +8,26 @@ export type GeoJSONPolygon = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateRestaurantType = {
|
export type UpdateRestaurantType = {
|
||||||
name: string;
|
name?: string;
|
||||||
menuColor: string;
|
menuColor?: string;
|
||||||
phone: string;
|
phone?: string;
|
||||||
instagram: string;
|
instagram?: string;
|
||||||
address: string;
|
address?: string;
|
||||||
logo?: string;
|
logo?: string;
|
||||||
latitude: number;
|
latitude?: number;
|
||||||
longitude: number;
|
longitude?: number;
|
||||||
description: string;
|
description?: string;
|
||||||
serviceArea?: GeoJSONPolygon;
|
serviceArea?: GeoJSONPolygon;
|
||||||
|
score?: {
|
||||||
|
purchaseAmount: string;
|
||||||
|
purchaseScore: string;
|
||||||
|
scoreAmount: string;
|
||||||
|
scoreCredit: string;
|
||||||
|
birthdayScore: string;
|
||||||
|
registerScore: string;
|
||||||
|
marriageDateScore: string;
|
||||||
|
referrerScore: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Restaurant = {
|
export type Restaurant = {
|
||||||
@@ -27,25 +37,36 @@ export type Restaurant = {
|
|||||||
deletedAt: string | null;
|
deletedAt: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
logo: string;
|
logo: string | null;
|
||||||
address: string;
|
address: string | null;
|
||||||
menuColor: string;
|
menuColor: string | null;
|
||||||
latitude: number;
|
latitude: number | null;
|
||||||
longitude: number;
|
longitude: number | null;
|
||||||
serviceArea: GeoJSONPolygon;
|
serviceArea: GeoJSONPolygon;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
establishedYear: number | null;
|
establishedYear: number | null;
|
||||||
phoneNumber: string | null;
|
phoneNumber: string | null;
|
||||||
phone: string;
|
phone: string;
|
||||||
instagram: string;
|
instagram: string | null;
|
||||||
telegram: string | null;
|
telegram: string | null;
|
||||||
whatsapp: string | null;
|
whatsapp: string | null;
|
||||||
description: string;
|
description: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
tagNames: unknown | null;
|
tagNames: unknown | null;
|
||||||
images: unknown | null;
|
images: unknown | null;
|
||||||
vat: number;
|
vat: number;
|
||||||
|
domain: string;
|
||||||
|
score: {
|
||||||
|
scoreAmount: string;
|
||||||
|
scoreCredit: string;
|
||||||
|
birthdayScore: string;
|
||||||
|
purchaseScore: string;
|
||||||
|
referrerScore: string;
|
||||||
|
registerScore: string;
|
||||||
|
purchaseAmount: string;
|
||||||
|
marriageDateScore: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetRestaurantResponse = IResponse<Restaurant>;
|
export type GetRestaurantResponse = IResponse<Restaurant>;
|
||||||
|
|||||||
Reference in New Issue
Block a user