update customer and update service category and ...

This commit is contained in:
hamid zarghami
2025-03-06 11:49:31 +03:30
parent 55e7cb6e77
commit 2fb3a64eb1
13 changed files with 600 additions and 29 deletions
+1
View File
@@ -87,6 +87,7 @@ export const Pages = {
customer: {
list: "/customers/list",
create: "/customers/create",
update: "/customers/update/",
},
cardBank: {
list: "/card-bank/list",
+4 -1
View File
@@ -645,7 +645,10 @@
"enter_company_name": "نام ثبتی شرکت را وارد کنید",
"postal_code": "کد پستی",
"enter_postal_code": "کد پستی را وارد کنید",
"address": "آدرس"
"address": "آدرس",
"image_profile": "تصویر پروفایل",
"upload_image": "آپلود تصویر",
"new_password": "رمز عبور جدید"
},
"messages": {
"messages_list": "لیست پیام ها",
+17 -18
View File
@@ -54,10 +54,9 @@ const AddCustomer: FC = () => {
nationalCode: '',
password: '',
economicCode: '',
companyRegisteredName: '',
registrationId: '',
nationalId: '',
number: '',
registrationName: '',
registrationCode: '',
nationalIdentity: '',
postalCode: '',
address: '',
cityId: '',
@@ -72,15 +71,15 @@ const AddCustomer: FC = () => {
nationalCode: Yup.string().required(t('errors.required')),
password: Yup.string().required(t('errors.required')),
economicCode: Yup.string().required(t('errors.required')),
companyRegisteredName: Yup.string().required(t('errors.required')),
registrationId: Yup.string().required(t('errors.required')),
nationalId: Yup.string().required(t('errors.required')),
number: Yup.string().required(t('errors.required')),
registrationName: Yup.string().required(t('errors.required')),
registrationCode: Yup.string().required(t('errors.required')),
nationalIdentity: Yup.string().required(t('errors.required')),
postalCode: Yup.string().required(t('errors.required')),
address: Yup.string().required(t('errors.required')),
cityId: Yup.string().required(t('errors.required')),
}),
onSubmit: async (values) => {
values.cityId = +values.cityId
if (file) {
const formData = new FormData()
formData.append('file', file)
@@ -185,15 +184,15 @@ const AddCustomer: FC = () => {
<Input
label={t('customer.copany_name')}
placeholder={t('customer.enter_company_name')}
{...formik.getFieldProps('companyRegisteredName')}
error_text={formik.touched.companyRegisteredName && formik.errors.companyRegisteredName ? formik.errors.companyRegisteredName : ''}
{...formik.getFieldProps('registrationName')}
error_text={formik.touched.registrationName && formik.errors.registrationName ? formik.errors.registrationName : ''}
/>
<Input
label={t('customer.id_national')}
placeholder={t('customer.enter_id_nation')}
{...formik.getFieldProps('nationalId')}
error_text={formik.touched.nationalId && formik.errors.nationalId ? formik.errors.nationalId : ''}
{...formik.getFieldProps('nationalIdentity')}
error_text={formik.touched.nationalIdentity && formik.errors.nationalIdentity ? formik.errors.nationalIdentity : ''}
/>
@@ -222,16 +221,16 @@ const AddCustomer: FC = () => {
<Input
label={t('customer.registeration_id')}
placeholder={t('customer.enter_registeration_id')}
{...formik.getFieldProps('registrationId')}
error_text={formik.touched.registrationId && formik.errors.registrationId ? formik.errors.registrationId : ''}
{...formik.getFieldProps('registrationCode')}
error_text={formik.touched.registrationCode && formik.errors.registrationCode ? formik.errors.registrationCode : ''}
/>
<Input
{/* <Input
label={t('customer.your_phone_fixed')}
placeholder={t('customer.enter_phone_fixed')}
{...formik.getFieldProps('number')}
error_text={formik.touched.number && formik.errors.number ? formik.errors.number : ''}
/>
{...formik.getFieldProps('phone')}
error_text={formik.touched.phone && formik.errors.phone ? formik.errors.phone : ''}
/> */}
</div>
<div className='mt-8 rowTwoInput'>
+1 -1
View File
@@ -105,7 +105,7 @@ const CustomerList: FC = () => {
</Link>
</Td>
<Td text={''}>
<Link to={Pages.ticket.detail + '1'}>
<Link to={Pages.customer.update + item.id}>
<Eye size={20} color='#8C90A3' />
</Link>
</Td>
+331
View File
@@ -0,0 +1,331 @@
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import UploadBoxDraggble from '../../components/UploadBoxDraggble';
import Button from '../../components/Button';
import { TickCircle } from 'iconsax-react';
import DatePickerComponent from '../../components/DatePicker';
import Select from '../../components/Select';
import * as Yup from 'yup'
import { useFormik } from 'formik';
import { CreateCustomerType, ProvinesItemType } from './types/CustomerTypes';
import { useCreateCustomer, useGetCities, useGetCustomerDetail, useGetProvines, useUpdateCustomer } from './hooks/useCustomerData';
import PageLoading from '../../components/PageLoading';
import { toast } from 'react-toastify';
import { ErrorType } from '../../helpers/types';
import { useSingleUpload } from '../service/hooks/useServiceData';
import { useNavigate, useParams } from 'react-router-dom';
import { Pages } from '../../config/Pages';
import Textarea from '../../components/Textarea';
const UpdateCustomer: FC = () => {
const { id } = useParams()
const { t } = useTranslation('global')
const navigate = useNavigate()
const [provinesId, setProvinesId] = useState<string>('')
const [file, setFile] = useState<File>()
const getProvines = useGetProvines()
const getCities = useGetCities(provinesId)
const singleUpload = useSingleUpload()
const createCustomer = useCreateCustomer()
const getDetailCustomer = useGetCustomerDetail(id)
const updateCustomer = useUpdateCustomer(id ? id : '')
const handleUpdateCustomer = (values: CreateCustomerType) => {
updateCustomer.mutate(values, {
onSuccess: () => {
toast.success(t('success'))
navigate(Pages.customer.list)
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0])
}
})
}
const formik = useFormik<CreateCustomerType>({
initialValues: {
phone: '',
firstName: '',
lastName: '',
email: '',
birthDate: '',
nationalCode: '',
password: '',
economicCode: '',
registrationName: '',
registrationCode: '',
nationalIdentity: '',
postalCode: '',
address: '',
cityId: '',
profilePic: ''
},
validationSchema: Yup.object({
phone: Yup.string().required(t('errors.required')),
firstName: Yup.string().required(t('errors.required')),
lastName: Yup.string().required(t('errors.required')),
email: Yup.string().email(t('errors.email')).required(t('errors.required')),
birthDate: Yup.string().required(t('errors.required')),
nationalCode: Yup.string().required(t('errors.required')),
economicCode: Yup.string().required(t('errors.required')),
registrationName: Yup.string().required(t('errors.required')),
registrationCode: Yup.string().required(t('errors.required')),
nationalIdentity: Yup.string().required(t('errors.required')),
postalCode: Yup.string().required(t('errors.required')),
address: Yup.string().required(t('errors.required')),
cityId: Yup.string().required(t('errors.required')),
}),
onSubmit: async (values) => {
values.cityId = +values.cityId
if (values.password === '') values.password = undefined
if (file) {
const formData = new FormData()
formData.append('file', file)
await singleUpload.mutateAsync(formData, {
onSuccess: (data) => {
values.profilePic = data?.data?.url
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
}
})
handleUpdateCustomer(values)
} else {
values.profilePic = undefined
handleUpdateCustomer(values)
}
}
})
useEffect(() => {
if (getDetailCustomer.data) {
const data = getDetailCustomer.data.data?.customer
const legal = getDetailCustomer.data.data?.customer.legalUser
formik.setValues({
phone: data.phone || '',
firstName: data.firstName || '',
lastName: data.lastName || '',
email: data.email || '',
birthDate: data.birthDate || '',
nationalCode: data.nationalCode || '',
password: data.password || '',
economicCode: legal.economicCode || '',
registrationName: legal.registrationName || '',
registrationCode: legal.registrationCode || '',
nationalIdentity: legal.nationalIdentity || '',
postalCode: legal.address.postalCode || '',
address: legal.address?.fullAddress || '',
cityId: legal?.address?.city?.id,
profilePic: data.profilePic || ''
});
setProvinesId(legal?.address?.city.province?.id);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getDetailCustomer.data])
return (
<div className='w-full mt-4'>
<div className='flex w-full justify-between items-center'>
<div>
{t('customer.add_cutomer')}
</div>
<div>
<Button
className='px-5'
onClick={() => formik.handleSubmit()}
isLoading={createCustomer.isPending || singleUpload.isPending}
>
<div className='flex gap-2'>
<TickCircle
className='size-5'
color='#fff'
/>
<div>{t('save')}</div>
</div>
</Button>
</div>
</div>
{
getProvines.isPending ?
<PageLoading />
:
<div className='flex gap-6'>
<div className='flex-1'>
<div className='flex-1 mt-10 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
<div className='rowTwoInput'>
<Input
label={t('customer.name')}
{...formik.getFieldProps('firstName')}
error_text={formik.touched.firstName && formik.errors.firstName ? formik.errors.firstName : ''}
/>
<Input
label={t('customer.family')}
{...formik.getFieldProps('lastName')}
error_text={formik.touched.lastName && formik.errors.lastName ? formik.errors.lastName : ''}
/>
</div>
<div className='mt-8 rowTwoInput'>
<DatePickerComponent
label={t('customer.dateofbirth')}
onChange={(value) => formik.setFieldValue('birthDate', value)}
error_text={formik.touched.birthDate && formik.errors.birthDate ? formik.errors.birthDate : ''}
placeholder=''
defaulValue={formik.values.birthDate}
/>
<Input
label={t('customer.national_code')}
placeholder={t('customer.enter_nation_code')}
{...formik.getFieldProps('nationalCode')}
error_text={formik.touched.nationalCode && formik.errors.nationalCode ? formik.errors.nationalCode : ''}
/>
</div>
<div className='mt-8 rowTwoInput'>
<Input
label={t('customer.email')}
placeholder={t('customer.enter_email')}
{...formik.getFieldProps('email')}
error_text={formik.touched.email && formik.errors.email ? formik.errors.email : ''}
/>
<Input
label={t('customer.phone_number')}
placeholder={t('customer.enter_phone_number')}
{...formik.getFieldProps('phone')}
error_text={formik.touched.phone && formik.errors.phone ? formik.errors.phone : ''}
/>
</div>
<div className='mt-8'>
{t('customer.information_legal')}
</div>
<div className='mt-8 rowTwoInput'>
<Input
label={t('customer.copany_name')}
placeholder={t('customer.enter_company_name')}
{...formik.getFieldProps('registrationName')}
error_text={formik.touched.registrationName && formik.errors.registrationName ? formik.errors.registrationName : ''}
/>
<Input
label={t('customer.id_national')}
placeholder={t('customer.enter_id_nation')}
{...formik.getFieldProps('nationalIdentity')}
error_text={formik.touched.nationalIdentity && formik.errors.nationalIdentity ? formik.errors.nationalIdentity : ''}
/>
</div>
<div className='mt-8 rowTwoInput'>
<Input
label={t('customer.economic_code')}
placeholder={t('customer.enter_economic_code')}
{...formik.getFieldProps('economicCode')}
error_text={formik.touched.economicCode && formik.errors.economicCode ? formik.errors.economicCode : ''}
/>
<Input
label={t('customer.postal_code')}
placeholder={t('customer.enter_postal_code')}
{...formik.getFieldProps('postalCode')}
error_text={formik.touched.postalCode && formik.errors.postalCode ? formik.errors.postalCode : ''}
/>
</div>
<div className='mt-8 rowTwoInput'>
<Input
label={t('customer.registeration_id')}
placeholder={t('customer.enter_registeration_id')}
{...formik.getFieldProps('registrationCode')}
error_text={formik.touched.registrationCode && formik.errors.registrationCode ? formik.errors.registrationCode : ''}
/>
{/* <Input
label={t('customer.your_phone_fixed')}
placeholder={t('customer.enter_phone_fixed')}
{...formik.getFieldProps('phone')}
error_text={formik.touched.phone && formik.errors.phone ? formik.errors.phone : ''}
/> */}
</div>
<div className='mt-8 rowTwoInput'>
<Select
label={t('customer.state')}
items={getProvines.data?.data?.provinces.map((item: ProvinesItemType) => ({ label: item.name, value: item.id }))}
onChange={(e) => setProvinesId(e.target.value)}
placeholder={t('select')}
value={provinesId}
/>
<Select
label={t('customer.city')}
items={getCities.data?.data?.cities ? getCities.data?.data?.cities.map((item: ProvinesItemType) => ({ label: item.name, value: item.id })) : []}
placeholder={t('select')}
{...formik.getFieldProps('cityId')}
error_text={formik.touched.cityId && formik.errors.cityId ? formik.errors.cityId : ''}
/>
</div>
<div className='mt-8 rowTwoInput'>
<Textarea
{...formik.getFieldProps('address')}
placeholder={t('customer.address')}
error_text={formik.touched.address && formik.errors.address ? formik.errors.address : ''}
/>
</div>
</div>
</div>
<div className='bg-white mt-10 w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl'>
<div>
<Input
label={t('customer.new_password')}
type='password'
{...formik.getFieldProps('password')}
error_text={formik.touched.password && formik.errors.password ? formik.errors.password : ''}
/>
</div>
{/* <div className='mt-5'>
<Input
label={t('customer.reapeat_password')}
type='password'
{...formik.getFieldProps('password')}
error_text={formik.touched.password && formik.errors.password ? formik.errors.password : ''}
/>
</div> */}
<div className='mt-8'>
<div>{t('customer.image_profile')}</div>
<div className='mt-2'>
<UploadBoxDraggble
label={t('customer.upload_image')}
onChange={(files) => setFile(files[0])}
/>
</div>
</div>
</div>
</div>
}
</div>
)
}
export default UpdateCustomer
@@ -36,3 +36,24 @@ export const useCreateCustomer = () => {
},
});
};
export const useGetCustomerDetail = (id?: string) => {
return useQuery({
queryKey: ["customer-detail", id],
queryFn: () => api.getDetailCustomer(id ? id : ""),
enabled: !!id,
});
};
export const useUpdateCustomer = (id: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (variables: CreateCustomerType) =>
api.updateCustomer(id, variables),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["customers"],
});
},
});
};
+14 -1
View File
@@ -17,6 +17,19 @@ export const getCities = async (id: string) => {
};
export const createCustomer = async (params: CreateCustomerType) => {
const { data } = await axios.post(`/users/customer`, params);
const { data } = await axios.post(`/users/customers`, params);
return data;
};
export const getDetailCustomer = async (id: string) => {
const { data } = await axios.get(`/users/customers/${id}`);
return data;
};
export const updateCustomer = async (
id: string,
params: CreateCustomerType
) => {
const { data } = await axios.patch(`/users/customers/${id}`, params);
return data;
};
+6 -7
View File
@@ -26,18 +26,17 @@ export type CreateCustomerType = {
phone: string;
firstName: string;
lastName: string;
email: string | null;
email: string;
birthDate: string;
nationalCode: string;
password: string;
password?: string;
economicCode: string;
companyRegisteredName: string;
registrationId: string;
nationalId: string;
number: string;
registrationName: string;
registrationCode: string;
nationalIdentity: string;
postalCode: string;
address: string;
cityId: string;
cityId: string | number;
profilePic?: string;
};
+4 -1
View File
@@ -9,6 +9,7 @@ import { ServiceCategoryType } from './types/ServiceTypes'
import { useGetAllCategory } from './hooks/useServiceData'
import StatusCategory from './components/StatusCategory'
import Pagination from '../../components/Pagination'
import UpdateCategory from './components/UpdateCategory'
const Category: FC = () => {
@@ -87,7 +88,9 @@ const Category: FC = () => {
id={item.id}
/>
</Td>
<Td text={''} />
<Td text={''}>
<UpdateCategory refetch={() => getCategory.refetch()} id={item.id} />
</Td>
</tr>
)
})
@@ -0,0 +1,168 @@
import { Edit, TickCircle } from 'iconsax-react'
import { FC, useEffect, useState } from 'react'
import DefaulModal from '../../../components/DefaulModal'
import { useGetCategoryDetail, useGetCategoryParents, useSingleUpload, useUpdateCategory } from '../hooks/useServiceData'
import Input from '../../../components/Input'
import { CreateServiceCategoryType, ServiceCategoryType } from '../types/ServiceTypes'
import * as Yup from 'yup'
import { useFormik } from 'formik'
import { toast } from 'react-toastify'
import { useTranslation } from 'react-i18next'
import { ErrorType } from '../../../helpers/types'
import Select from '../../../components/Select'
import UploadBox from '../../../components/UploadBox'
import Button from '../../../components/Button'
type Props = {
id: string,
refetch: () => void
}
const UpdateCategory: FC<Props> = (props) => {
const { t } = useTranslation('global')
const [showModal, setShowModal] = useState<boolean>(false)
const [file, setFile] = useState<File>()
const getCategoryDetail = useGetCategoryDetail(props.id, showModal)
const singleUpload = useSingleUpload()
const getCategory = useGetCategoryParents()
const updateCategory = useUpdateCategory(props.id)
const formik = useFormik<CreateServiceCategoryType>({
initialValues: {
title: '',
isActive: true,
parentId: ''
},
validationSchema: Yup.object({
title: Yup.string().required(t('errors.required')),
}),
onSubmit: async (values) => {
if (file) {
const formData = new FormData()
formData.append('file', file)
await singleUpload.mutateAsync(formData, {
onSuccess: (data) => {
values.icon = data?.data?.url
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
}
})
}
const params = {
...values,
parentId: values.parentId ? values.parentId : undefined
}
updateCategory.mutate(params, {
onSuccess: () => {
formik.resetForm()
getCategory.refetch()
toast.success(t('success'))
setShowModal(false)
props.refetch()
},
onError: (error: ErrorType) => {
toast.error(error?.response?.data?.error?.message[0])
}
})
}
})
useEffect(() => {
if (getCategoryDetail.data) {
const data = getCategoryDetail.data?.data?.category
formik.setValues({
title: data?.title,
parentId: data?.parentId,
isActive: data?.isActive
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getCategoryDetail.data])
return (
<div>
<Edit onClick={() => setShowModal(true)} size={20} color='#888888' />
<DefaulModal
open={showModal}
close={() => setShowModal(false)}
isHeader
title_header='ویرایش دسته بندی'
>
<div className='mt-5'>
<Input
label={t('service.title_category')}
value={formik.values.title}
name='title'
onChange={formik.handleChange}
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
className='bg-white bg-opacity-40'
/>
</div>
{
getCategory.data &&
<div className='mt-5'>
<Select
className='bg-white bg-opacity-40'
label={t('service.category_parent_2')}
items={getCategory.data?.data?.categories?.map((item: ServiceCategoryType) => {
return {
label: item.title,
value: item.id
}
})}
placeholder={t('select')}
name='parentId'
value={formik.values.parentId}
onChange={formik.handleChange}
error_text={formik.touched.parentId && formik.errors.parentId ? formik.errors.parentId : ''}
/>
</div>
}
<div className='mt-5'>
<UploadBox
label='آیکون جدید'
onChange={(file) => setFile(file[0])}
/>
</div>
<div className='mt-10 border-t border-border pt-5 flex justify-end'>
<div className='flex gap-5 items-center'>
<Button
className='w-[150px] bg-white bg-opacity-15 text-description'
label={t('plan.cancel')}
onClick={() => setShowModal(false)}
/>
<Button
onClick={() => formik.handleSubmit()}
>
<div className='flex gap-2'>
<TickCircle
size={20}
color='white'
/>
<div>
{t('save')}
</div>
</div>
</Button>
</div>
</div>
</DefaulModal>
</div>
)
}
export default UpdateCategory
+15
View File
@@ -161,3 +161,18 @@ export const useUpdateService = (id: string) => {
api.updateService(id, variables),
});
};
export const useGetCategoryDetail = (id: string, enabled: boolean) => {
return useQuery({
queryKey: ["category-detail", id],
queryFn: () => api.getCategoryDetail(id),
enabled: enabled,
});
};
export const useUpdateCategory = (id: string) => {
return useMutation({
mutationFn: (variables: CreateServiceCategoryType) =>
api.updateCategory(id, variables),
});
};
@@ -140,3 +140,19 @@ export const updateService = async (id: string, params: CreateServiceType) => {
const { data } = await axios.patch(`/danak-services/${id}`, params);
return data;
};
export const getCategoryDetail = async (id: string) => {
const { data } = await axios.get(`/danak-services/categories/${id}`);
return data;
};
export const updateCategory = async (
id: string,
params: CreateServiceCategoryType
) => {
const { data } = await axios.patch(
`/danak-services/categories/${id}`,
params
);
return data;
};
+2
View File
@@ -57,6 +57,7 @@ import GroupList from '../pages/users/GroupList'
import Reviews from '../pages/service/Reviews'
import UpdateAds from '../pages/ads/Update'
import UpdateService from '../pages/service/UpdateService'
import UpdateCustomer from '../pages/customer/Update'
const MainRouter: FC = () => {
@@ -114,6 +115,7 @@ const MainRouter: FC = () => {
<Route path={Pages.profile} element={<Profile />} />
<Route path={Pages.customer.list} element={<CustomerList />} />
<Route path={Pages.customer.create} element={<CreateCustomer />} />
<Route path={Pages.customer.update + ':id'} element={<UpdateCustomer />} />
<Route path={Pages.messages.list} element={<MessagesList />} />
<Route path={Pages.messages.detail + ':id'} element={<MessageDetail />} />
<Route path={Pages.users.list} element={<UsersList />} />