diff --git a/src/Pages/ManageJobs/ManageJobs.tsx b/src/Pages/ManageJobs/ManageJobs.tsx index ce55ef5..40023f0 100644 --- a/src/Pages/ManageJobs/ManageJobs.tsx +++ b/src/Pages/ManageJobs/ManageJobs.tsx @@ -3,15 +3,14 @@ import { HStack, Image, // Image, - Input, Text, } from "@chakra-ui/react"; -import { LuSearch } from "react-icons/lu"; +// import { LuSearch } from "react-icons/lu"; // import { RiDeleteBin5Line } from "react-icons/ri"; // import AlertDailog from "../../components/AlertDailog"; import DataTable from "../../components/DataTable"; import MainFrame from "../../components/MainFrame"; -import { InputGroup } from "../../components/ui/input-group"; +// import { InputGroup } from "../../components/ui/input-group"; import ManageJobsAdd from "./ManageJobsAdd"; import ViewManageJob from "./ViewManageJob"; import { @@ -23,6 +22,8 @@ import { Spinner } from "../../components/Sipnner/Spinner"; import Delete from "../../components/ActionIcons/Delete"; import { toaster } from "../../components/ui/toaster"; import AlertDailog from "../../components/AlertDailog"; +import { useDebounce } from "../../components/Hooks/useDebounce"; +import SearchComponent from "../../components/SearchComponent"; // import { useState } from "react"; // import { useGetManageJobsQuery } from "../../Redux/Service/manage.jobs.service"; // import Delete from "../../components/ActionIcons/Delete"; @@ -40,9 +41,12 @@ const tableHeadRow = [ ]; const ManageJobs = () => { - const [currentPage] = useState(1); + const [currentPage, setCurrentPage] = useState(1); const [localData, setLocalData] = useState([]); - const { data, refetch, isLoading } = useGetManageJobsQuery(currentPage); + const [searchTerm, setSearchTerm] = useState(""); + const debouncedSearchTerm = useDebounce(searchTerm, 500); + const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage }; + const { data, refetch, isLoading, isError, isFetching } = useGetManageJobsQuery(queryArgs); const [deleteJobsPost] = useDeleteJobsPostMutation(); const [deleteModal, setDeleteModal] = useState(false); const [selectedJobsId, setSelectedJobsId] = useState(null); @@ -54,6 +58,15 @@ const ManageJobs = () => { }, [data]); console.log(data?.data.data); + const handleSearchChange = (value: string) => { + setSearchTerm(value); + setCurrentPage(1); + }; + + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + const handleDeleteJobs = async (jobsId: number) => { try { const response = await deleteJobsPost({ id: jobsId }).unwrap(); @@ -84,8 +97,8 @@ const ManageJobs = () => { Salary: agency?.ctc_amount, Action: ( - - + + ( @@ -142,29 +155,10 @@ const ManageJobs = () => { - - } - color={"#000"} - > - - + {/* */} @@ -172,6 +166,15 @@ const ManageJobs = () => { sortableColumns={["Name", "Registration Date "]} tableHeadRow={tableHeadRow} data={managepost} + paginationData={{ + current_page: data?.data.current_page || 1, + last_page: data?.data.last_page || 1, + per_page: data?.data.per_page || 10, + total: data?.data.total || 0 + }} + onPageChange={handlePageChange} + isLoading={isFetching} + isError={isError} /> diff --git a/src/Pages/ManageJobs/ManageJobsAdd.tsx b/src/Pages/ManageJobs/ManageJobsAdd.tsx index 02dca50..e2099c6 100644 --- a/src/Pages/ManageJobs/ManageJobsAdd.tsx +++ b/src/Pages/ManageJobs/ManageJobsAdd.tsx @@ -1,10 +1,8 @@ import { Field, Input, - SelectValueText, Span, Stack, - createListCollection, } from "@chakra-ui/react"; import { Button } from "../../components/ui/button"; import { @@ -19,24 +17,151 @@ import { } from "../../components/ui/dialog"; // import { TbEdit } from "react-icons/tb"; -import { - SelectContent, - SelectItem, - SelectLabel, - SelectRoot, - SelectTrigger, -} from "../../components/ui/select"; import Edit from "../../components/ActionIcons/Edit"; +import { JobStatusData, useGetCountryQuery, useGetDepartmentQuery, useGetIndustryQuery, useGetManageJobTypeQuery, useGetWorkspaceModesQuery, useUpdateJobsMutation, WorkSpace } from "../../Redux/Service/manage.jobs.service"; +import { useReducer, useRef } from "react"; +import { Toaster, toaster } from "../../components/ui/toaster"; + +const reducerFunction = (state: any, action: any) => { + switch (action.type) { + case "SET_JOB_TITLE": + return { ...state, jobTitle: action.payload }; + case "SET_WORKSPACE_MODE": + return { ...state, workspaceMode: action.payload }; + case "SET_CATEGORY": + return { ...state, category: action.payload }; + case "SET_SUB_CATEGORY": + return { ...state, subCategory: action.payload }; + case "SET_SALARY": + return { ...state, salary: action.payload }; + case "SET_EXPERIENCE": + return { ...state, experience: action.payload }; + case "SET_JOB_LOCATION": + return { ...state, jobLocation: action.payload }; + case "SET_COUNTRY_SELECTION": + return { ...state, countrySelection: action.payload }; + case "SET_JOB_TYPE": + return { ...state, jobType: action.payload }; + case "SET_SKILLS_REQUIRED": + return { ...state, skillsRequired: action.payload }; + case "SET_JOB_DESCRIPTION": + return { ...state, jobDescription: action.payload }; + default: + return state; + } +}; + +function ManageJobsAdd({ data, refetch }: { data: JobStatusData, refetch: () => void }) { + const { data: workspaceModes } = useGetWorkspaceModesQuery({}); + const { data: industryData } = useGetIndustryQuery({}); + const { data: departmentData } = useGetDepartmentQuery({}); + const { data: countryData } = useGetCountryQuery({}); + const { data: jobTypeData } = useGetManageJobTypeQuery({}); + const [updateJobs] = useUpdateJobsMutation(); + // console.log('Modes:', jobTypeData?.data.data); + + const initialState = { + jobTitle: data?.job_title || "", + workspaceMode: data?.workspace_mode_xid || "", + category: data?.industry_xid || "", + subCategory: data?.department_xid || "", + salary: data?.ctc_amount || "", + experience: data?.experience || "", + jobLocation: data?.job_location || "", + countrySelection: data?.country_xid || "", + jobType: data?.job_type_xid || "", + skillsDescription: data?.skill_description || "", + jobDescription: data?.job_description || "", + }; + + const [state, dispatch] = useReducer(reducerFunction, initialState); + const closeRef = useRef(null); + + const getDisplayName = (name: string, maxLength = 30) => + name.length > maxLength ? name.slice(0, maxLength) + "..." : name; + + const handleSubmit = async () => { + const { + jobTitle, + workspaceMode, + category, + subCategory, + salary, + experience, + jobLocation, + countrySelection, + jobType, + skillsDescription, + jobDescription, + } = state; + + if ( + !jobTitle || + !workspaceMode || + !category || + !subCategory || + !salary || + !experience || + !jobLocation || + !countrySelection || + !jobType || + !skillsDescription || + !jobDescription + ) { + toaster.create({ + title: "Missing Fields", + description: "Please fill in all required fields before submitting.", + type: "error", + }); + return; + } + + // Only en_name is editable, so we only need to send that in the payload. + const payload = { + id: data?.id, + job_title: state.jobTitle, + workspace_mode_xid: state.workspaceMode, + industry_xid: state.category, + department_xid: state.subCategory, + ctc_amount: state.salary, + experience: state.experience, + job_location: state.jobLocation, + country_xid: state.countrySelection, + job_type_xid: state.jobType, + skill_description: state.skillsDescription, + job_description: state.jobDescription, + }; + + // console.log('payload', payload) + + try { + const response = await updateJobs(payload).unwrap(); + if (response?.status === "success") { + toaster.create({ + title: "Success", + description: "Country updated successfully", + type: "success", + }); + closeRef.current?.click(); + refetch() + } else { + toaster.create({ + title: "Error", + description: "Failed to update Country", + type: "error", + }); + } + } catch (error) { + console.error("Error updating template:", error); + // alert("Failed to update template"); + toaster.create({ + title: "Error", + description: "Something went wrong", + type: "error", + }); + } + }; -const frameworks = createListCollection({ - items: [ - { label: "React.js", value: "react" }, - { label: "Vue.js", value: "vue" }, - { label: "Angular", value: "angular" }, - { label: "Svelte", value: "svelte" }, - ], -}); -function ManageJobsAdd() { return ( @@ -73,50 +198,182 @@ function ManageJobsAdd() { pl={1} fontSize="12px" height="30px" + value={state.jobTitle} + onChange={(e) => + dispatch({ + type: "SET_JOB_TITLE", + payload: e.target.value, + }) + } /> Workspace mode - + Category - + Sub-Category - + + + + + Country + + + + + + Job type + + + + Salary @@ -129,6 +386,13 @@ function ManageJobsAdd() { pl={1} fontSize="12px" height="30px" + value={state.salary} + onChange={(e) => + dispatch({ + type: "SET_SALARY", + payload: e.target.value, + }) + } /> @@ -136,13 +400,20 @@ function ManageJobsAdd() { Experience + dispatch({ + type: "SET_EXPERIENCE", + payload: e.target.value, + }) + } /> @@ -157,71 +428,34 @@ function ManageJobsAdd() { pl={1} fontSize="12px" height="30px" - /> - - {/* Country Selection - */} - - - Country Selection - - - - - - {frameworks.items.map((movie) => ( - - {movie.label} - - ))} - - - - - Job type - - + dispatch({ + type: "SET_JOB_LOCATION", + payload: e.target.value, + }) + } /> - Skills required + Skills description + dispatch({ + type: "SET_SKILLS_DESCRIPTION", + payload: e.target.value, + }) + } /> @@ -236,6 +470,13 @@ function ManageJobsAdd() { pl={1} fontSize="12px" height="30px" + value={state.jobDescription} + onChange={(e) => + dispatch({ + type: "SET_JOB_DESCRIPTION", + payload: e.target.value, + }) + } /> @@ -247,13 +488,15 @@ function ManageJobsAdd() { color={"#fff"} fontSize="12px" height="30px" + onClick={handleSubmit} > Save - + + ); } diff --git a/src/Pages/ManageJobs/ViewManageJob.tsx b/src/Pages/ManageJobs/ViewManageJob.tsx index ed9536e..91c3928 100644 --- a/src/Pages/ManageJobs/ViewManageJob.tsx +++ b/src/Pages/ManageJobs/ViewManageJob.tsx @@ -1,17 +1,13 @@ import { Field, Input, - SelectValueText, - Span, Stack, - createListCollection, } from "@chakra-ui/react"; import { Button } from "../../components/ui/button"; import { DialogBody, DialogCloseTrigger, DialogContent, - DialogFooter, DialogHeader, DialogRoot, DialogTitle, @@ -19,32 +15,17 @@ import { } from "../../components/ui/dialog"; // import { MdOutlineRemoveRedEye } from "react-icons/md"; -import { - SelectContent, - SelectItem, - SelectLabel, - SelectRoot, - SelectTrigger, -} from "../../components/ui/select"; import View from "../../components/ActionIcons/View"; -import { useLazyViewJobsQuery } from "../../Redux/Service/manage.jobs.service"; +import { JobStatusData, useLazyViewJobsQuery } from "../../Redux/Service/manage.jobs.service"; -const frameworks = createListCollection({ - items: [ - { label: "React.js", value: "react" }, - { label: "Vue.js", value: "vue" }, - { label: "Angular", value: "angular" }, - { label: "Svelte", value: "svelte" }, - ], -}); -function ViewManageJob() { - const [ data ] = useLazyViewJobsQuery(); +function ViewManageJob({ data }: { data: JobStatusData }) { + const [trigger] = useLazyViewJobsQuery(); console.log(data); - // const handleView = () => { - // trigger(id); - // }; + const handleView = () => { + trigger(data.id); + }; // const viewJobs = data; @@ -53,242 +34,271 @@ function ViewManageJob() { return ( - + {/* {viewJobs?.map((data: any) => ( */} - - - - Add Details - - + + + + Add Details + + - - - - - Job title - - - - - - Workspace mode - - - - - - Category - - - - - - Sub-Category - - - - - - Salary - - - - - - Experience - - - - - - Job Location - - - - {/* Country Selection - */} - - - Country Selection - - - - - - {frameworks.items.map((movie) => ( - - {movie.label} - - ))} - - + + + + + Job title + + + + + + Workspace mode + + + + + + Category + + + + + + Sub-Category + + + + + + Salary + + + + + + Experience + + + + + + Job Location + + + - - - Job type - - - - - - Skills required - - - - - - Job Description* - - - - - - Upload Image - - - - - - - - + + + Country + + + - - + {/* + + Country Selection + + + + + + {frameworks.items.map((movie) => ( + + {movie.label} + + ))} + + */} + + + + Job type + + + + + + Skills required + + + + + + Job Description* + + + + {/* + + Upload Image + + + */} + + + {/* + + */} + + + {/* ))} */} ); diff --git a/src/Pages/MasterModule/AgencyMaster/AgencyMaster.tsx b/src/Pages/MasterModule/AgencyMaster/AgencyMaster.tsx index defaf14..a099f59 100644 --- a/src/Pages/MasterModule/AgencyMaster/AgencyMaster.tsx +++ b/src/Pages/MasterModule/AgencyMaster/AgencyMaster.tsx @@ -8,6 +8,7 @@ import ViewAgencyMaster from "./ViewAgencyMaster"; import { useAgencyMasterToggleMutation, useGetAgencyMasterQuery } from "../../../Redux/Service/agency.master.module.service"; import { useEffect, useState } from "react"; import SearchComponent from "../../../components/SearchComponent"; +import { useDebounce } from "../../../components/Hooks/useDebounce"; // table data @@ -48,10 +49,12 @@ const tableHeadRow = [ const AgencyMaster = () => { const [currentPage, setCurrentPage] = useState(1); - const { data, refetch } = useGetAgencyMasterQuery(currentPage) const [agencyMasterToggle] = useAgencyMasterToggleMutation() const [localData, setLocalData] = useState([]); const [searchTerm, setSearchTerm] = useState(""); + const debouncedSearchTerm = useDebounce(searchTerm, 500); + const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage }; + const { data, refetch, isError, isFetching } = useGetAgencyMasterQuery(queryArgs) const handleToggle = async (agencyId: string, currentStatus: number) => { const newStatus = currentStatus ? 0 : 1; @@ -77,6 +80,11 @@ const AgencyMaster = () => { setCurrentPage(page); }; + const handleSearchChange = (value: string) => { + setSearchTerm(value); + setCurrentPage(1); + }; + const filteredData = localData?.filter((agency) => agency?.name.toLowerCase().includes(searchTerm.toLowerCase()) || agency?.rc_number.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -118,7 +126,7 @@ const AgencyMaster = () => { ), }; }); - + useEffect(() => { if (data?.data?.data) { @@ -170,7 +178,7 @@ const AgencyMaster = () => { */} {/* */} @@ -186,8 +194,10 @@ const AgencyMaster = () => { total: data?.data.total || 0 }} onPageChange={handlePageChange} + isLoading={isFetching} + isError={isError} /> - + ) } diff --git a/src/Pages/MasterModule/Country/Country.tsx b/src/Pages/MasterModule/Country/Country.tsx index c2be07a..fb467d8 100644 --- a/src/Pages/MasterModule/Country/Country.tsx +++ b/src/Pages/MasterModule/Country/Country.tsx @@ -7,6 +7,7 @@ import EditCountryModel from "./EditCountryModel"; import { CountryData, useCountryToggleMutation, useGetCountryMasterQuery } from "../../../Redux/Service/country.master"; import { useEffect, useState } from "react"; import SearchComponent from "../../../components/SearchComponent"; +import { useDebounce } from "../../../components/Hooks/useDebounce"; @@ -36,10 +37,13 @@ const tableHeadRow = [ const Country = () => { const [currentPage, setCurrentPage] = useState(1); - const { data, refetch } = useGetCountryMasterQuery(currentPage) + // const { data, refetch } = useGetCountryMasterQuery(currentPage) const [countryToggle] = useCountryToggleMutation() const [localData, setLocalData] = useState([]); const [searchTerm, setSearchTerm] = useState(""); + const debouncedSearchTerm = useDebounce(searchTerm, 500); + const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage }; + const { data, refetch, isError, isFetching } = useGetCountryMasterQuery(queryArgs); console.log("Country Data", data?.data.data) useEffect(() => { @@ -52,6 +56,11 @@ const Country = () => { setCurrentPage(page); }; + const handleSearchChange = (value: string) => { + setSearchTerm(value); + setCurrentPage(1); + }; + const filteredData = localData?.filter((agency) => { const searchLower = searchTerm.toLowerCase(); const countryName = agency.en_name?.toLowerCase().includes(searchLower); @@ -98,6 +107,7 @@ const Country = () => { ), })) + return ( @@ -137,11 +147,12 @@ const Country = () => { */} { - setSearchTerm(value); - // setCurrentPage(1); - refetch() - }} + // onChange={(value) => { + // setSearchTerm(value); + // // setCurrentPage(1); + // refetch() + // }} + onChange={handleSearchChange} /> {/* */} @@ -158,6 +169,8 @@ const Country = () => { total: data?.data.total || 0 }} onPageChange={handlePageChange} + isLoading={isFetching} + isError={isError} /> diff --git a/src/Pages/MasterModule/DepartmentMaster/DepartmentMasterList.tsx b/src/Pages/MasterModule/DepartmentMaster/DepartmentMasterList.tsx index fc4e7ae..f69103d 100644 --- a/src/Pages/MasterModule/DepartmentMaster/DepartmentMasterList.tsx +++ b/src/Pages/MasterModule/DepartmentMaster/DepartmentMasterList.tsx @@ -9,6 +9,7 @@ import SearchComponent from "../../../components/SearchComponent"; import { useDepartmentToggleMutation, useGetDepartmentMasterQuery } from "../../../Redux/Service/department.master"; import AddDepartmentMaster from "./AddDepartmentMaster"; import EditDepartmentMaster from "./EditDepartmentMaster"; +import { useDebounce } from "../../../components/Hooks/useDebounce"; // table data @@ -24,12 +25,14 @@ const tableHeadRow = [ const DepartmentMasterList = () => { const [currentPage, setCurrentPage] = useState(1); - const { data, refetch } = useGetDepartmentMasterQuery(currentPage) const [departmentToggle] = useDepartmentToggleMutation() const [localData, setLocalData] = useState([]); - const [searchTerm, setSearchTerm] = useState(""); + const [searchTerm, setSearchTerm] = useState(""); + const debouncedSearchTerm = useDebounce(searchTerm, 500); + const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage }; + const { data, refetch, isError, isFetching } = useGetDepartmentMasterQuery(queryArgs) - console.log("Department Data", data?.data.data) + console.log("Department Data", data?.data.data) useEffect(() => { if (data?.data?.data) { @@ -37,6 +40,11 @@ const DepartmentMasterList = () => { } }, [data]); + const handleSearchChange = (value: string) => { + setSearchTerm(value); + setCurrentPage(1); + }; + const handlePageChange = (page: number) => { setCurrentPage(page); }; @@ -112,11 +120,7 @@ const DepartmentMasterList = () => { { - setSearchTerm(value); - // setCurrentPage(1); - refetch() - }} + onChange={handleSearchChange} /> {/* */} {/* */} @@ -134,6 +138,8 @@ const DepartmentMasterList = () => { total: data?.data.total || 0 }} onPageChange={handlePageChange} + isLoading={isFetching} + isError={isError} /> diff --git a/src/Pages/MasterModule/IndustryMaster/IndustryMasterList.tsx b/src/Pages/MasterModule/IndustryMaster/IndustryMasterList.tsx index 55b8213..8386473 100644 --- a/src/Pages/MasterModule/IndustryMaster/IndustryMasterList.tsx +++ b/src/Pages/MasterModule/IndustryMaster/IndustryMasterList.tsx @@ -9,6 +9,7 @@ import { useGetIndustryMasterQuery, useIndustryMasterToggleMutation } from "../. import EditIndustryMaster from "./EditIndustryMaster"; import AddIndustryMaster from "./AddIndustryMaster"; import SearchComponent from "../../../components/SearchComponent"; +import { useDebounce } from "../../../components/Hooks/useDebounce"; // table data @@ -44,10 +45,12 @@ const tableHeadRow = [ const IndustryMasterList = () => { const [currentPage, setCurrentPage] = useState(1); - const { data, refetch } = useGetIndustryMasterQuery(currentPage) const [industryMasterToggle] = useIndustryMasterToggleMutation() const [localData, setLocalData] = useState([]); - const [searchTerm, setSearchTerm] = useState(""); + const [searchTerm, setSearchTerm] = useState(""); + const debouncedSearchTerm = useDebounce(searchTerm, 500); + const queryArgs = debouncedSearchTerm ? { page: currentPage, search: debouncedSearchTerm } : { page: currentPage }; + const { data, refetch, isError, isFetching } = useGetIndustryMasterQuery(queryArgs) useEffect(() => { if (data?.data?.data) { @@ -55,6 +58,11 @@ const IndustryMasterList = () => { } }, [data]); + const handleSearchChange = (value: string) => { + setSearchTerm(value); + setCurrentPage(1); + }; + const handlePageChange = (page: number) => { setCurrentPage(page); }; @@ -130,11 +138,7 @@ const IndustryMasterList = () => { { - setSearchTerm(value); - // setCurrentPage(1); - refetch() - }} + onChange={handleSearchChange} /> {/* */} {/* */} @@ -152,6 +156,8 @@ const IndustryMasterList = () => { total: data?.data.total || 0 }} onPageChange={handlePageChange} + isLoading={isFetching} + isError={isError} /> diff --git a/src/Pages/MasterModule/TemplateMaster/TemplateMaster.tsx b/src/Pages/MasterModule/TemplateMaster/TemplateMaster.tsx index a1f4fdb..8e5d600 100644 --- a/src/Pages/MasterModule/TemplateMaster/TemplateMaster.tsx +++ b/src/Pages/MasterModule/TemplateMaster/TemplateMaster.tsx @@ -87,38 +87,44 @@ const TemplateMaster = () => { agency.post_template_translate[0].title.toLowerCase().includes(searchTerm.toLowerCase()) ); - const managepost = filteredData?.map((agency: Template, index: number) => ({ - 'id': agency.id, - "Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1, - "Title": agency.post_template_translate.length > 0 - ? agency.post_template_translate[0].title - : "N/A", - "User Type": agency.principle_type_xid === 2 ? 'Recruiter' : 'Job Seeker', - "Images": ( - // - - {agency.post_template_image.map((img) => ( - - ))} + const activeCount = filteredData?.filter((a: any) => a.is_active === 1).length ?? 0; - {/* */} - - ), + const managepost = filteredData?.map((agency: Template, index: number) => { + const isOnlyActive = activeCount === 1 && Number(agency.is_active) === 1; - "Action": ( - - - - handleToggle(agency.id.toString(), Number(agency.is_active ?? 0))} - checked={Boolean(Number(agency.is_active))} - /> - - - ), - })); + return { + 'id': agency.id, + "Sr. No": (currentPage - 1) * (data?.data.per_page ?? 0) + index + 1, + "Title": agency.post_template_translate.length > 0 + ? agency.post_template_translate[0].title + : "N/A", + "User Type": agency.principle_type_xid === 2 ? 'Recruiter' : 'Job Seeker', + "Images": ( + // + + {agency.post_template_image.map((img) => ( + + ))} + + {/* */} + + ), + + "Action": ( + + + + handleToggle(agency.id.toString(), Number(agency.is_active ?? 0))} + checked={Boolean(Number(agency.is_active))} + disabled={isOnlyActive} + /> + + + ) + }}); return ( diff --git a/src/Pages/SubAdmin/AddModel.tsx b/src/Pages/SubAdmin/AddModel.tsx index 9192c09..50a7447 100644 --- a/src/Pages/SubAdmin/AddModel.tsx +++ b/src/Pages/SubAdmin/AddModel.tsx @@ -12,11 +12,11 @@ import { import { Field, Grid, Heading, Input, Stack, Text } from "@chakra-ui/react"; import { IoMdAdd } from "react-icons/io"; import { Checkbox } from "../../components/ui/checkbox"; -import { useCreateSubAdminPostMutation } from "../../Redux/Service/manage.subadmin.service"; -import { toaster } from "../../components/ui/toaster"; -import { useState } from "react"; +import { PermissionResponse, useCreateSubAdminPostMutation } from "../../Redux/Service/manage.subadmin.service"; +import { toaster, Toaster } from "../../components/ui/toaster"; +import { useEffect, useState } from "react"; -function AddModel({ refetch }: { refetch: VoidFunction }) { +function AddModel({ refetch, allPermissions }: { refetch: VoidFunction, allPermissions: PermissionResponse }) { const [createSubAdminPost] = useCreateSubAdminPostMutation(); // State fields @@ -25,8 +25,37 @@ function AddModel({ refetch }: { refetch: VoidFunction }) { const [userName, setUserName] = useState(""); const [dateOfBirth, setDateOfBirth] = useState(""); const [gender, setGender] = useState(""); + const [email, setEmail] = useState(""); + const [phonenumber, setPhonenumber] = useState(""); + const [permissions, setPermission] = useState([]); + const [isOpen, setIsOpen] = useState(false); // const [ setIsOpen] = useState(false); + const handleOpenModal = () => { + setIsOpen(true); + }; + + const handleCheckboxToggle = (permissionId: number) => { + setPermission((prevData) => + prevData.includes(permissionId) + ? prevData.filter((id) => id !== permissionId) + : [...prevData, permissionId] + ); + }; + +useEffect(() => { + if (!isOpen) { + setFirstName(""); + setLastName(""); + setUserName(""); + setDateOfBirth(""); + setGender(""); + setEmail(""); + setPhonenumber(""); + setPermission([]); + } + }, [isOpen]); + const handleSubmit = async () => { if ( !firstName.trim() || @@ -43,17 +72,35 @@ function AddModel({ refetch }: { refetch: VoidFunction }) { return; } + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + toaster.create({ + title: "Invalid Email", + description: "Please enter a valid email address", + type: "error", + }); + return; + } + + if (phonenumber.length !== 10) { + toaster.create({ + title: "Invalid Phone Number", + description: "Phone number must be exactly 10 digits", + type: "error", + }); + return; + } + const payload = { - principal_type_xid: 4, - principal_source_xid: 1, user_name: userName, first_name: firstName, last_name: lastName, date_of_birth: dateOfBirth, gender: gender, - email_address: "example@yopmail.com", // Hardcoded - phone_number: "9876543210", // Hardcoded - created_by: 1, + email_address: email, + phone_number: phonenumber, + permission: permissions.filter((id) => typeof id === "number"), + // created_by: 1, }; try { @@ -71,21 +118,28 @@ function AddModel({ refetch }: { refetch: VoidFunction }) { setUserName(""); setDateOfBirth(""); setGender(""); + setIsOpen(false); } - } catch (error) { + } catch (error:any) { console.error("Error creating sub-admin:", error); toaster.create({ title: "Error", - description: "Failed to create sub-admin", + description: error ? error.data.message : "Failed to create sub-admin", type: "error", }); } }; return ( - + setIsOpen(open)}> - @@ -184,28 +238,56 @@ function AddModel({ refetch }: { refetch: VoidFunction }) { onChange={(e) => setGender(e.target.value)} /> + + Email Address + + setEmail(e.target.value)} + /> + + + Phone Number + + { + const value = e.target.value; + if (/^\d*$/.test(value)) { // Only allow digits + setPhonenumber(value); + } + }} + /> + Permissions - {[ - "Dashboard", - "Manage contact us", - "manage User", - "Manage CMS", - "Manage Post", - "Manage Reports", - "manage Sub-Admin", - "My profile", - "Manage Jobs", - "manage feedbacks", - "Manage community", - "Notification", - ].map((permission) => ( - - {permission} + {allPermissions?.data.permission.map((permission: any) => ( + handleCheckboxToggle(permission.id)}> + {permission.app_resource_title} ))} @@ -223,9 +305,9 @@ function AddModel({ refetch }: { refetch: VoidFunction }) { Save - - + setIsOpen(false)} /> + ); } diff --git a/src/Pages/SubAdmin/SubAdmin.tsx b/src/Pages/SubAdmin/SubAdmin.tsx index 924fb51..d2c540c 100644 --- a/src/Pages/SubAdmin/SubAdmin.tsx +++ b/src/Pages/SubAdmin/SubAdmin.tsx @@ -9,7 +9,7 @@ import AddModel from "./AddModel" import EditSubAdmin from "../../components/EditSubAdmin" import ViewSubAdmin from "./ViewSubAdmin" import Delete from "../../components/ActionIcons/Delete" -import { useDeleteSubAdminPostMutation, useGetSubAdminQuery } from "../../Redux/Service/manage.subadmin.service" +import { PermissionResponse, useDeleteSubAdminPostMutation, useGetPermissionQuery, useGetSubAdminQuery } from "../../Redux/Service/manage.subadmin.service" import { useEffect, useState } from "react" import { toaster } from "../../components/ui/toaster" @@ -56,36 +56,37 @@ const tableHeadRow = [ const SubAdmin = () => { const { data, refetch } = useGetSubAdminQuery() + const { data: permissions } = useGetPermissionQuery() const [localData, setLocalData] = useState([]); + const [allPermissions, setAllPermissions] = useState(); const [deleteModal, setDeleteModal] = useState(false) const [deleteSubAdminPost] = useDeleteSubAdminPostMutation() - console.log("============================",data); - - useEffect(() => { if (data?.data.data) { setLocalData(data?.data.data); + setAllPermissions(permissions); } - }, [data]); - + }, [data, permissions]); + + console.log("============================", allPermissions); console.log('localData', localData); - const handleDeleteFaq = async (faqId: number) => { + const handleDeleteAdmin = async (faqId: number) => { try { const response = await deleteSubAdminPost(faqId).unwrap(); if (response.success) { toaster.create({ title: "Success", - description: "FAQ deleted successfully", + description: "Sub Admin deleted successfully", type: "success", }); refetch() - console.log("FAQ deleted successfully:", response); + console.log("Sub Admin deleted successfully:", response); } // Optionally, refetch data or update state after deletion } catch (error) { - console.error("Error deleting FAQ:", error); + console.error("Error deleting Sub Admin:", error); toaster.create({ title: "Error", description: "Something went wrong", @@ -114,20 +115,20 @@ const SubAdmin = () => { "Action": ( {/* */} - - + + {allPermissions && } setDeleteModal(prev => !prev)} />} - alertText="Delete FAQ" + alertText="Delete sub admin" alertIcon={} alertCaption="are you sure you want to delete ?" onClose={() => setDeleteModal(false)} onConfirm={() => { // console.log("User deleted:", index + 1); setDeleteModal(false); - handleDeleteFaq(agency.id) + handleDeleteAdmin(agency.id) }} /> @@ -171,7 +172,7 @@ const SubAdmin = () => { /> {/* */} - + {allPermissions && } { @@ -64,6 +64,7 @@ function ViewSubAdmin({ id }: { id: number }) { overflowX="hidden" p={3} // Reduced padding bgSize={"md"} + key={data.id} > @@ -149,8 +150,8 @@ function ViewSubAdmin({ id }: { id: number }) { {data.get_resource_action_link.map((check: any) => ( - - {check.app_resource_xid} + + {check?.app_resource.app_resource_title} // <> // diff --git a/src/Redux/Service/agency.master.module.service.ts b/src/Redux/Service/agency.master.module.service.ts index 65fa22f..dff836e 100644 --- a/src/Redux/Service/agency.master.module.service.ts +++ b/src/Redux/Service/agency.master.module.service.ts @@ -70,8 +70,18 @@ export const agencyMasterModule = createApi({ }), }), - getAgencyMaster: builder.query({ - query: (page = 1) => `/agency-master?page=${page}` + // getAgencyMaster: builder.query({ + // query: (page = 1) => `/agency-master?page=${page}` + // }), + + getAgencyMaster: builder.query({ + query: ({ page, search }) => { + const params = new URLSearchParams(); + if (page) params.append("page", page.toString()); + if (search) params.append("search", search); + + return `/agency-master?${params.toString()}`; + }, }), agencyMasterToggle: builder.mutation({ diff --git a/src/Redux/Service/country.master.ts b/src/Redux/Service/country.master.ts index f70f360..5a3f7c6 100644 --- a/src/Redux/Service/country.master.ts +++ b/src/Redux/Service/country.master.ts @@ -65,8 +65,18 @@ export const countryMaster = createApi({ }), }), // 🔹 GET: Fetch all posts - getCountryMaster: builder.query({ - query: (page = 1) => `/country-list?page=${page}`, + // getCountryMaster: builder.query({ + // query: (page = 1) => `/country-list?page=${page}`, + // }), + + getCountryMaster: builder.query({ + query: ({ page, search }) => { + const params = new URLSearchParams(); + if (page) params.append("page", page.toString()); + if (search) params.append("search", search); + + return `/country-list?${params.toString()}`; + }, }), getCountryMasterEdit: builder.query({ diff --git a/src/Redux/Service/department.master.ts b/src/Redux/Service/department.master.ts index 28e617d..c0987fd 100644 --- a/src/Redux/Service/department.master.ts +++ b/src/Redux/Service/department.master.ts @@ -64,8 +64,18 @@ export const departmentMaster = createApi({ }), }), // 🔹 GET: Fetch all posts - getDepartmentMaster: builder.query({ - query: (page = 1) => `/department-master-list?page=${page}`, + // getDepartmentMaster: builder.query({ + // query: (page = 1) => `/department-master-list?page=${page}`, + // }), + + getDepartmentMaster: builder.query({ + query: ({ page, search }) => { + const params = new URLSearchParams(); + if (page) params.append("page", page.toString()); + if (search) params.append("search", search); + + return `/department-master-list?${params.toString()}`; + }, }), getDepartmentMasterDropDown: builder.query({ diff --git a/src/Redux/Service/industry.master.service.ts b/src/Redux/Service/industry.master.service.ts index d227bf8..03de4c1 100644 --- a/src/Redux/Service/industry.master.service.ts +++ b/src/Redux/Service/industry.master.service.ts @@ -61,8 +61,18 @@ export const industryMaster = createApi({ }), }), // 🔹 GET: Fetch all posts - getIndustryMaster: builder.query({ - query: (page = 1) => `/industry-master-list?page=${page}`, + // getIndustryMaster: builder.query({ + // query: (page = 1) => `/industry-master-list?page=${page}`, + // }), + + getIndustryMaster: builder.query({ + query: ({ page, search }) => { + const params = new URLSearchParams(); + if (page) params.append("page", page.toString()); + if (search) params.append("search", search); + + return `/industry-master-list?${params.toString()}`; + }, }), updateIndustryMaster: builder.mutation({ diff --git a/src/Redux/Service/manage.jobs.service.ts b/src/Redux/Service/manage.jobs.service.ts index 728b3a1..a8b24ff 100644 --- a/src/Redux/Service/manage.jobs.service.ts +++ b/src/Redux/Service/manage.jobs.service.ts @@ -66,17 +66,40 @@ export type PostJobStatus = { title: string }; +export type WorkSpace = { + id: number; + en_name: string; +}; + export const manageJobs = createApi({ reducerPath: "manageJobs", baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling endpoints: (builder) => ({ - getManageJobs: builder.query({ - query: (page = 1) => `/manage-jobs-list?page=${page}`, + // getManageJobs: builder.query({ + // query: (page = 1) => `/manage-jobs-list?page=${page}`, + // }), + + getManageJobs: builder.query({ + query: ({ page, search }) => { + const params = new URLSearchParams(); + if (page) params.append("page", page.toString()); + if (search) params.append("search", search); + + return `/manage-jobs-list?${params.toString()}`; + }, + }), + + updateJobs: builder.mutation({ + query: (updatedData) => ({ + url: "/manage-jobs-update", + method: "POST", + body: updatedData, + }), }), viewJobs: builder.query({ - query: (id) => `/manage-jobs-list/${id}`, + query: () => `/manage-jobs-list`, }), deleteJobsPost: builder.mutation<{ status: string; message: string }, { id: number }>({ @@ -87,10 +110,38 @@ export const manageJobs = createApi({ }), }), - + // Modes + getWorkspaceModes: builder.query({ + query: () => `/manage-jobs-get-workspace`, + }), + getIndustry: builder.query({ + query: () => `/manage-jobs-get-industry`, + }), + + getDepartment: builder.query({ + query: () => `/manage-jobs-get-department`, + }), + + getCountry: builder.query({ + query: () => `/manage-jobs-get-country`, + }), + + getManageJobType: builder.query({ + query: () => `/job-type`, + }), }), }); -export const { useGetManageJobsQuery,useLazyViewJobsQuery,useDeleteJobsPostMutation } = manageJobs; +export const { + useGetManageJobsQuery, + useLazyViewJobsQuery, + useDeleteJobsPostMutation, + useUpdateJobsMutation, + useGetWorkspaceModesQuery, + useGetIndustryQuery, + useGetDepartmentQuery, + useGetCountryQuery, + useGetManageJobTypeQuery +} = manageJobs; diff --git a/src/Redux/Service/manage.subadmin.service.ts b/src/Redux/Service/manage.subadmin.service.ts index 082a66b..bc61604 100644 --- a/src/Redux/Service/manage.subadmin.service.ts +++ b/src/Redux/Service/manage.subadmin.service.ts @@ -40,6 +40,25 @@ interface ApiResponse { data: PaginatedData; } +export type Permission = { + id: number; + app_resource_title: string; + is_active: boolean; + created_by: string | null; + modified_by: string | null; + deleted_at: string | null; + created_at: string; + updated_at: string; +}; + +export type PermissionResponse = { + status: string; + status_code: number; + message: string; + data: { + permission: Permission[]; + }; +}; // export type SubAdminPost = { // id: number; // first_name: string, @@ -60,6 +79,10 @@ interface ResourceActionLink { deleted_at: string | null; created_at: string; updated_at: string; + app_resource:{ + id: number; + app_resource_title: string + } } interface SubAdmin { @@ -80,8 +103,6 @@ interface SubAdminView { } interface CreateSubAdminPayload { - principal_type_xid: number; - principal_source_xid: number; user_name: string; first_name: string; last_name: string; @@ -89,7 +110,7 @@ interface CreateSubAdminPayload { gender: string; email_address: string; phone_number: string; - created_by: number; + // created_by: number; } interface CreateSubAdminResponse { @@ -117,6 +138,10 @@ export const manageSubAdmin = createApi({ query: () => `/sub-admin`, }), + getPermission: builder.query({ + query: () => `/resources`, + }), + viewSubAdmin: builder.query({ query: (id) => `/sub-admin-view/${id}`, }), @@ -151,8 +176,9 @@ export const manageSubAdmin = createApi({ deleteSubAdminPost: builder.mutation<{ success: boolean }, number>({ query: (id) => ({ - url: `/faq-delete/${id}`, - method: "DELETE", + url: `/sub-admin-delete`, + method: "POST", + body: { id }, }), }), }), @@ -160,6 +186,7 @@ export const manageSubAdmin = createApi({ export const { useGetSubAdminQuery, + useGetPermissionQuery, useLazyViewSubAdminQuery, useUpdateSubAdminMutation, useDeleteSubAdminPostMutation, diff --git a/src/components/DataTable.tsx b/src/components/DataTable.tsx index 28c3bf7..e39d786 100644 --- a/src/components/DataTable.tsx +++ b/src/components/DataTable.tsx @@ -25,6 +25,8 @@ interface TableProps { total: number; }; onPageChange?: (page: number) => void; + isLoading?: boolean; + isError?: boolean; } const DataTable: React.FC = ({ @@ -33,6 +35,8 @@ const DataTable: React.FC = ({ sortableColumns = [], paginationData, onPageChange, + isLoading, + isError }: TableProps) => { const { current_page = 1, last_page = 1 } = paginationData || {}; const [sortConfig, setSortConfig] = useState<{ @@ -103,17 +107,21 @@ const DataTable: React.FC = ({ ))} - {data?.length === 0 ? ( - - - No data - - No records found — they’ll appear here if available. - - + {isLoading ? ( + + + Loading... + + + ) : isError ? ( + + No data + + No records found — they’ll appear here if available. + ) : ( - + {data.map((item: any, index) => ( = ({ border={"none"} > {(() => { - const words = - item[heading]?.toString().split(" ") || []; + const words = item[heading]?.toString().split(" ") || []; return words.length > 5 ? `${words.slice(0, 5).join(" ")}...` : item[heading]; @@ -141,9 +148,10 @@ const DataTable: React.FC = ({ ))} )} + - {last_page > 1 && ( + {last_page > 1 && !isLoading && !isError && ( { - if (data?.data && data.data.length > 0) { - const subAdmin = data.data[0]; // Extract the first item from the array + if (data?.data?.length && allPermissions?.data?.permission?.length) { + const subAdmin = data.data[0]; + + const activePermissionIds = subAdmin.get_resource_action_link + .filter((perm: any) => perm.is_active) + .map((perm: any) => perm.app_resource_xid); + + const mergedPermissions: ResourceActionLink[] = allPermissions.data.permission.map((perm) => ({ + id: perm.id, + app_resource_xid: perm.id, + is_active: activePermissionIds.includes(perm.id), + app_resource: { + id: perm.id, + app_resource_title: perm.app_resource_title, + }, + })); + // Map the API response to editData setEditData({ @@ -73,10 +92,10 @@ function EditSubAdmin({ id, refetch }: { id: number, refetch: VoidFunction }) { last_name: subAdmin.last_name, date_of_birth: formatDateOfBirth(subAdmin.date_of_birth), gender: subAdmin.gender, - permission: subAdmin.get_resource_action_link, + permission: mergedPermissions, }); } - }, [data]); + }, [data, allPermissions]); const formatDateOfBirth = (dob: string): string => { // Convert the date to the desired format with slashes @@ -99,7 +118,7 @@ function EditSubAdmin({ id, refetch }: { id: number, refetch: VoidFunction }) { setEditData((prevData) => ({ ...prevData, permission: prevData.permission.map((permission) => - permission.id === permissionId + permission.app_resource_xid === permissionId ? { ...permission, is_active: !permission.is_active } : permission ), @@ -119,6 +138,8 @@ function EditSubAdmin({ id, refetch }: { id: number, refetch: VoidFunction }) { date_of_birth: editData.date_of_birth, gender: editData.gender, permission: editData.permission + .filter((p) => p.is_active) + .map((p) => p.app_resource_xid), }; try { @@ -295,7 +316,7 @@ function EditSubAdmin({ id, refetch }: { id: number, refetch: VoidFunction }) { size="sm" color="black" checked={permission.is_active} - onChange={() => handleCheckboxToggle(permission.id)} + onChange={() => handleCheckboxToggle(permission.app_resource_xid)} cursor={'pointer'} > {label} @@ -312,6 +333,7 @@ function EditSubAdmin({ id, refetch }: { id: number, refetch: VoidFunction }) { setIsOpen(false)} /> + ); } diff --git a/src/components/Hooks/useDebounce.ts b/src/components/Hooks/useDebounce.ts new file mode 100644 index 0000000..3dd267a --- /dev/null +++ b/src/components/Hooks/useDebounce.ts @@ -0,0 +1,16 @@ +// hooks/useDebounce.ts +import { useEffect, useState } from "react"; + +export function useDebounce(value: T, delay = 500): T { + const [debounced, setDebounced] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebounced(value); + }, delay); + + return () => clearTimeout(handler); // cleanup on unmount or value change + }, [value, delay]); + + return debounced; +}