diff --git a/src/config/Pages.ts b/src/config/Pages.ts index 63d51f3..4aa3b42 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -115,5 +115,7 @@ export const Pages = { company: { create: "/company/create", industries: "/company/industries", + list: "/company/list", + detail: "/company/detail/", }, }; diff --git a/src/config/SideBarSubMenu.ts b/src/config/SideBarSubMenu.ts index e3e9467..abef8ac 100644 --- a/src/config/SideBarSubMenu.ts +++ b/src/config/SideBarSubMenu.ts @@ -7,4 +7,5 @@ export const SideBarItemHasSubMenu = [ "learning", "blog", "support", + "company", ]; diff --git a/src/index.css b/src/index.css index 333801a..dec5e0f 100644 --- a/src/index.css +++ b/src/index.css @@ -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; } diff --git a/src/langs/fa.json b/src/langs/fa.json index e88a6bf..6a4606f 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -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": "تعداد صورتحساب" } } diff --git a/src/pages/company/Create.tsx b/src/pages/company/Create.tsx index 32734c0..a6dbcac 100644 --- a/src/pages/company/Create.tsx +++ b/src/pages/company/Create.tsx @@ -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(null) + const [coverImage, setCoverImage] = useState(null) + const multiUpload = useMultiUpload() + const singleUpload = useSingleUpload() + const createCompany = useCreateCompany() - const formik = useFormik({ + const formik = useFormik({ 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 (
@@ -50,6 +139,7 @@ const CreateCompany: FC = () => {
) diff --git a/src/pages/company/List.tsx b/src/pages/company/List.tsx new file mode 100644 index 0000000..c662f98 --- /dev/null +++ b/src/pages/company/List.tsx @@ -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 ( +
+ { + isLoading ? + + : +
+ + + + + + + { + data?.data?.companies?.map((item: CompanyItemType) => { + return + + + + }) + } + +
+ + + + + + +
+ + + + + + + +
+ + + + refetch()} id={item.id} /> +
+
+
+ } +
+ ) +} + +export default CompanyList \ No newline at end of file diff --git a/src/pages/company/Update.tsx b/src/pages/company/Update.tsx new file mode 100644 index 0000000..83d27fb --- /dev/null +++ b/src/pages/company/Update.tsx @@ -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(null) + const [coverImage, setCoverImage] = useState(null) + const multiUpload = useMultiUpload() + const singleUpload = useSingleUpload() + const updateCompany = useUpdateCompany() + const { data: companyDetail } = useGetCompanyDetail(id) + + const formik = useFormik({ + 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 ( +
+
+
+ ویرایش شرکت +
+ + +
+ +
+
+
+
اطلاعات شرکت
+ +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ + + formik.setFieldValue('dateOfEstablishment', moment(date).format('YYYY-MM-DD'))} + error_text={formik.touched.dateOfEstablishment && formik.errors.dateOfEstablishment ? formik.errors.dateOfEstablishment : ''} + defaulValue={formik.values.dateOfEstablishment} + /> +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + +
+ + +
+ + +
+
+ ) +} + +export default UpdateCompany \ No newline at end of file diff --git a/src/pages/company/components/CompanyProducts.tsx b/src/pages/company/components/CompanyProducts.tsx new file mode 100644 index 0000000..479f31a --- /dev/null +++ b/src/pages/company/components/CompanyProducts.tsx @@ -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 = ({ value, onChange }) => { + const [productTitle, setProductTitle] = useState('') + const [productImage, setProductImage] = useState(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 ( +
+
محصولات شرکت
+
+
+ setProductTitle(e.target.value)} + /> +
+
+ setProductImage(files[0])} + isMultiple={false} + isReset={!productImage && !productTitle} + /> +
+
+ +
+
+ {productError &&
{productError}
} + {/* نمایش لیست محصولات */} +
+ {value.map((prd, idx) => ( +
+
+ {prd.image && } + {prd.existingImageUrl && !prd.image && } +
+
{prd.title}
+ +
+ ))} +
+
+ ) +} + +export default CompanyProducts \ No newline at end of file diff --git a/src/pages/company/components/CompanyServices.tsx b/src/pages/company/components/CompanyServices.tsx new file mode 100644 index 0000000..0a622ac --- /dev/null +++ b/src/pages/company/components/CompanyServices.tsx @@ -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 = ({ value, onChange }) => { + const [serviceTitle, setServiceTitle] = useState('') + const [serviceImage, setServiceImage] = useState(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 ( +
+
خدمات شرکت
+
+
+ setServiceTitle(e.target.value)} + /> +
+
+ setServiceImage(files[0])} + isMultiple={false} + isReset={!serviceImage && !serviceTitle} + /> +
+
+ +
+
+ {serviceError &&
{serviceError}
} + {/* نمایش لیست خدمات */} +
+ {value.map((srv, idx) => ( +
+
+ {srv.image && } + {srv.existingImageUrl && !srv.image && } +
+
{srv.title}
+ +
+ ))} +
+
+ ) +} + +export default CompanyServices \ No newline at end of file diff --git a/src/pages/company/components/CreateIndustry.tsx b/src/pages/company/components/CreateIndustry.tsx index 3ac2fc5..556aeb9 100644 --- a/src/pages/company/components/CreateIndustry.tsx +++ b/src/pages/company/components/CreateIndustry.tsx @@ -53,7 +53,7 @@ const CreateIndustry: FC = ({ refetch }) => { }) return ( -
+
وضعیت صنعت diff --git a/src/pages/company/components/CreateSidebar.tsx b/src/pages/company/components/CreateSidebar.tsx index b72901f..41e6ba4 100644 --- a/src/pages/company/components/CreateSidebar.tsx +++ b/src/pages/company/components/CreateSidebar.tsx @@ -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 + setProfileImage: (image: File) => void + setCoverImage: (image: File) => void +} + +const CreateSidebar: FC = ({ formik, setProfileImage, setCoverImage }) => { + + const { t } = useTranslation('global') + const { data: industries } = useGetIndustries(1, '', '') -const CreateSidebar: FC = () => { return (
@@ -10,17 +26,54 @@ const CreateSidebar: FC = () => { وضعیت شرکت
{ }} + active={formik.values.isActive} + onChange={(value) => formik.setFieldValue('isActive', value)} />
{ }} - error_text='' + name='websiteUrl' + value={formik.values.websiteUrl} + onChange={formik.handleChange} + error_text={formik.touched.websiteUrl && formik.errors.websiteUrl ? formik.errors.websiteUrl : ''} + /> +
+ +
+
{t('company.industry')}
+
+ { + industries?.data?.industries?.map((item: IndustryItemType) => { + return ( +
+ formik.setFieldValue('industryId', e.target.checked ? item.id : '')} + /> +
{item.title}
+
+ ) + }) + } + +
+
+ +
+
{t('company.profile_image')}
+ setProfileImage(files[0])} + /> +
+ +
+
{t('company.cover_image')}
+ setCoverImage(files[0])} />
diff --git a/src/pages/company/components/DeleteCompany.tsx b/src/pages/company/components/DeleteCompany.tsx new file mode 100644 index 0000000..51be937 --- /dev/null +++ b/src/pages/company/components/DeleteCompany.tsx @@ -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 = ({ id, refetch }) => { + + const [isOpen, setIsOpen] = useState(false) + const deleteCompany = useDeleteCompany() + + const handleConfrim = () => { + deleteCompany.mutate(id, { + onSuccess: () => { + refetch() + }, + onError: (error: ErrorType) => { + toast.error(error.response?.data?.error.message[0]) + } + }) + } + + return ( +
+ setIsOpen(true)} size={20} color='#888' /> + + setIsOpen(false)} + isOpen={isOpen} + onConfrim={handleConfrim} + isLoading={deleteCompany.isPending} + /> +
+ ) +} + +export default DeleteCompany \ No newline at end of file diff --git a/src/pages/company/components/Industries.tsx b/src/pages/company/components/Industries.tsx index 339c834..e0478c1 100644 --- a/src/pages/company/components/Industries.tsx +++ b/src/pages/company/components/Industries.tsx @@ -32,7 +32,7 @@ const Industries: FC = () => { return (
- {t('service.cateory_services')} + {t('company.industries')}
diff --git a/src/pages/company/components/ToggleStatus.tsx b/src/pages/company/components/ToggleStatus.tsx new file mode 100644 index 0000000..3db2546 --- /dev/null +++ b/src/pages/company/components/ToggleStatus.tsx @@ -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) => { + + const toggleStatus = useToggleStatusCompany() + const [isActive, setIsActive] = useState(props.defaultActive) + + const handleChange = (value: boolean) => { + setIsActive(value) + toggleStatus.mutate(props.id) + } + + return ( + + ) +} + +export default ToggleStatusCompany \ No newline at end of file diff --git a/src/pages/company/components/UpdateSideBar.tsx b/src/pages/company/components/UpdateSideBar.tsx new file mode 100644 index 0000000..ebcce01 --- /dev/null +++ b/src/pages/company/components/UpdateSideBar.tsx @@ -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 + setProfileImage: (image: File) => void + setCoverImage: (image: File) => void +} + +const UpdateSidebar: FC = ({ formik, setProfileImage, setCoverImage }) => { + + const { t } = useTranslation('global') + const { data: industries } = useGetIndustries(1, '', '') + + return ( +
+
+
+ وضعیت شرکت +
+ formik.setFieldValue('isActive', value)} + /> +
+ +
+ +
+ +
+
{t('company.industry')}
+
+ { + industries?.data?.industries?.map((item: IndustryItemType) => { + return ( +
+ formik.setFieldValue('industryId', e.target.checked ? item.id : '')} + /> +
{item.title}
+
+ ) + }) + } + +
+
+ +
+
{t('company.profile_image')}
+ setProfileImage(files[0])} + /> +
+ +
+
{t('company.cover_image')}
+ setCoverImage(files[0])} + /> +
+
+ ) +} + +export default UpdateSidebar \ No newline at end of file diff --git a/src/pages/company/hooks/useCompanyData.ts b/src/pages/company/hooks/useCompanyData.ts index e240e10..e9ee3f0 100644 --- a/src/pages/company/hooks/useCompanyData.ts +++ b/src/pages/company/hooks/useCompanyData.ts @@ -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), + }); +}; diff --git a/src/pages/company/service/CompanyService.ts b/src/pages/company/service/CompanyService.ts index eb9a42d..af2f457 100644 --- a/src/pages/company/service/CompanyService.ts +++ b/src/pages/company/service/CompanyService.ts @@ -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; +}; diff --git a/src/pages/company/types/CompanyTypes.ts b/src/pages/company/types/CompanyTypes.ts index af45887..673f854 100644 --- a/src/pages/company/types/CompanyTypes.ts +++ b/src/pages/company/types/CompanyTypes.ts @@ -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; }; diff --git a/src/router/Main.tsx b/src/router/Main.tsx index a2f6211..e96fbd4 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -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 ( -
+
{ hasSubMenu && 'xl:ms-[305px]', )}>
@@ -153,6 +155,8 @@ const MainRouter: FC = () => { } /> } /> } /> + } /> + } />
diff --git a/src/shared/SideBar.tsx b/src/shared/SideBar.tsx index b99d592..8c1a7e6 100644 --- a/src/shared/SideBar.tsx +++ b/src/shared/SideBar.tsx @@ -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='' /> + } - title={t('sidebar.services')} - isActive={isActive('services')} - link={Pages.services.list} - name='services' - activeName='services' + icon={} + title={t('sidebar.company')} + isActive={isActive('company')} + link={Pages.company.list} + activeName='company' /> { : subMenuName === 'support' ? - : null + : subMenuName === 'company' ? + + : null }
} diff --git a/src/shared/components/CompanySubMenu.tsx b/src/shared/components/CompanySubMenu.tsx new file mode 100644 index 0000000..c322c0f --- /dev/null +++ b/src/shared/components/CompanySubMenu.tsx @@ -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 ( +
+
+ +
+ {t('sidebar.company')} +
+
+ +
+ + + +
+ +
+ ) +} + +export default CompanySubMenu \ No newline at end of file