crud company
This commit is contained in:
@@ -115,5 +115,7 @@ export const Pages = {
|
||||
company: {
|
||||
create: "/company/create",
|
||||
industries: "/company/industries",
|
||||
list: "/company/list",
|
||||
detail: "/company/detail/",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,4 +7,5 @@ export const SideBarItemHasSubMenu = [
|
||||
"learning",
|
||||
"blog",
|
||||
"support",
|
||||
"company",
|
||||
];
|
||||
|
||||
+25
-14
@@ -25,6 +25,17 @@ body,
|
||||
}
|
||||
* {
|
||||
outline: none;
|
||||
scrollbar-width: auto;
|
||||
-ms-overflow-style: auto;
|
||||
}
|
||||
*::-webkit-scrollbar {
|
||||
display: block;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: #c9c9c9;
|
||||
border-radius: 5px;
|
||||
}
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
@@ -135,9 +146,14 @@ tbody tr {
|
||||
@apply rounded-b-xl text-right;
|
||||
font-family: irancell !important;
|
||||
direction: rtl;
|
||||
min-height: 200px !important;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.ql-toolbar {
|
||||
@apply rounded-t-xl;
|
||||
position: relative !important;
|
||||
z-index: 10;
|
||||
}
|
||||
.ql-editor {
|
||||
@apply !text-right;
|
||||
@@ -151,22 +167,17 @@ tbody tr {
|
||||
font-family: "irancell" !important;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
.main-content-scroll {
|
||||
scrollbar-width: auto !important;
|
||||
-ms-overflow-style: auto !important;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
display: none;
|
||||
.main-content-scroll::-webkit-scrollbar {
|
||||
display: block !important;
|
||||
width: 5px !important;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
min-height: 200px !important;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ql-toolbar {
|
||||
position: relative !important;
|
||||
z-index: 10;
|
||||
.main-content-scroll::-webkit-scrollbar-thumb {
|
||||
background-color: #c9c9c9 !important;
|
||||
border-radius: 5px !important;
|
||||
}
|
||||
|
||||
+22
-2
@@ -71,7 +71,8 @@
|
||||
"card": "حساب های بانکی",
|
||||
"payments": "پرداخت ها",
|
||||
"sliders": "اسلایدر",
|
||||
"support": "پلن پشتیبانی"
|
||||
"support": "پلن پشتیبانی",
|
||||
"company": "شرکت ها"
|
||||
},
|
||||
"slider": {
|
||||
"new_slider": "اسلایدر جدید",
|
||||
@@ -808,6 +809,7 @@
|
||||
"company": {
|
||||
"count_company": "تعداد شرکت ها",
|
||||
"company_name": "نام شرکت",
|
||||
"ceo_name": "نام مدیر عامل",
|
||||
"ceo_first_name": "نام مدیر عامل",
|
||||
"ceo_last_name": "نام خانوادگی مدیر عامل",
|
||||
"phone_number": "شماره تماس",
|
||||
@@ -816,6 +818,24 @@
|
||||
"establishment_date": "تاریخ تاسیس",
|
||||
"address": "آدرس",
|
||||
"map_link": "لینک نقشه شرکت",
|
||||
"short_description": "توضیح کوتاه شرکت"
|
||||
"short_description": "توضیح کوتاه شرکت",
|
||||
"website_url": "آدرس وبسایت",
|
||||
"profile_image_url": "تصویر پروفایل",
|
||||
"cover_image_url": "تصویر پوشه",
|
||||
"is_active": "وضعیت فعالیت",
|
||||
"industry_id": "صنعت",
|
||||
"industry": "صنعت",
|
||||
"profile_image": "تصویر پروفایل",
|
||||
"cover_image": "تصویر کاور",
|
||||
"created_at": "تاریخ ثبت",
|
||||
"status": "وضعیت",
|
||||
"plans": "پلن ها",
|
||||
"detail": "جزییات",
|
||||
"slider": "اسلایدر",
|
||||
"category": "دسته بندی",
|
||||
"company_developed": "شرکت توسعه دهنده",
|
||||
"slug": "اسلاگ",
|
||||
"industries": "صنعت ها",
|
||||
"invoice_count": "تعداد صورتحساب"
|
||||
}
|
||||
}
|
||||
|
||||
+167
-53
@@ -6,40 +6,129 @@ import CreateSidebar from './components/CreateSidebar'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { CreateCompanyType } from './types/CompanyTypes'
|
||||
import { useState } from 'react'
|
||||
import { useMultiUpload, useSingleUpload } from '../service/hooks/useServiceData'
|
||||
import { useCreateCompany } from './hooks/useCompanyData'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../helpers/types'
|
||||
import { Pages } from '../../config/Pages'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import CompanyServices from './components/CompanyServices'
|
||||
import CompanyProducts from './components/CompanyProducts'
|
||||
import DatePickerComponent from '../../components/DatePicker'
|
||||
import moment from 'moment-jalaali'
|
||||
|
||||
const CreateCompany: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation('global')
|
||||
const [profileImage, setProfileImage] = useState<File | null>(null)
|
||||
const [coverImage, setCoverImage] = useState<File | null>(null)
|
||||
const multiUpload = useMultiUpload()
|
||||
const singleUpload = useSingleUpload()
|
||||
const createCompany = useCreateCompany()
|
||||
|
||||
const formik = useFormik({
|
||||
const formik = useFormik<CreateCompanyType>({
|
||||
initialValues: {
|
||||
phone: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
nationalCode: '',
|
||||
password: '',
|
||||
name: '',
|
||||
ceoFirstName: '',
|
||||
ceoLastName: '',
|
||||
phoneNumber: '',
|
||||
email: '',
|
||||
nationalId: '',
|
||||
establishmentDate: '',
|
||||
identificationNumber: '',
|
||||
dateOfEstablishment: '',
|
||||
address: '',
|
||||
mapLink: '',
|
||||
shortDescription: ''
|
||||
mapAddressLink: '',
|
||||
description: '',
|
||||
websiteUrl: '',
|
||||
profileImageUrl: '',
|
||||
coverImageUrl: '',
|
||||
isActive: true,
|
||||
industryId: '',
|
||||
products: [],
|
||||
services: [],
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
phone: Yup.string().required(t('errors.required')),
|
||||
firstName: Yup.string().required(t('errors.required')),
|
||||
lastName: Yup.string().required(t('errors.required')),
|
||||
nationalCode: Yup.string().required(t('errors.required')),
|
||||
password: Yup.string().required(t('errors.required')),
|
||||
name: Yup.string().required(t('errors.required')),
|
||||
ceoFirstName: Yup.string().required(t('errors.required')),
|
||||
ceoLastName: Yup.string().required(t('errors.required')),
|
||||
phoneNumber: Yup.string().required(t('errors.required')),
|
||||
email: Yup.string().email(t('errors.invalid_email')).required(t('errors.required')),
|
||||
nationalId: Yup.string().required(t('errors.required')),
|
||||
establishmentDate: Yup.string().required(t('errors.required')),
|
||||
identificationNumber: Yup.string().required(t('errors.required')),
|
||||
dateOfEstablishment: Yup.string().required(t('errors.required')),
|
||||
address: Yup.string().required(t('errors.required')),
|
||||
mapLink: Yup.string().url(t('errors.invalid_url')),
|
||||
shortDescription: Yup.string().required(t('errors.required'))
|
||||
mapAddressLink: Yup.string().url(t('errors.invalid_url')),
|
||||
description: Yup.string().required(t('errors.required')),
|
||||
websiteUrl: Yup.string().url(t('errors.invalid_url')).required(t('errors.required')),
|
||||
isActive: Yup.boolean().required(t('errors.required')),
|
||||
industryId: Yup.string().required(t('errors.required')),
|
||||
// products and services validation can be added if needed
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
console.log(values)
|
||||
onSubmit: async (values) => {
|
||||
|
||||
if (!profileImage || !coverImage) {
|
||||
toast.error('لطفا تصاویر را انتخاب کنید')
|
||||
return
|
||||
}
|
||||
|
||||
// آپلود تصاویر خدمات
|
||||
let serviceImages: string[] = []
|
||||
if (services.length > 0) {
|
||||
const formDataServices = new FormData()
|
||||
services.forEach(s => s.image && formDataServices.append('files', s.image))
|
||||
const serviceUploadRes = await multiUpload.mutateAsync(formDataServices)
|
||||
serviceImages = serviceUploadRes.data.map((item: { url: string }) => item?.url)
|
||||
}
|
||||
|
||||
// آپلود تصاویر محصولات
|
||||
let productImages: string[] = []
|
||||
if (products.length > 0) {
|
||||
const formDataProducts = new FormData()
|
||||
products.forEach(p => p.image && formDataProducts.append('files', p.image))
|
||||
const productUploadRes = await multiUpload.mutateAsync(formDataProducts)
|
||||
productImages = productUploadRes.data.map((item: { url: string }) => item?.url)
|
||||
}
|
||||
|
||||
// مقداردهی به values
|
||||
values.services = services.map((s, idx) => ({
|
||||
title: s.title,
|
||||
imageUrl: serviceImages[idx] ? serviceImages[idx] : ''
|
||||
}))
|
||||
values.products = products.map((p, idx) => ({
|
||||
title: p.title,
|
||||
imageUrl: productImages[idx] ? productImages[idx] : ''
|
||||
}))
|
||||
|
||||
const formDataProfile = new FormData()
|
||||
formDataProfile.append('file', profileImage)
|
||||
const profileUploadRes = await singleUpload.mutateAsync(formDataProfile)
|
||||
values.profileImageUrl = profileUploadRes.data.url
|
||||
|
||||
const formDataCover = new FormData()
|
||||
formDataCover.append('file', coverImage)
|
||||
const coverUploadRes = await singleUpload.mutateAsync(formDataCover)
|
||||
values.coverImageUrl = coverUploadRes.data.url
|
||||
|
||||
await createCompany.mutateAsync(values, {
|
||||
onSuccess: () => {
|
||||
toast.success('شرکت با موفقیت ثبت شد')
|
||||
navigate(Pages.company.list)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const [services, setServices] = useState<{ title: string; image?: File }[]>([])
|
||||
const [products, setProducts] = useState<{ title: string; image?: File }[]>([])
|
||||
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='flex justify-between items-center'>
|
||||
@@ -50,6 +139,7 @@ const CreateCompany: FC = () => {
|
||||
<Button
|
||||
className='w-fit px-4'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={createCompany.isPending || singleUpload.isPending || multiUpload.isPending}
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<TickCircle size={20} color='white' />
|
||||
@@ -63,41 +153,57 @@ const CreateCompany: FC = () => {
|
||||
<div className='flex-1 mt-10 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||||
<div>اطلاعات شرکت</div>
|
||||
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label={t('company.company_name')}
|
||||
label='نام شرکت'
|
||||
name='name'
|
||||
value={formik.values.name}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.name && formik.errors.name ? formik.errors.name : ''}
|
||||
/>
|
||||
<Input
|
||||
label={t('company.ceo_first_name')}
|
||||
name='ceoFirstName'
|
||||
value={formik.values.ceoFirstName}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.ceoFirstName && formik.errors.ceoFirstName ? formik.errors.ceoFirstName : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label={t('company.ceo_last_name')}
|
||||
name='ceoLastName'
|
||||
value={formik.values.ceoLastName}
|
||||
label='نام'
|
||||
name='firstName'
|
||||
value={formik.values.firstName}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.ceoLastName && formik.errors.ceoLastName ? formik.errors.ceoLastName : ''}
|
||||
error_text={formik.touched.firstName && formik.errors.firstName ? formik.errors.firstName : ''}
|
||||
/>
|
||||
<Input
|
||||
label='نام خانوادگی'
|
||||
name='lastName'
|
||||
value={formik.values.lastName}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.lastName && formik.errors.lastName ? formik.errors.lastName : ''}
|
||||
/>
|
||||
</div>
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label='کد ملی'
|
||||
name='nationalCode'
|
||||
value={formik.values.nationalCode}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.nationalCode && formik.errors.nationalCode ? formik.errors.nationalCode : ''}
|
||||
/>
|
||||
<Input
|
||||
label='رمز عبور'
|
||||
name='password'
|
||||
type='password'
|
||||
value={formik.values.password}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.password && formik.errors.password ? formik.errors.password : ''}
|
||||
/>
|
||||
</div>
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label={t('company.phone_number')}
|
||||
name='phoneNumber'
|
||||
value={formik.values.phoneNumber}
|
||||
name='phone'
|
||||
value={formik.values.phone}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.phoneNumber && formik.errors.phoneNumber ? formik.errors.phoneNumber : ''}
|
||||
error_text={formik.touched.phone && formik.errors.phone ? formik.errors.phone : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label={t('company.email')}
|
||||
name='email'
|
||||
@@ -105,29 +211,32 @@ const CreateCompany: FC = () => {
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.email && formik.errors.email ? formik.errors.email : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label={t('company.national_id')}
|
||||
name='nationalId'
|
||||
value={formik.values.nationalId}
|
||||
name='identificationNumber'
|
||||
value={formik.values.identificationNumber}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.nationalId && formik.errors.nationalId ? formik.errors.nationalId : ''}
|
||||
error_text={formik.touched.identificationNumber && formik.errors.identificationNumber ? formik.errors.identificationNumber : ''}
|
||||
/>
|
||||
|
||||
<DatePickerComponent
|
||||
label={t('company.establishment_date')}
|
||||
placeholder='تاریخ تاسیس'
|
||||
onChange={(date) => formik.setFieldValue('dateOfEstablishment', moment(date).format('YYYY-MM-DD'))}
|
||||
error_text={formik.touched.dateOfEstablishment && formik.errors.dateOfEstablishment ? formik.errors.dateOfEstablishment : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label={t('company.establishment_date')}
|
||||
name='establishmentDate'
|
||||
value={formik.values.establishmentDate}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.establishmentDate && formik.errors.establishmentDate ? formik.errors.establishmentDate : ''}
|
||||
/>
|
||||
<Input
|
||||
label={t('company.map_link')}
|
||||
name='mapLink'
|
||||
value={formik.values.mapLink}
|
||||
name='mapAddressLink'
|
||||
value={formik.values.mapAddressLink}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.mapLink && formik.errors.mapLink ? formik.errors.mapLink : ''}
|
||||
error_text={formik.touched.mapAddressLink && formik.errors.mapAddressLink ? formik.errors.mapAddressLink : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -144,16 +253,21 @@ const CreateCompany: FC = () => {
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label={t('company.short_description')}
|
||||
name='shortDescription'
|
||||
value={formik.values.shortDescription}
|
||||
name='description'
|
||||
value={formik.values.description}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.shortDescription && formik.errors.shortDescription ? formik.errors.shortDescription : ''}
|
||||
error_text={formik.touched.description && formik.errors.description ? formik.errors.description : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CompanyServices value={services} onChange={setServices} />
|
||||
<CompanyProducts value={products} onChange={setProducts} />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<CreateSidebar />
|
||||
<CreateSidebar formik={formik} setProfileImage={setProfileImage} setCoverImage={setCoverImage} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { FC } from 'react'
|
||||
import { useGetCompanies } from './hooks/useCompanyData'
|
||||
import Td from '../../components/Td'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { CompanyItemType } from './types/CompanyTypes'
|
||||
import moment from 'moment-jalaali'
|
||||
import { Edit } from 'iconsax-react'
|
||||
import DeleteCompany from './components/DeleteCompany'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../config/Pages'
|
||||
import ToggleStatusCompany from './components/ToggleStatus'
|
||||
|
||||
const CompanyList: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { data, isLoading, refetch } = useGetCompanies(1)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
isLoading ?
|
||||
<PageLoading />
|
||||
:
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
||||
<table className='w-full text-sm '>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={t('company.company_name')} />
|
||||
<Td text={t('company.ceo_name')} />
|
||||
<Td text={t('company.industry')} />
|
||||
<Td text={t('company.invoice_count')} />
|
||||
<Td text={t('company.status')} />
|
||||
<Td text={t('company.created_at')} />
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
data?.data?.companies?.map((item: CompanyItemType) => {
|
||||
return <tr className='tr' key={item.id}>
|
||||
<Td text={item.name} />
|
||||
<Td text={item.user?.firstName + ' ' + item.user?.lastName} />
|
||||
<Td text={item.industry?.title} />
|
||||
<Td text={item.invoiceCount.toString()} />
|
||||
<Td text=''>
|
||||
<ToggleStatusCompany
|
||||
defaultActive={item.isActive}
|
||||
id={item.id}
|
||||
/>
|
||||
</Td>
|
||||
<Td text={moment(item.createdAt).format('jYYYY/jMM/jDD')} />
|
||||
<Td text={''}>
|
||||
<div className='flex gap-2'>
|
||||
<Link to={Pages.company.detail + item.id}>
|
||||
<Edit size={20} color='#888' />
|
||||
</Link>
|
||||
<DeleteCompany refetch={() => refetch()} id={item.id} />
|
||||
</div>
|
||||
</Td>
|
||||
</tr>
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CompanyList
|
||||
@@ -0,0 +1,378 @@
|
||||
import { FC, useEffect } from 'react'
|
||||
import Button from '../../components/Button'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import Input from '../../components/Input'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { CreateCompanyType } from './types/CompanyTypes'
|
||||
import { useState } from 'react'
|
||||
import { useMultiUpload, useSingleUpload } from '../service/hooks/useServiceData'
|
||||
import { useGetCompanyDetail, useUpdateCompany } from './hooks/useCompanyData'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../helpers/types'
|
||||
import { Pages } from '../../config/Pages'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import CompanyServices from './components/CompanyServices'
|
||||
import CompanyProducts from './components/CompanyProducts'
|
||||
import DatePickerComponent from '../../components/DatePicker'
|
||||
import moment from 'moment-jalaali'
|
||||
import UpdateSidebar from './components/UpdateSideBar'
|
||||
|
||||
type ServiceType = {
|
||||
id: string;
|
||||
title: string;
|
||||
imageUrl: string;
|
||||
company: string;
|
||||
}
|
||||
|
||||
type ProductType = {
|
||||
id: string;
|
||||
title: string;
|
||||
imageUrl: string;
|
||||
company: string;
|
||||
}
|
||||
|
||||
const UpdateCompany: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation('global')
|
||||
const [profileImage, setProfileImage] = useState<File | null>(null)
|
||||
const [coverImage, setCoverImage] = useState<File | null>(null)
|
||||
const multiUpload = useMultiUpload()
|
||||
const singleUpload = useSingleUpload()
|
||||
const updateCompany = useUpdateCompany()
|
||||
const { data: companyDetail } = useGetCompanyDetail(id)
|
||||
|
||||
const formik = useFormik<CreateCompanyType>({
|
||||
initialValues: {
|
||||
phone: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
nationalCode: '',
|
||||
password: '',
|
||||
name: '',
|
||||
email: '',
|
||||
identificationNumber: '',
|
||||
dateOfEstablishment: '',
|
||||
address: '',
|
||||
mapAddressLink: '',
|
||||
description: '',
|
||||
websiteUrl: '',
|
||||
profileImageUrl: '',
|
||||
coverImageUrl: '',
|
||||
isActive: true,
|
||||
industryId: '',
|
||||
products: [],
|
||||
services: [],
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
phone: Yup.string().required(t('errors.required')),
|
||||
firstName: Yup.string().required(t('errors.required')),
|
||||
lastName: Yup.string().required(t('errors.required')),
|
||||
nationalCode: Yup.string().required(t('errors.required')),
|
||||
name: Yup.string().required(t('errors.required')),
|
||||
email: Yup.string().email(t('errors.invalid_email')).required(t('errors.required')),
|
||||
identificationNumber: Yup.string().required(t('errors.required')),
|
||||
dateOfEstablishment: Yup.string().required(t('errors.required')),
|
||||
address: Yup.string().required(t('errors.required')),
|
||||
mapAddressLink: Yup.string().url(t('errors.invalid_url')),
|
||||
description: Yup.string().required(t('errors.required')),
|
||||
websiteUrl: Yup.string().url(t('errors.invalid_url')).required(t('errors.required')),
|
||||
isActive: Yup.boolean().required(t('errors.required')),
|
||||
industryId: Yup.string().required(t('errors.required')),
|
||||
// products and services validation can be added if needed
|
||||
}),
|
||||
onSubmit: async (values) => {
|
||||
// آپلود تصاویر خدمات
|
||||
let serviceImages: string[] = []
|
||||
const serviceImageIndices: number[] = []
|
||||
if (services.length > 0) {
|
||||
const formDataServices = new FormData()
|
||||
services.forEach((s, index) => {
|
||||
if (s.image) {
|
||||
formDataServices.append('files', s.image)
|
||||
serviceImageIndices.push(index)
|
||||
}
|
||||
})
|
||||
|
||||
// فقط تصاویر جدید را آپلود کن
|
||||
if (formDataServices.getAll('files').length > 0) {
|
||||
const serviceUploadRes = await multiUpload.mutateAsync(formDataServices)
|
||||
serviceImages = serviceUploadRes.data.map((item: { url: string }) => item?.url)
|
||||
}
|
||||
}
|
||||
|
||||
// آپلود تصاویر محصولات
|
||||
let productImages: string[] = []
|
||||
const productImageIndices: number[] = []
|
||||
if (products.length > 0) {
|
||||
const formDataProducts = new FormData()
|
||||
products.forEach((p, index) => {
|
||||
if (p.image) {
|
||||
formDataProducts.append('files', p.image)
|
||||
productImageIndices.push(index)
|
||||
}
|
||||
})
|
||||
|
||||
// فقط تصاویر جدید را آپلود کن
|
||||
if (formDataProducts.getAll('files').length > 0) {
|
||||
const productUploadRes = await multiUpload.mutateAsync(formDataProducts)
|
||||
productImages = productUploadRes.data.map((item: { url: string }) => item?.url)
|
||||
}
|
||||
}
|
||||
|
||||
// مقداردهی به values
|
||||
values.services = services.map((s, idx) => {
|
||||
if (s.existingImageUrl) {
|
||||
return {
|
||||
title: s.title,
|
||||
imageUrl: s.existingImageUrl
|
||||
}
|
||||
}
|
||||
|
||||
if (s.image) {
|
||||
const imageIndex = serviceImageIndices.indexOf(idx)
|
||||
if (imageIndex !== -1 && serviceImages[imageIndex]) {
|
||||
return {
|
||||
title: s.title,
|
||||
imageUrl: serviceImages[imageIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: s.title,
|
||||
imageUrl: ''
|
||||
}
|
||||
})
|
||||
|
||||
values.products = products.map((p, idx) => {
|
||||
if (p.existingImageUrl) {
|
||||
return {
|
||||
title: p.title,
|
||||
imageUrl: p.existingImageUrl
|
||||
}
|
||||
}
|
||||
|
||||
if (p.image) {
|
||||
const imageIndex = productImageIndices.indexOf(idx)
|
||||
if (imageIndex !== -1 && productImages[imageIndex]) {
|
||||
return {
|
||||
title: p.title,
|
||||
imageUrl: productImages[imageIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: p.title,
|
||||
imageUrl: ''
|
||||
}
|
||||
})
|
||||
|
||||
if (profileImage) {
|
||||
const formDataProfile = new FormData()
|
||||
formDataProfile.append('file', profileImage)
|
||||
const profileUploadRes = await singleUpload.mutateAsync(formDataProfile)
|
||||
values.profileImageUrl = profileUploadRes.data.url
|
||||
}
|
||||
|
||||
if (coverImage) {
|
||||
const formDataCover = new FormData()
|
||||
formDataCover.append('file', coverImage)
|
||||
const coverUploadRes = await singleUpload.mutateAsync(formDataCover)
|
||||
values.coverImageUrl = coverUploadRes.data.url
|
||||
}
|
||||
|
||||
await updateCompany.mutateAsync({ id: id || '', params: values }, {
|
||||
onSuccess: () => {
|
||||
toast.success('شرکت با موفقیت ثبت شد')
|
||||
navigate(Pages.company.list)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (companyDetail) {
|
||||
formik.setValues(companyDetail?.data?.company)
|
||||
formik.setFieldValue('dateOfEstablishment', moment(companyDetail?.data?.company?.dateOfEstablishment).format('YYYY-MM-DD'))
|
||||
formik.setFieldValue('firstName', companyDetail?.data?.company?.user?.firstName)
|
||||
formik.setFieldValue('lastName', companyDetail?.data?.company?.user?.lastName)
|
||||
formik.setFieldValue('nationalCode', companyDetail?.data?.company?.user?.nationalCode)
|
||||
formik.setFieldValue('phone', companyDetail?.data?.company?.user?.phone)
|
||||
formik.setFieldValue('email', companyDetail?.data?.company?.user?.email)
|
||||
formik.setFieldValue('address', companyDetail?.data?.company?.address)
|
||||
formik.setFieldValue("industryId", companyDetail?.data?.company?.industry?.id)
|
||||
|
||||
// ذخیره سرویسها با URL تصاویر موجود
|
||||
setServices(companyDetail?.data?.company?.services?.map((s: ServiceType) => ({
|
||||
title: s.title,
|
||||
existingImageUrl: s.imageUrl
|
||||
})) || [])
|
||||
|
||||
// ذخیره محصولات با URL تصاویر موجود
|
||||
setProducts(companyDetail?.data?.company?.products?.map((p: ProductType) => ({
|
||||
title: p.title,
|
||||
existingImageUrl: p.imageUrl
|
||||
})) || [])
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [companyDetail])
|
||||
|
||||
const [services, setServices] = useState<{ title: string; image?: File; existingImageUrl?: string }[]>([])
|
||||
const [products, setProducts] = useState<{ title: string; image?: File; existingImageUrl?: string }[]>([])
|
||||
|
||||
console.log(formik.errors);
|
||||
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div>
|
||||
ویرایش شرکت
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className='w-fit px-4'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={updateCompany.isPending || singleUpload.isPending || multiUpload.isPending}
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<TickCircle size={20} color='white' />
|
||||
<div>ثبت شرکت</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<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>اطلاعات شرکت</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label='نام شرکت'
|
||||
name='name'
|
||||
value={formik.values.name}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.name && formik.errors.name ? formik.errors.name : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label='نام'
|
||||
name='firstName'
|
||||
value={formik.values.firstName}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.firstName && formik.errors.firstName ? formik.errors.firstName : ''}
|
||||
/>
|
||||
<Input
|
||||
label='نام خانوادگی'
|
||||
name='lastName'
|
||||
value={formik.values.lastName}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.lastName && formik.errors.lastName ? formik.errors.lastName : ''}
|
||||
/>
|
||||
</div>
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label='کد ملی'
|
||||
name='nationalCode'
|
||||
value={formik.values.nationalCode}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.nationalCode && formik.errors.nationalCode ? formik.errors.nationalCode : ''}
|
||||
/>
|
||||
<Input
|
||||
label='رمز عبور'
|
||||
name='password'
|
||||
type='password'
|
||||
value={formik.values.password}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.password && formik.errors.password ? formik.errors.password : ''}
|
||||
/>
|
||||
</div>
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label={t('company.phone_number')}
|
||||
name='phone'
|
||||
value={formik.values.phone}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.phone && formik.errors.phone ? formik.errors.phone : ''}
|
||||
/>
|
||||
<Input
|
||||
label={t('company.email')}
|
||||
name='email'
|
||||
value={formik.values.email}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.email && formik.errors.email ? formik.errors.email : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label={t('company.national_id')}
|
||||
name='identificationNumber'
|
||||
value={formik.values.identificationNumber}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.identificationNumber && formik.errors.identificationNumber ? formik.errors.identificationNumber : ''}
|
||||
/>
|
||||
|
||||
<DatePickerComponent
|
||||
label={t('company.establishment_date')}
|
||||
placeholder='تاریخ تاسیس'
|
||||
onChange={(date) => formik.setFieldValue('dateOfEstablishment', moment(date).format('YYYY-MM-DD'))}
|
||||
error_text={formik.touched.dateOfEstablishment && formik.errors.dateOfEstablishment ? formik.errors.dateOfEstablishment : ''}
|
||||
defaulValue={formik.values.dateOfEstablishment}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label={t('company.map_link')}
|
||||
name='mapAddressLink'
|
||||
value={formik.values.mapAddressLink}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.mapAddressLink && formik.errors.mapAddressLink ? formik.errors.mapAddressLink : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label={t('company.address')}
|
||||
name='address'
|
||||
value={formik.values.address}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.address && formik.errors.address ? formik.errors.address : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label={t('company.short_description')}
|
||||
name='description'
|
||||
value={formik.values.description}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.description && formik.errors.description ? formik.errors.description : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CompanyServices value={services} onChange={setServices} />
|
||||
<CompanyProducts value={products} onChange={setProducts} />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<UpdateSidebar formik={formik} setProfileImage={setProfileImage} setCoverImage={setCoverImage} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateCompany
|
||||
@@ -0,0 +1,85 @@
|
||||
import { FC, useState } from 'react'
|
||||
import Input from '../../../components/Input'
|
||||
import UploadBox from '../../../components/UploadBox'
|
||||
import { Add, CloseCircle } from 'iconsax-react'
|
||||
|
||||
type Product = { title: string; image?: File; existingImageUrl?: string }
|
||||
|
||||
type Props = {
|
||||
value: Product[]
|
||||
onChange: (val: Product[]) => void
|
||||
}
|
||||
|
||||
const CompanyProducts: FC<Props> = ({ value, onChange }) => {
|
||||
const [productTitle, setProductTitle] = useState('')
|
||||
const [productImage, setProductImage] = useState<File | undefined>(undefined)
|
||||
const [productError, setProductError] = useState('')
|
||||
|
||||
const handleAddProduct = () => {
|
||||
if (!productTitle.trim()) {
|
||||
setProductError('عنوان محصول الزامی است')
|
||||
return
|
||||
}
|
||||
if (!productImage) {
|
||||
setProductError('تصویر محصول الزامی است')
|
||||
return
|
||||
}
|
||||
if (value.some(s => s.title.trim() === productTitle.trim())) {
|
||||
setProductError('عنوان محصول تکراری است')
|
||||
return
|
||||
}
|
||||
onChange([...value, { title: productTitle.trim(), image: productImage }])
|
||||
setProductTitle('')
|
||||
setProductImage(undefined)
|
||||
setProductError('')
|
||||
}
|
||||
|
||||
const handleRemoveProduct = (idx: number) => {
|
||||
onChange(value.filter((_, i) => i !== idx))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-8'>
|
||||
<div className='mb-2'>محصولات شرکت</div>
|
||||
<div className='rowTwoInput items-end gap-4'>
|
||||
<div className='w-full'>
|
||||
<Input
|
||||
label='عنوان محصول'
|
||||
name='productTitle'
|
||||
value={productTitle}
|
||||
onChange={e => setProductTitle(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<UploadBox
|
||||
label='تصویر'
|
||||
onChange={files => setProductImage(files[0])}
|
||||
isMultiple={false}
|
||||
isReset={!productImage && !productTitle}
|
||||
/>
|
||||
</div>
|
||||
<div onClick={handleAddProduct} className='size-10 min-w-10 flex items-center justify-center border-black rounded-xl border'>
|
||||
<Add size={22} color='black' />
|
||||
</div>
|
||||
</div>
|
||||
{productError && <div className='text-xs text-red-600 mt-2'>{productError}</div>}
|
||||
{/* نمایش لیست محصولات */}
|
||||
<div className='mt-4 flex gap-2'>
|
||||
{value.map((prd, idx) => (
|
||||
<div key={idx} className='rowTwoInput border p-2 rounded-xl items-center gap-2'>
|
||||
<div className='flex items-center gap-2'>
|
||||
{prd.image && <img src={URL.createObjectURL(prd.image)} alt='' className='size-6 min-w-6 rounded-full object-cover' />}
|
||||
{prd.existingImageUrl && !prd.image && <img src={prd.existingImageUrl} alt='' className='size-6 min-w-6 rounded-full object-cover' />}
|
||||
</div>
|
||||
<div className='w-full text-xs'>{prd.title}</div>
|
||||
<button type='button' onClick={() => handleRemoveProduct(idx)} className='ml-auto'>
|
||||
<CloseCircle size={16} color='red' />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CompanyProducts
|
||||
@@ -0,0 +1,85 @@
|
||||
import { FC, useState } from 'react'
|
||||
import Input from '../../../components/Input'
|
||||
import UploadBox from '../../../components/UploadBox'
|
||||
import { Add, CloseCircle } from 'iconsax-react'
|
||||
|
||||
type Service = { title: string; image?: File; existingImageUrl?: string }
|
||||
|
||||
type Props = {
|
||||
value: Service[]
|
||||
onChange: (val: Service[]) => void
|
||||
}
|
||||
|
||||
const CompanyServices: FC<Props> = ({ value, onChange }) => {
|
||||
const [serviceTitle, setServiceTitle] = useState('')
|
||||
const [serviceImage, setServiceImage] = useState<File | undefined>(undefined)
|
||||
const [serviceError, setServiceError] = useState('')
|
||||
|
||||
const handleAddService = () => {
|
||||
if (!serviceTitle.trim()) {
|
||||
setServiceError('عنوان خدمت الزامی است')
|
||||
return
|
||||
}
|
||||
if (!serviceImage) {
|
||||
setServiceError('تصویر خدمت الزامی است')
|
||||
return
|
||||
}
|
||||
if (value.some(s => s.title.trim() === serviceTitle.trim())) {
|
||||
setServiceError('عنوان خدمت تکراری است')
|
||||
return
|
||||
}
|
||||
onChange([...value, { title: serviceTitle.trim(), image: serviceImage }])
|
||||
setServiceTitle('')
|
||||
setServiceImage(undefined)
|
||||
setServiceError('')
|
||||
}
|
||||
|
||||
const handleRemoveService = (idx: number) => {
|
||||
onChange(value.filter((_, i) => i !== idx))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-8'>
|
||||
<div className='mb-2'>خدمات شرکت</div>
|
||||
<div className='rowTwoInput items-end gap-4'>
|
||||
<div className='w-full'>
|
||||
<Input
|
||||
label='عنوان خدمت'
|
||||
name='serviceTitle'
|
||||
value={serviceTitle}
|
||||
onChange={e => setServiceTitle(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<UploadBox
|
||||
label='تصویر'
|
||||
onChange={files => setServiceImage(files[0])}
|
||||
isMultiple={false}
|
||||
isReset={!serviceImage && !serviceTitle}
|
||||
/>
|
||||
</div>
|
||||
<div onClick={handleAddService} className='size-10 min-w-10 flex items-center justify-center border-black rounded-xl border'>
|
||||
<Add size={22} color='black' />
|
||||
</div>
|
||||
</div>
|
||||
{serviceError && <div className='text-xs text-red-600 mt-2'>{serviceError}</div>}
|
||||
{/* نمایش لیست خدمات */}
|
||||
<div className='mt-4 flex gap-2'>
|
||||
{value.map((srv, idx) => (
|
||||
<div key={idx} className='rowTwoInput border p-2 rounded-xl items-center gap-2'>
|
||||
<div className='flex items-center gap-2'>
|
||||
{srv.image && <img src={URL.createObjectURL(srv.image)} alt='' className='size-6 min-w-6 rounded-full object-cover' />}
|
||||
{srv.existingImageUrl && !srv.image && <img src={srv.existingImageUrl} alt='' className='size-6 min-w-6 rounded-full object-cover' />}
|
||||
</div>
|
||||
<div className='w-full text-xs'>{srv.title}</div>
|
||||
<button type='button' onClick={() => handleRemoveService(idx)} className='ml-auto'>
|
||||
<CloseCircle size={16} color='red' />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CompanyServices
|
||||
@@ -53,7 +53,7 @@ const CreateIndustry: FC<Props> = ({ refetch }) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='bg-white mt-10 w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl'>
|
||||
<div className='bg-white w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='text-sm'>
|
||||
وضعیت صنعت
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
import { FC } from 'react'
|
||||
import SwitchComponent from '../../../components/Switch'
|
||||
import Input from '../../../components/Input'
|
||||
import { useGetIndustries } from '../hooks/useCompanyData'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CheckBoxComponent from '../../../components/CheckBoxComponent'
|
||||
import { CreateCompanyType, IndustryItemType } from '../types/CompanyTypes'
|
||||
import { FormikProps } from 'formik'
|
||||
import UploadBoxDraggble from '../../../components/UploadBoxDraggble'
|
||||
|
||||
type Props = {
|
||||
formik: FormikProps<CreateCompanyType>
|
||||
setProfileImage: (image: File) => void
|
||||
setCoverImage: (image: File) => void
|
||||
}
|
||||
|
||||
const CreateSidebar: FC<Props> = ({ formik, setProfileImage, setCoverImage }) => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { data: industries } = useGetIndustries(1, '', '')
|
||||
|
||||
const CreateSidebar: FC = () => {
|
||||
return (
|
||||
<div className='bg-white mt-10 w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl'>
|
||||
<div className='flex justify-between items-center'>
|
||||
@@ -10,17 +26,54 @@ const CreateSidebar: FC = () => {
|
||||
وضعیت شرکت
|
||||
</div>
|
||||
<SwitchComponent
|
||||
active={true}
|
||||
onChange={() => { }}
|
||||
active={formik.values.isActive}
|
||||
onChange={(value) => formik.setFieldValue('isActive', value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label='آدرس وبسایت شرکت'
|
||||
name='name'
|
||||
onChange={() => { }}
|
||||
error_text=''
|
||||
name='websiteUrl'
|
||||
value={formik.values.websiteUrl}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.websiteUrl && formik.errors.websiteUrl ? formik.errors.websiteUrl : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<div className='text-sm'>{t('company.industry')}</div>
|
||||
<div className='mt-2 border border-border rounded-xl p-4'>
|
||||
{
|
||||
industries?.data?.industries?.map((item: IndustryItemType) => {
|
||||
return (
|
||||
<div key={item.id} className='flex items-center'>
|
||||
<CheckBoxComponent
|
||||
checked={formik.values.industryId === item.id}
|
||||
onChange={(e) => formik.setFieldValue('industryId', e.target.checked ? item.id : '')}
|
||||
/>
|
||||
<div>{item.title}</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<div className='text-sm mb-2'>{t('company.profile_image')}</div>
|
||||
<UploadBoxDraggble
|
||||
label={t('company.profile_image')}
|
||||
onChange={(files) => setProfileImage(files[0])}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<div className='text-sm mb-2'>{t('company.cover_image')}</div>
|
||||
<UploadBoxDraggble
|
||||
label={t('company.cover_image')}
|
||||
onChange={(files) => setCoverImage(files[0])}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Trash } from 'iconsax-react'
|
||||
import { FC, useState } from 'react'
|
||||
import { useDeleteCompany } from '../hooks/useCompanyData'
|
||||
import ModalConfrim from '../../../components/ModalConfrim'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
type Props = {
|
||||
id: string,
|
||||
refetch: () => void,
|
||||
}
|
||||
|
||||
const DeleteCompany: FC<Props> = ({ id, refetch }) => {
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
||||
const deleteCompany = useDeleteCompany()
|
||||
|
||||
const handleConfrim = () => {
|
||||
deleteCompany.mutate(id, {
|
||||
onSuccess: () => {
|
||||
refetch()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Trash onClick={() => setIsOpen(true)} size={20} color='#888' />
|
||||
|
||||
<ModalConfrim
|
||||
close={() => setIsOpen(false)}
|
||||
isOpen={isOpen}
|
||||
onConfrim={handleConfrim}
|
||||
isLoading={deleteCompany.isPending}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeleteCompany
|
||||
@@ -32,7 +32,7 @@ const Industries: FC = () => {
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div>
|
||||
{t('service.cateory_services')}
|
||||
{t('company.industries')}
|
||||
</div>
|
||||
|
||||
<div className='flex gap-6 mt-9'>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { FC, useState } from 'react'
|
||||
import SwitchComponent from '../../../components/Switch'
|
||||
import { useToggleStatusCompany } from '../hooks/useCompanyData'
|
||||
|
||||
type Props = {
|
||||
defaultActive: boolean,
|
||||
id: string
|
||||
}
|
||||
|
||||
const ToggleStatusCompany: FC<Props> = (props: Props) => {
|
||||
|
||||
const toggleStatus = useToggleStatusCompany()
|
||||
const [isActive, setIsActive] = useState<boolean>(props.defaultActive)
|
||||
|
||||
const handleChange = (value: boolean) => {
|
||||
setIsActive(value)
|
||||
toggleStatus.mutate(props.id)
|
||||
}
|
||||
|
||||
return (
|
||||
<SwitchComponent
|
||||
active={isActive}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ToggleStatusCompany
|
||||
@@ -0,0 +1,83 @@
|
||||
import { FC } from 'react'
|
||||
import SwitchComponent from '../../../components/Switch'
|
||||
import Input from '../../../components/Input'
|
||||
import { useGetIndustries } from '../hooks/useCompanyData'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CheckBoxComponent from '../../../components/CheckBoxComponent'
|
||||
import { CreateCompanyType, IndustryItemType } from '../types/CompanyTypes'
|
||||
import { FormikProps } from 'formik'
|
||||
import UploadBoxDraggble from '../../../components/UploadBoxDraggble'
|
||||
|
||||
type Props = {
|
||||
formik: FormikProps<CreateCompanyType>
|
||||
setProfileImage: (image: File) => void
|
||||
setCoverImage: (image: File) => void
|
||||
}
|
||||
|
||||
const UpdateSidebar: FC<Props> = ({ formik, setProfileImage, setCoverImage }) => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { data: industries } = useGetIndustries(1, '', '')
|
||||
|
||||
return (
|
||||
<div className='bg-white mt-10 w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='text-sm'>
|
||||
وضعیت شرکت
|
||||
</div>
|
||||
<SwitchComponent
|
||||
active={formik.values.isActive}
|
||||
onChange={(value) => formik.setFieldValue('isActive', value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label='آدرس وبسایت شرکت'
|
||||
name='websiteUrl'
|
||||
value={formik.values.websiteUrl}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.websiteUrl && formik.errors.websiteUrl ? formik.errors.websiteUrl : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<div className='text-sm'>{t('company.industry')}</div>
|
||||
<div className='mt-2 border border-border rounded-xl p-4'>
|
||||
{
|
||||
industries?.data?.industries?.map((item: IndustryItemType) => {
|
||||
return (
|
||||
<div key={item.id} className='flex items-center'>
|
||||
<CheckBoxComponent
|
||||
checked={formik.values.industryId === item.id}
|
||||
onChange={(e) => formik.setFieldValue('industryId', e.target.checked ? item.id : '')}
|
||||
/>
|
||||
<div>{item.title}</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<div className='text-sm mb-2'>{t('company.profile_image')}</div>
|
||||
<UploadBoxDraggble
|
||||
label={t('company.profile_image')}
|
||||
onChange={(files) => setProfileImage(files[0])}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<div className='text-sm mb-2'>{t('company.cover_image')}</div>
|
||||
<UploadBoxDraggble
|
||||
label={t('company.cover_image')}
|
||||
onChange={(files) => setCoverImage(files[0])}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateSidebar
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/CompanyService";
|
||||
import { CreateIndustryType } from "../types/CompanyTypes";
|
||||
import { CreateIndustryType, CreateCompanyType } from "../types/CompanyTypes";
|
||||
export const useGetIndustries = (
|
||||
page: number,
|
||||
search: string,
|
||||
@@ -43,3 +43,43 @@ export const useGetIndustryDetail = (id: string) => {
|
||||
queryFn: () => api.getIndustryDetail(id),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateCompany = () => {
|
||||
return useMutation({
|
||||
mutationFn: (params: CreateCompanyType) => api.createCompany(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetCompanies = (page: number) => {
|
||||
return useQuery({
|
||||
queryKey: ["companies", page],
|
||||
queryFn: () => api.getCompanies(page),
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteCompany = () => {
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.deleteCompany(id),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetCompanyDetail = (id?: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["company-detail", id],
|
||||
queryFn: () => api.getCompanyDetail(id || ""),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateCompany = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ id, params }: { id: string; params: CreateCompanyType }) =>
|
||||
api.updateCompany(id, params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useToggleStatusCompany = () => {
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.toggleStatusCompany(id),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from "../../../config/axios";
|
||||
import { CreateIndustryType } from "../types/CompanyTypes";
|
||||
import { CreateCompanyType, CreateIndustryType } from "../types/CompanyTypes";
|
||||
|
||||
export const getIndustries = async (
|
||||
page: number,
|
||||
@@ -41,3 +41,33 @@ export const getIndustryDetail = async (id: string) => {
|
||||
const { data } = await axios.get(`/industries/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createCompany = async (params: CreateCompanyType) => {
|
||||
const { data } = await axios.post("/companies", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getCompanies = async (page: number) => {
|
||||
const { data } = await axios.get(`/companies/list?page=${page}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteCompany = async (id: string) => {
|
||||
const { data } = await axios.delete(`/companies/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getCompanyDetail = async (id: string) => {
|
||||
const { data } = await axios.get(`/companies/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateCompany = async (id: string, params: CreateCompanyType) => {
|
||||
const { data } = await axios.patch(`/companies/${id}`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const toggleStatusCompany = async (id: string) => {
|
||||
const { data } = await axios.patch(`/companies/${id}/toggle-status`);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -13,9 +13,12 @@ export type IndustryItemType = {
|
||||
};
|
||||
|
||||
export type CreateCompanyType = {
|
||||
name: string;
|
||||
chiefExecutive: string;
|
||||
phone: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
nationalCode: string;
|
||||
password: string;
|
||||
name: string;
|
||||
email: string;
|
||||
identificationNumber: string;
|
||||
dateOfEstablishment: string;
|
||||
@@ -27,4 +30,30 @@ export type CreateCompanyType = {
|
||||
coverImageUrl: string;
|
||||
isActive: boolean;
|
||||
industryId: string;
|
||||
products: {
|
||||
title: string;
|
||||
imageUrl: string;
|
||||
}[];
|
||||
services: {
|
||||
title: string;
|
||||
imageUrl: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type CompanyItemType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
status: string;
|
||||
industry: {
|
||||
id: string;
|
||||
title: string;
|
||||
};
|
||||
user: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
invoiceCount: number;
|
||||
};
|
||||
|
||||
+6
-2
@@ -71,12 +71,14 @@ import UpdateSupport from '../pages/support/Update'
|
||||
import PlanUsers from '../pages/support/PlanUsers'
|
||||
import CreateCompany from '../pages/company/Create'
|
||||
import Industries from '../pages/company/components/Industries'
|
||||
import CompanyList from '../pages/company/List'
|
||||
import UpdateCompany from '../pages/company/Update'
|
||||
const MainRouter: FC = () => {
|
||||
|
||||
const { hasSubMenu } = useSharedStore()
|
||||
|
||||
return (
|
||||
<div className='p-4 overflow-hidden'>
|
||||
<div className='p-4 overflow-hidden h-screen'>
|
||||
<SideBar />
|
||||
<Header />
|
||||
<div className={clx(
|
||||
@@ -84,7 +86,7 @@ const MainRouter: FC = () => {
|
||||
hasSubMenu && 'xl:ms-[305px]',
|
||||
)}>
|
||||
<div className={clx(
|
||||
`overflow-auto w-[${window.innerWidth}] max-h-[calc(100vh-113px)]`,
|
||||
`overflow-auto h-[calc(100vh-113px)] main-content-scroll`,
|
||||
)}>
|
||||
<div>
|
||||
<Routes>
|
||||
@@ -153,6 +155,8 @@ const MainRouter: FC = () => {
|
||||
<Route path={Pages.support.planUsers + ':id'} element={<PlanUsers />} />
|
||||
<Route path={Pages.company.create} element={<CreateCompany />} />
|
||||
<Route path={Pages.company.industries} element={<Industries />} />
|
||||
<Route path={Pages.company.list} element={<CompanyList />} />
|
||||
<Route path={Pages.company.detail + ':id'} element={<UpdateCompany />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+11
-8
@@ -2,7 +2,7 @@ import { FC, useEffect } from 'react'
|
||||
import LogoImage from '../assets/images/logo.svg'
|
||||
import LogoSmall from '../assets/images/logo-small.svg'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Card, Code, CodeCircle, DocumentLike, DocumentText, Element3, Gallery, Headphone, Home2, Logout, Messages3, Money3, NotificationStatus, People, Profile, Receipt21, Setting2, SmsTracking, Teacher, TicketDiscount, UserSquare } from 'iconsax-react'
|
||||
import { Building, Card, Code, CodeCircle, DocumentLike, DocumentText, Gallery, Headphone, Home2, Logout, Messages3, Money3, NotificationStatus, People, Profile, Receipt21, Setting2, SmsTracking, Teacher, TicketDiscount, UserSquare } from 'iconsax-react'
|
||||
import SideBarItem from './SideBarItem'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Pages } from '../config/Pages'
|
||||
@@ -18,6 +18,7 @@ import LearningSubMenu from './components/LearningSubMenu'
|
||||
import BlogSubMenu from './components/BlogSubMenu'
|
||||
import { useGetProfile } from '../pages/profile/hooks/useProfileData'
|
||||
import SupportSubMenu from './components/SupportSubMenu'
|
||||
import CompanySubMenu from './components/CompanySubMenu'
|
||||
const SideBar: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
@@ -86,13 +87,13 @@ const SideBar: FC = () => {
|
||||
link={Pages.dashboard}
|
||||
activeName=''
|
||||
/>
|
||||
|
||||
<SideBarItem
|
||||
icon={<Element3 variant={isActive('services') ? 'Bold' : 'Outline'} color={isActive('services') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.services')}
|
||||
isActive={isActive('services')}
|
||||
link={Pages.services.list}
|
||||
name='services'
|
||||
activeName='services'
|
||||
icon={<Building variant={isActive('company') ? 'Bold' : 'Outline'} color={isActive('company') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.company')}
|
||||
isActive={isActive('company')}
|
||||
link={Pages.company.list}
|
||||
activeName='company'
|
||||
/>
|
||||
|
||||
<SideBarItem
|
||||
@@ -327,7 +328,9 @@ const SideBar: FC = () => {
|
||||
<BlogSubMenu />
|
||||
: subMenuName === 'support' ?
|
||||
<SupportSubMenu />
|
||||
: null
|
||||
: subMenuName === 'company' ?
|
||||
<CompanySubMenu />
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Building } from 'iconsax-react'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SubMenuItem from './SubMenuItem'
|
||||
import { Pages } from '../../config/Pages'
|
||||
|
||||
const CompanySubMenu: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
|
||||
const isActive = (name: string) => {
|
||||
return location.pathname.includes(name)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='py-12'>
|
||||
<div className='flex gap-3 items-center border-b pb-10 px-6'>
|
||||
<Building
|
||||
size={24}
|
||||
color='#000'
|
||||
variant='Bold'
|
||||
/>
|
||||
<div className='text-xs'>
|
||||
{t('sidebar.company')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col mt-10 gap-4'>
|
||||
<SubMenuItem
|
||||
title={'لیست'}
|
||||
isActive={isActive('list')}
|
||||
link={Pages.company.list}
|
||||
/>
|
||||
<SubMenuItem
|
||||
title={'ایجاد'}
|
||||
isActive={isActive('create')}
|
||||
link={Pages.company.create}
|
||||
/>
|
||||
<SubMenuItem
|
||||
title={'صنعت'}
|
||||
isActive={isActive('industries')}
|
||||
link={Pages.company.industries}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CompanySubMenu
|
||||
Reference in New Issue
Block a user