crud company
This commit is contained in:
@@ -115,5 +115,7 @@ export const Pages = {
|
|||||||
company: {
|
company: {
|
||||||
create: "/company/create",
|
create: "/company/create",
|
||||||
industries: "/company/industries",
|
industries: "/company/industries",
|
||||||
|
list: "/company/list",
|
||||||
|
detail: "/company/detail/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ export const SideBarItemHasSubMenu = [
|
|||||||
"learning",
|
"learning",
|
||||||
"blog",
|
"blog",
|
||||||
"support",
|
"support",
|
||||||
|
"company",
|
||||||
];
|
];
|
||||||
|
|||||||
+25
-14
@@ -25,6 +25,17 @@ body,
|
|||||||
}
|
}
|
||||||
* {
|
* {
|
||||||
outline: none;
|
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,
|
input::placeholder,
|
||||||
textarea::placeholder {
|
textarea::placeholder {
|
||||||
@@ -135,9 +146,14 @@ tbody tr {
|
|||||||
@apply rounded-b-xl text-right;
|
@apply rounded-b-xl text-right;
|
||||||
font-family: irancell !important;
|
font-family: irancell !important;
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
|
min-height: 200px !important;
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.ql-toolbar {
|
.ql-toolbar {
|
||||||
@apply rounded-t-xl;
|
@apply rounded-t-xl;
|
||||||
|
position: relative !important;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
.ql-editor {
|
.ql-editor {
|
||||||
@apply !text-right;
|
@apply !text-right;
|
||||||
@@ -151,22 +167,17 @@ tbody tr {
|
|||||||
font-family: "irancell" !important;
|
font-family: "irancell" !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
.main-content-scroll {
|
||||||
scrollbar-width: none;
|
scrollbar-width: auto !important;
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar {
|
.main-content-scroll::-webkit-scrollbar {
|
||||||
display: none;
|
display: block !important;
|
||||||
|
width: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ql-container {
|
.main-content-scroll::-webkit-scrollbar-thumb {
|
||||||
min-height: 200px !important;
|
background-color: #c9c9c9 !important;
|
||||||
max-height: 500px;
|
border-radius: 5px !important;
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ql-toolbar {
|
|
||||||
position: relative !important;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-2
@@ -71,7 +71,8 @@
|
|||||||
"card": "حساب های بانکی",
|
"card": "حساب های بانکی",
|
||||||
"payments": "پرداخت ها",
|
"payments": "پرداخت ها",
|
||||||
"sliders": "اسلایدر",
|
"sliders": "اسلایدر",
|
||||||
"support": "پلن پشتیبانی"
|
"support": "پلن پشتیبانی",
|
||||||
|
"company": "شرکت ها"
|
||||||
},
|
},
|
||||||
"slider": {
|
"slider": {
|
||||||
"new_slider": "اسلایدر جدید",
|
"new_slider": "اسلایدر جدید",
|
||||||
@@ -808,6 +809,7 @@
|
|||||||
"company": {
|
"company": {
|
||||||
"count_company": "تعداد شرکت ها",
|
"count_company": "تعداد شرکت ها",
|
||||||
"company_name": "نام شرکت",
|
"company_name": "نام شرکت",
|
||||||
|
"ceo_name": "نام مدیر عامل",
|
||||||
"ceo_first_name": "نام مدیر عامل",
|
"ceo_first_name": "نام مدیر عامل",
|
||||||
"ceo_last_name": "نام خانوادگی مدیر عامل",
|
"ceo_last_name": "نام خانوادگی مدیر عامل",
|
||||||
"phone_number": "شماره تماس",
|
"phone_number": "شماره تماس",
|
||||||
@@ -816,6 +818,24 @@
|
|||||||
"establishment_date": "تاریخ تاسیس",
|
"establishment_date": "تاریخ تاسیس",
|
||||||
"address": "آدرس",
|
"address": "آدرس",
|
||||||
"map_link": "لینک نقشه شرکت",
|
"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": "تعداد صورتحساب"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+168
-54
@@ -6,39 +6,128 @@ import CreateSidebar from './components/CreateSidebar'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
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 CreateCompany: FC = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
const { t } = useTranslation('global')
|
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: {
|
initialValues: {
|
||||||
|
phone: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
nationalCode: '',
|
||||||
|
password: '',
|
||||||
name: '',
|
name: '',
|
||||||
ceoFirstName: '',
|
|
||||||
ceoLastName: '',
|
|
||||||
phoneNumber: '',
|
|
||||||
email: '',
|
email: '',
|
||||||
nationalId: '',
|
identificationNumber: '',
|
||||||
establishmentDate: '',
|
dateOfEstablishment: '',
|
||||||
address: '',
|
address: '',
|
||||||
mapLink: '',
|
mapAddressLink: '',
|
||||||
shortDescription: ''
|
description: '',
|
||||||
|
websiteUrl: '',
|
||||||
|
profileImageUrl: '',
|
||||||
|
coverImageUrl: '',
|
||||||
|
isActive: true,
|
||||||
|
industryId: '',
|
||||||
|
products: [],
|
||||||
|
services: [],
|
||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
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')),
|
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')),
|
email: Yup.string().email(t('errors.invalid_email')).required(t('errors.required')),
|
||||||
nationalId: Yup.string().required(t('errors.required')),
|
identificationNumber: Yup.string().required(t('errors.required')),
|
||||||
establishmentDate: Yup.string().required(t('errors.required')),
|
dateOfEstablishment: Yup.string().required(t('errors.required')),
|
||||||
address: Yup.string().required(t('errors.required')),
|
address: Yup.string().required(t('errors.required')),
|
||||||
mapLink: Yup.string().url(t('errors.invalid_url')),
|
mapAddressLink: Yup.string().url(t('errors.invalid_url')),
|
||||||
shortDescription: Yup.string().required(t('errors.required'))
|
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) => {
|
onSubmit: async (values) => {
|
||||||
console.log(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 (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
@@ -50,6 +139,7 @@ const CreateCompany: FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
className='w-fit px-4'
|
className='w-fit px-4'
|
||||||
onClick={() => formik.handleSubmit()}
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={createCompany.isPending || singleUpload.isPending || multiUpload.isPending}
|
||||||
>
|
>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<TickCircle size={20} color='white' />
|
<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 className='flex-1 mt-10 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||||||
<div>اطلاعات شرکت</div>
|
<div>اطلاعات شرکت</div>
|
||||||
|
|
||||||
<div className='rowTwoInput mt-6'>
|
<div className='mt-6'>
|
||||||
<Input
|
<Input
|
||||||
label={t('company.company_name')}
|
label='نام شرکت'
|
||||||
name='name'
|
name='name'
|
||||||
value={formik.values.name}
|
value={formik.values.name}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
error_text={formik.touched.name && formik.errors.name ? formik.errors.name : ''}
|
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>
|
||||||
|
|
||||||
<div className='rowTwoInput mt-6'>
|
<div className='rowTwoInput mt-6'>
|
||||||
<Input
|
<Input
|
||||||
label={t('company.ceo_last_name')}
|
label='نام'
|
||||||
name='ceoLastName'
|
name='firstName'
|
||||||
value={formik.values.ceoLastName}
|
value={formik.values.firstName}
|
||||||
onChange={formik.handleChange}
|
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
|
<Input
|
||||||
label={t('company.phone_number')}
|
label={t('company.phone_number')}
|
||||||
name='phoneNumber'
|
name='phone'
|
||||||
value={formik.values.phoneNumber}
|
value={formik.values.phone}
|
||||||
onChange={formik.handleChange}
|
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
|
<Input
|
||||||
label={t('company.email')}
|
label={t('company.email')}
|
||||||
name='email'
|
name='email'
|
||||||
@@ -105,29 +211,32 @@ const CreateCompany: FC = () => {
|
|||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
error_text={formik.touched.email && formik.errors.email ? formik.errors.email : ''}
|
error_text={formik.touched.email && formik.errors.email ? formik.errors.email : ''}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='rowTwoInput mt-6'>
|
||||||
<Input
|
<Input
|
||||||
label={t('company.national_id')}
|
label={t('company.national_id')}
|
||||||
name='nationalId'
|
name='identificationNumber'
|
||||||
value={formik.values.nationalId}
|
value={formik.values.identificationNumber}
|
||||||
onChange={formik.handleChange}
|
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>
|
||||||
|
|
||||||
<div className='rowTwoInput mt-6'>
|
<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
|
<Input
|
||||||
label={t('company.map_link')}
|
label={t('company.map_link')}
|
||||||
name='mapLink'
|
name='mapAddressLink'
|
||||||
value={formik.values.mapLink}
|
value={formik.values.mapAddressLink}
|
||||||
onChange={formik.handleChange}
|
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>
|
</div>
|
||||||
|
|
||||||
@@ -144,16 +253,21 @@ const CreateCompany: FC = () => {
|
|||||||
<div className='mt-6'>
|
<div className='mt-6'>
|
||||||
<Input
|
<Input
|
||||||
label={t('company.short_description')}
|
label={t('company.short_description')}
|
||||||
name='shortDescription'
|
name='description'
|
||||||
value={formik.values.shortDescription}
|
value={formik.values.description}
|
||||||
onChange={formik.handleChange}
|
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>
|
</div>
|
||||||
</div>
|
|
||||||
|
<CompanyServices value={services} onChange={setServices} />
|
||||||
|
<CompanyProducts value={products} onChange={setProducts} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CreateSidebar />
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CreateSidebar formik={formik} setProfileImage={setProfileImage} setCoverImage={setCoverImage} />
|
||||||
</div>
|
</div>
|
||||||
</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 (
|
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='flex justify-between items-center'>
|
||||||
<div className='text-sm'>
|
<div className='text-sm'>
|
||||||
وضعیت صنعت
|
وضعیت صنعت
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import SwitchComponent from '../../../components/Switch'
|
import SwitchComponent from '../../../components/Switch'
|
||||||
import Input from '../../../components/Input'
|
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 (
|
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 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='flex justify-between items-center'>
|
||||||
@@ -10,17 +26,54 @@ const CreateSidebar: FC = () => {
|
|||||||
وضعیت شرکت
|
وضعیت شرکت
|
||||||
</div>
|
</div>
|
||||||
<SwitchComponent
|
<SwitchComponent
|
||||||
active={true}
|
active={formik.values.isActive}
|
||||||
onChange={() => { }}
|
onChange={(value) => formik.setFieldValue('isActive', value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-6'>
|
<div className='mt-6'>
|
||||||
<Input
|
<Input
|
||||||
label='آدرس وبسایت شرکت'
|
label='آدرس وبسایت شرکت'
|
||||||
name='name'
|
name='websiteUrl'
|
||||||
onChange={() => { }}
|
value={formik.values.websiteUrl}
|
||||||
error_text=''
|
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>
|
||||||
</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 (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
<div>
|
<div>
|
||||||
{t('service.cateory_services')}
|
{t('company.industries')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex gap-6 mt-9'>
|
<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 { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import * as api from "../service/CompanyService";
|
import * as api from "../service/CompanyService";
|
||||||
import { CreateIndustryType } from "../types/CompanyTypes";
|
import { CreateIndustryType, CreateCompanyType } from "../types/CompanyTypes";
|
||||||
export const useGetIndustries = (
|
export const useGetIndustries = (
|
||||||
page: number,
|
page: number,
|
||||||
search: string,
|
search: string,
|
||||||
@@ -43,3 +43,43 @@ export const useGetIndustryDetail = (id: string) => {
|
|||||||
queryFn: () => api.getIndustryDetail(id),
|
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 axios from "../../../config/axios";
|
||||||
import { CreateIndustryType } from "../types/CompanyTypes";
|
import { CreateCompanyType, CreateIndustryType } from "../types/CompanyTypes";
|
||||||
|
|
||||||
export const getIndustries = async (
|
export const getIndustries = async (
|
||||||
page: number,
|
page: number,
|
||||||
@@ -41,3 +41,33 @@ export const getIndustryDetail = async (id: string) => {
|
|||||||
const { data } = await axios.get(`/industries/${id}`);
|
const { data } = await axios.get(`/industries/${id}`);
|
||||||
return data;
|
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 = {
|
export type CreateCompanyType = {
|
||||||
name: string;
|
|
||||||
chiefExecutive: string;
|
|
||||||
phone: string;
|
phone: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
nationalCode: string;
|
||||||
|
password: string;
|
||||||
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
identificationNumber: string;
|
identificationNumber: string;
|
||||||
dateOfEstablishment: string;
|
dateOfEstablishment: string;
|
||||||
@@ -27,4 +30,30 @@ export type CreateCompanyType = {
|
|||||||
coverImageUrl: string;
|
coverImageUrl: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
industryId: string;
|
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 PlanUsers from '../pages/support/PlanUsers'
|
||||||
import CreateCompany from '../pages/company/Create'
|
import CreateCompany from '../pages/company/Create'
|
||||||
import Industries from '../pages/company/components/Industries'
|
import Industries from '../pages/company/components/Industries'
|
||||||
|
import CompanyList from '../pages/company/List'
|
||||||
|
import UpdateCompany from '../pages/company/Update'
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
const { hasSubMenu } = useSharedStore()
|
const { hasSubMenu } = useSharedStore()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='p-4 overflow-hidden'>
|
<div className='p-4 overflow-hidden h-screen'>
|
||||||
<SideBar />
|
<SideBar />
|
||||||
<Header />
|
<Header />
|
||||||
<div className={clx(
|
<div className={clx(
|
||||||
@@ -84,7 +86,7 @@ const MainRouter: FC = () => {
|
|||||||
hasSubMenu && 'xl:ms-[305px]',
|
hasSubMenu && 'xl:ms-[305px]',
|
||||||
)}>
|
)}>
|
||||||
<div className={clx(
|
<div className={clx(
|
||||||
`overflow-auto w-[${window.innerWidth}] max-h-[calc(100vh-113px)]`,
|
`overflow-auto h-[calc(100vh-113px)] main-content-scroll`,
|
||||||
)}>
|
)}>
|
||||||
<div>
|
<div>
|
||||||
<Routes>
|
<Routes>
|
||||||
@@ -153,6 +155,8 @@ const MainRouter: FC = () => {
|
|||||||
<Route path={Pages.support.planUsers + ':id'} element={<PlanUsers />} />
|
<Route path={Pages.support.planUsers + ':id'} element={<PlanUsers />} />
|
||||||
<Route path={Pages.company.create} element={<CreateCompany />} />
|
<Route path={Pages.company.create} element={<CreateCompany />} />
|
||||||
<Route path={Pages.company.industries} element={<Industries />} />
|
<Route path={Pages.company.industries} element={<Industries />} />
|
||||||
|
<Route path={Pages.company.list} element={<CompanyList />} />
|
||||||
|
<Route path={Pages.company.detail + ':id'} element={<UpdateCompany />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+10
-7
@@ -2,7 +2,7 @@ import { FC, useEffect } from 'react'
|
|||||||
import LogoImage from '../assets/images/logo.svg'
|
import LogoImage from '../assets/images/logo.svg'
|
||||||
import LogoSmall from '../assets/images/logo-small.svg'
|
import LogoSmall from '../assets/images/logo-small.svg'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 SideBarItem from './SideBarItem'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { Pages } from '../config/Pages'
|
import { Pages } from '../config/Pages'
|
||||||
@@ -18,6 +18,7 @@ import LearningSubMenu from './components/LearningSubMenu'
|
|||||||
import BlogSubMenu from './components/BlogSubMenu'
|
import BlogSubMenu from './components/BlogSubMenu'
|
||||||
import { useGetProfile } from '../pages/profile/hooks/useProfileData'
|
import { useGetProfile } from '../pages/profile/hooks/useProfileData'
|
||||||
import SupportSubMenu from './components/SupportSubMenu'
|
import SupportSubMenu from './components/SupportSubMenu'
|
||||||
|
import CompanySubMenu from './components/CompanySubMenu'
|
||||||
const SideBar: FC = () => {
|
const SideBar: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
@@ -86,13 +87,13 @@ const SideBar: FC = () => {
|
|||||||
link={Pages.dashboard}
|
link={Pages.dashboard}
|
||||||
activeName=''
|
activeName=''
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
icon={<Element3 variant={isActive('services') ? 'Bold' : 'Outline'} color={isActive('services') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
icon={<Building variant={isActive('company') ? 'Bold' : 'Outline'} color={isActive('company') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||||
title={t('sidebar.services')}
|
title={t('sidebar.company')}
|
||||||
isActive={isActive('services')}
|
isActive={isActive('company')}
|
||||||
link={Pages.services.list}
|
link={Pages.company.list}
|
||||||
name='services'
|
activeName='company'
|
||||||
activeName='services'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
@@ -327,6 +328,8 @@ const SideBar: FC = () => {
|
|||||||
<BlogSubMenu />
|
<BlogSubMenu />
|
||||||
: subMenuName === 'support' ?
|
: subMenuName === 'support' ?
|
||||||
<SupportSubMenu />
|
<SupportSubMenu />
|
||||||
|
: subMenuName === 'company' ?
|
||||||
|
<CompanySubMenu />
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</div>
|
</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