From 35591cde476a57a75399f47ecc8f5a4f2c343b49 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sun, 11 May 2025 12:38:04 +0330 Subject: [PATCH] industries --- src/App.tsx | 1 + src/config/Pages.ts | 1 + src/langs/fa.json | 3 + .../company/components/CreateIndustry.tsx | 106 ++++++++++++++ .../company/components/CreateSidebar.tsx | 14 +- src/pages/company/components/Industries.tsx | 132 +++++++++++++++++ .../company/components/StatusIndustry.tsx | 28 ++++ .../company/components/UpdateIndustry.tsx | 138 ++++++++++++++++++ src/pages/company/hooks/useCompanyData.ts | 45 ++++++ src/pages/company/service/CompanyService.ts | 43 ++++++ src/pages/company/types/CompanyTypes.ts | 13 ++ src/router/Main.tsx | 2 + 12 files changed, 524 insertions(+), 2 deletions(-) create mode 100644 src/pages/company/components/CreateIndustry.tsx create mode 100644 src/pages/company/components/Industries.tsx create mode 100644 src/pages/company/components/StatusIndustry.tsx create mode 100644 src/pages/company/components/UpdateIndustry.tsx create mode 100644 src/pages/company/hooks/useCompanyData.ts create mode 100644 src/pages/company/service/CompanyService.ts create mode 100644 src/pages/company/types/CompanyTypes.ts diff --git a/src/App.tsx b/src/App.tsx index 01a408c..53d9b4c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,6 +36,7 @@ const queryClient = new QueryClient({ queryCache: new QueryCache({ onError: async (error: IApiErrorRepsonse) => { if (error?.response?.status === 401) { + return try { // Use a flag to track if refresh is in progress if (window.isRefreshingToken) { diff --git a/src/config/Pages.ts b/src/config/Pages.ts index 394b2de..63d51f3 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -114,5 +114,6 @@ export const Pages = { }, company: { create: "/company/create", + industries: "/company/industries", }, }; diff --git a/src/langs/fa.json b/src/langs/fa.json index b5278b0..fcb77c4 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -804,5 +804,8 @@ "status": "وضعیت", "full_name": "نام و نام خانوادگی", "users": "کاربران" + }, + "company": { + "count_company": "تعداد شرکت ها" } } diff --git a/src/pages/company/components/CreateIndustry.tsx b/src/pages/company/components/CreateIndustry.tsx new file mode 100644 index 0000000..3ac2fc5 --- /dev/null +++ b/src/pages/company/components/CreateIndustry.tsx @@ -0,0 +1,106 @@ +import { FC, useState } from 'react' +import SwitchComponent from '../../../components/Switch' +import { useFormik } from 'formik' +import { CreateIndustryType } from '../types/CompanyTypes' +import * as yup from 'yup' +import Input from '../../../components/Input' +import UploadBoxDraggble from '../../../components/UploadBoxDraggble' +import Button from '../../../components/Button' +import { TickCircle } from 'iconsax-react' +import { toast } from 'react-toastify' +import { useSingleUpload } from '../../service/hooks/useServiceData' +import { useCreateIndustry } from '../hooks/useCompanyData' + +type Props = { + refetch: () => void +} + +const CreateIndustry: FC = ({ refetch }) => { + + const [file, setFile] = useState() + const singleUpload = useSingleUpload() + const createIndustry = useCreateIndustry() + + const formik = useFormik({ + initialValues: { + title: '', + iconUrl: '', + isActive: true, + }, + validationSchema: yup.object({ + title: yup.string().required('عنوان صنعت الزامی است'), + }), + onSubmit: (values) => { + if (!file) { + toast.error('لطفا آیکن صنعت را انتخاب کنید') + return + } + + const formData = new FormData() + formData.append('file', file) + singleUpload.mutate(formData, { + onSuccess: (data) => { + values.iconUrl = data?.data?.url + createIndustry.mutate(values, { + onSuccess: () => { + toast.success('صنعت با موفقیت ثبت شد') + refetch() + } + }) + } + }) + }, + }) + + return ( +
+
+
+ وضعیت صنعت +
+ { + formik.setFieldValue('isActive', value) + }} + /> +
+ +
+ +
+ +
+ { + setFile(files[0]) + }} + /> +
+ +
+ +
+ +
+ ) +} + +export default CreateIndustry \ No newline at end of file diff --git a/src/pages/company/components/CreateSidebar.tsx b/src/pages/company/components/CreateSidebar.tsx index 01068b8..b72901f 100644 --- a/src/pages/company/components/CreateSidebar.tsx +++ b/src/pages/company/components/CreateSidebar.tsx @@ -1,11 +1,12 @@ import { FC } from 'react' import SwitchComponent from '../../../components/Switch' +import Input from '../../../components/Input' const CreateSidebar: FC = () => { return (
-
-
+
+
وضعیت شرکت
{ onChange={() => { }} />
+ +
+ { }} + error_text='' + /> +
) } diff --git a/src/pages/company/components/Industries.tsx b/src/pages/company/components/Industries.tsx new file mode 100644 index 0000000..339c834 --- /dev/null +++ b/src/pages/company/components/Industries.tsx @@ -0,0 +1,132 @@ +import { FC, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Select from '../../../components/Select' +import Input from '../../../components/Input' +import Td from '../../../components/Td' +import PageLoading from '../../../components/PageLoading' +import { Trash } from 'iconsax-react' +import CreateIndustry from './CreateIndustry' +import { useDeleteIndustry, useGetIndustries } from '../hooks/useCompanyData' +import { IndustryItemType } from '../types/CompanyTypes' +import Pagination from '../../../components/Pagination' +import StatusIndustry from './StatusIndustry' +import UpdateIndustry from './UpdateIndustry' + +const Industries: FC = () => { + + const { t } = useTranslation('global') + const [search, setSearch] = useState('') + const [page, setPage] = useState(1) + const [isActive, setIsActive] = useState<'active' | 'deactive' | ''>('') + const getIndustries = useGetIndustries(page, search, isActive) + const deleteIndustry = useDeleteIndustry() + + const handleDelete = (id: string) => { + deleteIndustry.mutate(id, { + onSuccess: () => { + getIndustries.refetch() + } + }) + } + + return ( +
+
+ {t('service.cateory_services')} +
+ +
+
+
+
+ setSearch(value)} + /> +
+
+
+ { + getIndustries.isPending ? +
+ +
+ : +
+ + + + + + + { + getIndustries.data?.data?.industries?.map((item: IndustryItemType) => { + return ( + + + + + + ) + }) + } + +
+ + + + +
+ + + + + + +
+ getIndustries.refetch()} id={item.id} /> + handleDelete(item.id)} + /> +
+
+
+ +
+
+ } + + +
+
+ + getIndustries.refetch()} /> +
+
+ ) +} + +export default Industries diff --git a/src/pages/company/components/StatusIndustry.tsx b/src/pages/company/components/StatusIndustry.tsx new file mode 100644 index 0000000..1aab6a4 --- /dev/null +++ b/src/pages/company/components/StatusIndustry.tsx @@ -0,0 +1,28 @@ +import { FC, useState } from 'react' +import SwitchComponent from '../../../components/Switch' +import { useChangeStatusIndustry } from '../hooks/useCompanyData' + +type Props = { + defaultActive: boolean, + id: string +} + +const StatusIndustry: FC = (props: Props) => { + + const [isActive, setIsActive] = useState(props.defaultActive) + const toggleStatus = useChangeStatusIndustry() + + const handleChange = (value: boolean) => { + setIsActive(value) + toggleStatus.mutate(props.id) + } + + return ( + + ) +} + +export default StatusIndustry \ No newline at end of file diff --git a/src/pages/company/components/UpdateIndustry.tsx b/src/pages/company/components/UpdateIndustry.tsx new file mode 100644 index 0000000..271a7bc --- /dev/null +++ b/src/pages/company/components/UpdateIndustry.tsx @@ -0,0 +1,138 @@ +import { Edit, TickCircle } from 'iconsax-react' +import { FC, useEffect, useState } from 'react' +import DefaulModal from '../../../components/DefaulModal' +import Input from '../../../components/Input' +import * as Yup from 'yup' +import { useFormik } from 'formik' +import { toast } from 'react-toastify' +import { useTranslation } from 'react-i18next' +import { ErrorType } from '../../../helpers/types' +import UploadBox from '../../../components/UploadBox' +import Button from '../../../components/Button' +import { useGetIndustryDetail, useUpdateIndustry } from '../hooks/useCompanyData' +import { useSingleUpload } from '../../service/hooks/useServiceData' +import { CreateIndustryType } from '../types/CompanyTypes' + +type Props = { + id: string, + refetch: () => void +} + +const UpdateIndustry: FC = (props) => { + const { t } = useTranslation('global') + const [showModal, setShowModal] = useState(false) + const [file, setFile] = useState() + const getIndustryDetail = useGetIndustryDetail(props.id) + const singleUpload = useSingleUpload() + const updateIndustry = useUpdateIndustry() + + const formik = useFormik({ + initialValues: { + title: '', + isActive: true, + iconUrl: '', + }, + validationSchema: Yup.object({ + title: Yup.string().required(t('errors.required')), + }), + onSubmit: async (values) => { + if (file) { + const formData = new FormData() + formData.append('file', file) + await singleUpload.mutateAsync(formData, { + onSuccess: (data) => { + values.iconUrl = data?.data?.url + }, + onError: (error: ErrorType) => { + toast.error(error.response?.data?.error?.message[0]) + } + }) + } + + const params = { + ...values, + } + updateIndustry.mutate({ id: props.id, params }, { + onSuccess: () => { + formik.resetForm() + getIndustryDetail.refetch() + toast.success(t('success')) + setShowModal(false) + props.refetch() + }, + onError: (error: ErrorType) => { + toast.error(error?.response?.data?.error?.message[0]) + } + }) + } + }) + + useEffect(() => { + if (getIndustryDetail.data) { + const data = getIndustryDetail.data?.data?.industry + formik.setValues({ + title: data?.title, + iconUrl: data?.iconUrl, + isActive: data?.isActive, + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getIndustryDetail.data, showModal]) + + return ( +
+ setShowModal(true)} size={20} color='#888888' /> + + setShowModal(false)} + isHeader + title_header='ویرایش دسته بندی' + > +
+ +
+ +
+ setFile(file[0])} + /> +
+ +
+
+ +
+
+
+
+ ) +} + +export default UpdateIndustry \ No newline at end of file diff --git a/src/pages/company/hooks/useCompanyData.ts b/src/pages/company/hooks/useCompanyData.ts new file mode 100644 index 0000000..e240e10 --- /dev/null +++ b/src/pages/company/hooks/useCompanyData.ts @@ -0,0 +1,45 @@ +import { useMutation, useQuery } from "@tanstack/react-query"; +import * as api from "../service/CompanyService"; +import { CreateIndustryType } from "../types/CompanyTypes"; +export const useGetIndustries = ( + page: number, + search: string, + isActive: string +) => { + return useQuery({ + queryKey: ["industries", page, search, isActive], + queryFn: () => api.getIndustries(page, search, isActive), + }); +}; + +export const useCreateIndustry = () => { + return useMutation({ + mutationFn: api.createIndustry, + }); +}; + +export const useDeleteIndustry = () => { + return useMutation({ + mutationFn: api.deleteIndustry, + }); +}; + +export const useChangeStatusIndustry = () => { + return useMutation({ + mutationFn: (id: string) => api.changeStatusIndustry(id), + }); +}; + +export const useUpdateIndustry = () => { + return useMutation({ + mutationFn: ({ id, params }: { id: string; params: CreateIndustryType }) => + api.updateIndustry(id, params), + }); +}; + +export const useGetIndustryDetail = (id: string) => { + return useQuery({ + queryKey: ["industry-detail", id], + queryFn: () => api.getIndustryDetail(id), + }); +}; diff --git a/src/pages/company/service/CompanyService.ts b/src/pages/company/service/CompanyService.ts new file mode 100644 index 0000000..eb9a42d --- /dev/null +++ b/src/pages/company/service/CompanyService.ts @@ -0,0 +1,43 @@ +import axios from "../../../config/axios"; +import { CreateIndustryType } from "../types/CompanyTypes"; + +export const getIndustries = async ( + page: number, + search: string, + isActive: string +) => { + let query = `?page=${page}&q=${search}`; + if (isActive) { + query += `&isActive=${isActive === "active" ? "1" : "0"}`; + } + const { data } = await axios.get(`/industries/list${query}`); + return data; +}; + +export const createIndustry = async (params: CreateIndustryType) => { + const { data } = await axios.post("/industries", params); + return data; +}; + +export const deleteIndustry = async (id: string) => { + const { data } = await axios.delete(`/industries/${id}`); + return data; +}; + +export const changeStatusIndustry = async (id: string) => { + const { data } = await axios.put(`/industries/${id}/status`); + return data; +}; + +export const updateIndustry = async ( + id: string, + params: CreateIndustryType +) => { + const { data } = await axios.patch(`/industries/${id}`, params); + return data; +}; + +export const getIndustryDetail = async (id: string) => { + const { data } = await axios.get(`/industries/${id}`); + return data; +}; diff --git a/src/pages/company/types/CompanyTypes.ts b/src/pages/company/types/CompanyTypes.ts new file mode 100644 index 0000000..c3e779e --- /dev/null +++ b/src/pages/company/types/CompanyTypes.ts @@ -0,0 +1,13 @@ +export type CreateIndustryType = { + title: string; + iconUrl: string; + isActive: boolean; +}; + +export type IndustryItemType = { + iconUrl: string; + id: string; + isActive: boolean; + title: string; + companiesCount: number; +}; diff --git a/src/router/Main.tsx b/src/router/Main.tsx index 4dee592..a2f6211 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -70,6 +70,7 @@ import SupportList from '../pages/support/List' 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' const MainRouter: FC = () => { const { hasSubMenu } = useSharedStore() @@ -151,6 +152,7 @@ const MainRouter: FC = () => { } /> } /> } /> + } />