integrate React Query for project details and real-time updates

This commit is contained in:
Pramod Mahajan 2025-06-29 00:55:02 +05:30
parent 22e65c167e
commit 467d5d4b13
7 changed files with 789 additions and 507 deletions

View File

@ -122,18 +122,9 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
}; };
return ( return (
<div
className="modal-dialog modal-lg modal-simple mx-sm-auto mx-1 edit-project-modal" <div className="p-sm-2 p-2">
role="document"
>
<div className="modal-content">
<div className="modal-body p-sm-4 p-0">
<button
type="button"
className="btn-close"
onClick={handleCancel}
aria-label="Close"
></button>
<div className="text-center mb-2"> <div className="text-center mb-2">
<h5 className="mb-2"> <h5 className="mb-2">
{project?.id ? "Edit Project" : "Create Project"} {project?.id ? "Edit Project" : "Create Project"}
@ -148,7 +139,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
type="text" type="text"
id="name" id="name"
name="name" name="name"
className="form-control" className="form-control form-control-sm"
placeholder="Project Name" placeholder="Project Name"
{...register("name")} {...register("name")}
/> />
@ -169,7 +160,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
type="text" type="text"
id="shortName" id="shortName"
name="shortName" name="shortName"
className="form-control" className="form-control form-control-sm"
placeholder="Short Name" placeholder="Short Name"
{...register("shortName")} {...register("shortName")}
/> />
@ -190,7 +181,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
type="text" type="text"
id="contactPerson" id="contactPerson"
name="contactPerson" name="contactPerson"
className="form-control" className="form-control form-control-sm"
placeholder="Contact Person" placeholder="Contact Person"
maxLength={50} maxLength={50}
{...register("contactPerson")} {...register("contactPerson")}
@ -252,7 +243,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
<select <select
id="modalEditUserStatus" id="modalEditUserStatus"
name="modalEditUserStatus" name="modalEditUserStatus"
className="select2 form-select" className="select2 form-select form-select-sm"
aria-label="Default select example" aria-label="Default select example"
{...register("projectStatusId", { {...register("projectStatusId", {
required: "Status is required", required: "Status is required",
@ -321,8 +312,6 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
</div> </div>
</form> </form>
</div> </div>
</div>
</div>
); );
}; };

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import moment from "moment"; import moment from "moment";
import { getDateDifferenceInDays } from "../../utils/dateUtils"; import { getDateDifferenceInDays } from "../../utils/dateUtils";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useProjectDetails } from "../../hooks/useProjects"; import { useProjectDetails, useUpdateProject } from "../../hooks/useProjects";
import ManageProjectInfo from "./ManageProjectInfo"; import ManageProjectInfo from "./ManageProjectInfo";
import ProjectRepository from "../../repositories/ProjectRepository"; import ProjectRepository from "../../repositories/ProjectRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager"; import { cacheData, getCachedData } from "../../slices/apiDataManager";
@ -13,29 +13,36 @@ import {
getProjectStatusColor, getProjectStatusColor,
getProjectStatusName, getProjectStatusName,
} from "../../utils/projectStatus"; } from "../../utils/projectStatus";
import GlobalModel from "../common/GlobalModel";
const ProjectCard = ({ projectData, recall }) => { const ProjectCard = ({ projectData, recall }) => {
const [projectInfo, setProjectInfo] = useState(projectData); const [ projectInfo, setProjectInfo ] = useState( projectData );
const [projectDetails, setProjectDetails] = useState(null); const { projects_Details, loading, error, refetch } = useProjectDetails(
projectInfo?.id,false
);
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const ManageProject = useHasUserPermission(MANAGE_PROJECT); const ManageProject = useHasUserPermission(MANAGE_PROJECT);
const [modifyProjectLoading, setMdifyProjectLoading] = useState(false); const {
mutate: updateProject,
isPending,
isSuccess,
isError,
} = useUpdateProject({
onSuccessCallback: () => {
setShowModal(false);
},
})
useEffect(()=>{ useEffect(()=>{
setProjectInfo(projectData); setProjectInfo(projectData);
},[projectData]) }, [ projectData ] )
// console.log("in card view",projectInfo);
const handleShow = async () => { const handleShow = async () => {
try { try {
setMdifyProjectLoading(true); const { data } = await refetch();
const response = await ProjectRepository.getProjectByprojectId(
projectInfo.id
);
setProjectDetails(response.data);
setMdifyProjectLoading(false);
setShowModal(true); setShowModal(true);
} catch (error) { } catch (err) {
showToast("Failed to load project details", "error"); showToast("Failed to load project details", "error");
} }
}; };
@ -54,64 +61,68 @@ const ProjectCard = ({ projectData, recall }) => {
}; };
const handleFormSubmit = (updatedProject) => { const handleFormSubmit = (updatedProject) => {
if (projectInfo?.id) { if (projectInfo?.id) {
ProjectRepository.updateProject(projectInfo.id, updatedProject) updateProject({
.then((response) => { projectId: projectInfo.id,
const updatedProjectData = { updatedData: updatedProject,
...projectInfo, });
...response.data, }
building: projectDetails?.building, };
};
setProjectInfo(updatedProject); // const handleFormSubmit = (updatedProject) => {
// if (projectInfo?.id) {
// ProjectRepository.updateProject(projectInfo.id, updatedProject)
// .then((response) => {
// const updatedProjectData = {
// ...projectInfo,
// ...response.data,
// building: projectDetails?.building,
// };
if (getCachedData(`projectinfo-${projectInfo.id}`)) { // setProjectInfo(updatedProject);
cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
}
const projects_list = getCachedData("projectslist"); // if (getCachedData(`projectinfo-${projectInfo.id}`)) {
if (projects_list) { // cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
const updatedProjectsList = projects_list.map((project) => // }
project.id === projectInfo.id
? { // const projects_list = getCachedData("projectslist");
...project, // if (projects_list) {
...response.data, // const updatedProjectsList = projects_list.map((project) =>
// tenant: project.tenant // project.id === projectInfo.id
} // ? {
: project // ...project,
); // ...response.data,
cacheData("projectslist", updatedProjectsList); // // tenant: project.tenant
} // }
recall(getCachedData("projectslist")); // : project
showToast("Project updated successfully.", "success"); // );
setShowModal(false); // cacheData("projectslist", updatedProjectsList);
}) // }
.catch((error) => { // recall(getCachedData("projectslist"));
showToast(error.message, "error"); // showToast("Project updated successfully.", "success");
}); // setShowModal(false);
} // })
}; // .catch((error) => {
// showToast(error.message, "error");
// });
// }
// };
return ( return (
<> <>
{showModal && projectDetails && (
<div {showModal && projects_Details && (
className="modal fade show" <GlobalModel isOpen={showModal} closeModal={handleClose}>
tabIndex="-1" <ManageProjectInfo
role="dialog" project={projects_Details}
style={{ display: "block" }}
aria-hidden="false"
>
<ManageProjectInfo
project={projectDetails}
handleSubmitForm={handleFormSubmit} handleSubmitForm={handleFormSubmit}
onClose={handleClose} onClose={handleClose}
/> />
</div> </GlobalModel>
)} )}
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4"> <div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
<div className="card cursor-pointer"> <div className={`card cursor-pointer ${isPending ? "bg-light opacity-50 pointer-events-none" : ""}`}>
<div className="card-header pb-4"> <div className="card-header pb-4">
<div className="d-flex align-items-start"> <div className="d-flex align-items-start">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
@ -143,23 +154,23 @@ const ProjectCard = ({ projectData, recall }) => {
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false" aria-expanded="false"
> >
{modifyProjectLoading ? ( {loading ? (
<div <div
className="spinner-border spinner-border-sm text-secondary" className="spinner-border spinner-border-sm text-secondary"
role="status" role="status"
> >
<span className="visually-hidden">Loading...</span> <span className="visually-hidden">Loading...</span>
</div> </div>
) : ( ) : (
<i <i
className="bx bx-dots-vertical-rounded bx-sm text-muted" className="bx bx-dots-vertical-rounded bx-sm text-muted"
data-bs-toggle="tooltip" data-bs-toggle="tooltip"
data-bs-offset="0,8" data-bs-offset="0,8"
data-bs-placement="top" data-bs-placement="top"
data-bs-custom-class="tooltip-dark" data-bs-custom-class="tooltip-dark"
title="More Action" title="More Action"
></i> ></i>
)} )}
</button> </button>
<ul className="dropdown-menu dropdown-menu-end"> <ul className="dropdown-menu dropdown-menu-end">
<li> <li>

View File

@ -14,6 +14,7 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { ASSIGN_TO_PROJECT } from "../../utils/constants"; import { ASSIGN_TO_PROJECT } from "../../utils/constants";
import ConfirmModal from "../common/ConfirmModal"; import ConfirmModal from "../common/ConfirmModal";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import {useEmployeesByProjectAllocated, useManageProjectAllocation} from "../../hooks/useProjects";
const Teams = ({ project }) => { const Teams = ({ project }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -26,69 +27,104 @@ const Teams = ({ project }) => {
const [filteredEmployees, setFilteredEmployees] = useState([]); const [filteredEmployees, setFilteredEmployees] = useState([]);
const [removingEmployeeId, setRemovingEmployeeId] = useState(null); const [removingEmployeeId, setRemovingEmployeeId] = useState(null);
const [assignedLoading, setAssignedLoading] = useState(false); const [assignedLoading, setAssignedLoading] = useState(false);
const [ employeeLodaing, setEmployeeLoading ] = useState( false ); // const [ employeeLodaing, setEmployeeLoading ] = useState( false );
const [ activeEmployee, setActiveEmployee ] = useState( true ) const [ activeEmployee, setActiveEmployee ] = useState( true )
const [deleteEmployee,setDeleteEmplyee] = useState(null) const [deleteEmployee,setDeleteEmplyee] = useState(null)
const navigate = useNavigate(); const navigate = useNavigate();
const HasAssignUserPermission = useHasUserPermission( ASSIGN_TO_PROJECT ); const HasAssignUserPermission = useHasUserPermission( ASSIGN_TO_PROJECT );
const[IsDeleteModal,setIsDeleteModal] = useState(false) const [ IsDeleteModal, setIsDeleteModal ] = useState( false )
const {projectEmployees, loading:employeeLodaing, refetch} = useEmployeesByProjectAllocated( project.id )
const {
mutate: submitAllocations,
isPending,
isSuccess,
isError,
} = useManageProjectAllocation({
onSuccessCallback: () => {
// Example: UI reset
setRemovingEmployeeId(null);
setAssignedLoading(false);
setDeleteEmplyee(null);
closeDeleteModal();
},
onErrorCallback: () => {
closeDeleteModal();
},
});
const fetchEmployees = async () => {
try {
setEmployeeLoading(true);
// if (!empRoles) { // const fetchEmployees = async () => {
ProjectRepository.getProjectAllocation(project.id) // try {
.then((response) => { // setEmployeeLoading(true);
setEmployees(response.data);
setFilteredEmployees( response.data.filter( ( emp ) => emp.isActive ) );
setEmployeeLoading(false);
})
.catch((error) => {
setError("Failed to fetch data.");
setEmployeeLoading(false);
});
} catch (err) {
setError("Failed to fetch activities.");
}
};
const submitAllocations = (items,added) => { // // if (!empRoles) {
ProjectRepository.manageProjectAllocation(items) // ProjectRepository.getProjectAllocation(project.id)
.then((response) => { // .then((response) => {
fetchEmployees(); // setEmployees(response.data);
if ( added ) // setFilteredEmployees( response.data.filter( ( emp ) => emp.isActive ) );
{ // setEmployeeLoading(false);
showToast("Employee Assigned Successfully", "success"); // })
}else{ // .catch((error) => {
showToast("Removed Employee Successfully", "success"); // setError("Failed to fetch data.");
} // setEmployeeLoading(false);
setRemovingEmployeeId(null); // });
setAssignedLoading( false ); // } catch (err) {
setDeleteEmplyee( null ) // setError("Failed to fetch activities.");
closeDeleteModal() // }
}) // };
.catch((error) => {
const message = error.response.data.message || error.message || "Error Occured during Api Call"; // const submitAllocations = (items,added) => {
showToast( message, "error" ); // ProjectRepository.manageProjectAllocation(items)
closeDeleteModal() // .then((response) => {
}); // fetchEmployees();
}; // if ( added )
// {
// showToast("Employee Assigned Successfully", "success");
// }else{
// showToast("Removed Employee Successfully", "success");
// }
// setRemovingEmployeeId(null);
// setAssignedLoading( false );
// setDeleteEmplyee( null )
// closeDeleteModal()
// })
// .catch((error) => {
// const message = error.response.data.message || error.message || "Error Occured during Api Call";
// showToast( message, "error" );
// closeDeleteModal()
// });
// };
// const removeAllocation = (item) => {
// setRemovingEmployeeId(item.id);
// submitAllocations([
// {
// empID: item.employeeId,
// jobRoleId: item.jobRoleId,
// projectId: project.id,
// status: false,
// },
// ] ,false);
// };
const removeAllocation = (item) => { const removeAllocation = (item) => {
setRemovingEmployeeId(item.id); setRemovingEmployeeId(item.id);
submitAllocations([
submitAllocations({
items: [
{ {
empID: item.employeeId, empID: item.employeeId,
jobRoleId: item.jobRoleId, jobRoleId: item.jobRoleId,
projectId: project.id, projectId: project.id,
status: false, status: false,
}, },
] ,false); ],
added: false,
}; });
};
const handleEmpAlicationFormSubmit = (allocaionObj) => { const handleEmpAlicationFormSubmit = (allocaionObj) => {
let items = allocaionObj.map((item) => { let items = allocaionObj.map((item) => {
@ -100,7 +136,7 @@ const Teams = ({ project }) => {
}; };
}); });
submitAllocations(items, true); submitAllocations({ items, added: true });
// Force switch to active view after assignment // Force switch to active view after assignment
setActiveEmployee(true); setActiveEmployee(true);
@ -146,8 +182,12 @@ const Teams = ({ project }) => {
useEffect(() => { useEffect(() => {
fetchEmployees(); if ( projectEmployees )
}, []); {
setEmployees(projectEmployees);
setFilteredEmployees( projectEmployees?.filter( ( emp ) => emp.isActive ) );
}
}, [projectEmployees,employeeLodaing]);
useEffect(() => { useEffect(() => {
if (data) { if (data) {
@ -177,10 +217,10 @@ const Teams = ({ project }) => {
const handler = useCallback( const handler = useCallback(
(msg) => { (msg) => {
if (msg.projectIds.some((item) => item === project.id)) { if (msg.projectIds.some((item) => item === project.id)) {
fetchEmployees(); refetch();
} }
}, },
[] [project.id, refetch]
); );
useEffect(() => { useEffect(() => {
@ -191,9 +231,9 @@ const Teams = ({ project }) => {
const employeeHandler = useCallback( const employeeHandler = useCallback(
(msg) => { (msg) => {
if(filteredEmployees.some((item) => item.employeeId == msg.employeeId)){ if(filteredEmployees.some((item) => item.employeeId == msg.employeeId)){
fetchEmployees(); refetch();
} }
},[filteredEmployees] },[filteredEmployees, refetch]
); );
useEffect(() => { useEffect(() => {
@ -240,7 +280,7 @@ const Teams = ({ project }) => {
message={"Are you sure you want delete?"} message={"Are you sure you want delete?"}
onSubmit={removeAllocation} onSubmit={removeAllocation}
onClose={closeDeleteModal} onClose={closeDeleteModal}
loading={employeeLodaing} loading={isPending}
paramData={deleteEmployee} paramData={deleteEmployee}
/> />
</div> </div>

View File

@ -6,194 +6,430 @@ import { useDispatch, useSelector } from "react-redux";
import { setProjectId } from "../slices/localVariablesSlice"; import { setProjectId } from "../slices/localVariablesSlice";
import EmployeeList from "../components/Directory/EmployeeList"; import EmployeeList from "../components/Directory/EmployeeList";
import eventBus from "../services/eventBus"; import eventBus from "../services/eventBus";
import {Mutation, useMutation, useQuery, useQueryClient} from "@tanstack/react-query";
import showToast from "../services/toastService";
// export const useProjects = () => {
// const loggedUser = useSelector((store) => store.globalVariables.loginUser);
// const [projects, setProjects] = useState([]);
// const [loading, setLoading] = useState(true);
// const [error, setError] = useState("");
// const fetchData = async () => {
// const projectIds = loggedUser?.projects || [];
// const filterProjects = (projectsList) => {
// return projectsList
// .filter((proj) => projectIds.includes(String(proj.id)))
// .sort((a, b) => a?.name?.localeCompare(b.name));
// };
// const projects_cache = getCachedData("projectslist");
// if (!projects_cache) {
// setLoading(true);
// try {
// const response = await ProjectRepository.getProjectList();
// const allProjects = response.data;
// const filtered = filterProjects(allProjects);
// setProjects(filtered);
// cacheData("projectslist", allProjects);
// } catch (err) {
// setError("Failed to fetch data.");
// } finally {
// setLoading(false);
// }
// } else {
// if (!projects.length) {
// const filtered = filterProjects(projects_cache);
// setProjects(filtered);
// setLoading(false);
// }
// }
// };
// useEffect(() => {
// if (loggedUser) {
// fetchData();
// }
// }, [loggedUser]);
// return { projects, loading, error, refetch: fetchData };
// };
// export const useEmployeesByProjectAllocated = (selectedProject) => {
// const [projectEmployees, setEmployeeList] = useState([]);
// const [loading, setLoading] = useState(true);
// const [projects, setProjects] = useState([]);
// const fetchData = async (projectid) => {
// try {
// let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated");
// if (
// !EmployeeByProject_Cache ||
// !EmployeeByProject_Cache.projectId === projectid
// ) {
// let response = await ProjectRepository.getProjectAllocation(projectid);
// setEmployeeList(response.data);
// cacheData("empListByProjectAllocated", {
// data: response.data,
// projectId: projectid,
// });
// setLoading(false);
// } else {
// setEmployeeList(EmployeeByProject_Cache.data);
// setLoading(false);
// }
// } catch (err) {
// setError("Failed to fetch data.");
// setLoading(false);
// }
// };
// useEffect(() => {
// if (selectedProject) {
// fetchData(selectedProject);
// }
// }, [selectedProject]);
// return { projectEmployees, loading, projects };
// };
// export const useProjectDetails = (projectId) => {
// const { profile } = useProfile();
// const [projects_Details, setProject_Details] = useState(null);
// const [loading, setLoading] = useState(true);
// const [error, setError] = useState("");
// const fetchData = async () => {
// setLoading(true);
// const project_cache = getCachedData("projectInfo");
// if (!project_cache || project_cache?.projectId != projectId) {
// ProjectRepository.getProjectByprojectId(projectId)
// .then((response) => {
// setProject_Details(response.data);
// cacheData("projectInfo", {
// projectId: projectId,
// data: response.data,
// });
// setLoading(false);
// })
// .catch((error) => {
// console.error(error);
// setError("Failed to fetch data.");
// setLoading(false);
// });
// } else {
// setProject_Details(project_cache.data);
// setLoading(false);
// }
// };
// useEffect(() => {
// if (profile && projectId != undefined) {
// fetchData();
// }
// }, [projectId, profile]);
// return { projects_Details, loading, error, refetch: fetchData };
// };
// export const useProjectsByEmployee = (employeeId) => {
// const [projectList, setProjectList] = useState([]);
// const [loading, setLoading] = useState(false);
// const [error, setError] = useState("");
// const fetchProjects = async (id) => {
// try {
// setLoading(true);
// setError(""); // clear previous error
// const res = await ProjectRepository.getProjectsByEmployee(id);
// setProjectList(res.data);
// cacheData("ProjectsByEmployee", { data: res.data, employeeId: id });
// setLoading(false);
// } catch (err) {
// setError(err?.message || "Failed to fetch projects");
// setLoading(false);
// }
// };
// useEffect(() => {
// if (!employeeId) return;
// const cache_project = getCachedData("ProjectsByEmployee");
// if (!cache_project?.data || cache_project?.employeeId !== employeeId) {
// fetchProjects(employeeId);
// } else {
// setProjectList(cache_project.data);
// }
// }, [employeeId]);
// return {
// projectList,
// loading,
// error,
// refetch: fetchProjects,
// };
// };
// export const useProjectName = () => {
// const [loading, setLoading] = useState(true);
// const [projectNames, setProjectName] = useState([]);
// const [Error, setError] = useState();
// const dispatch = useDispatch();
// const fetchData = async () => {
// try {
// let response = await ProjectRepository.projectNameList();
// setProjectName(response.data);
// cacheData("basicProjectNameList", response.data);
// setLoading(false);
// if(response.data.length === 1){
// dispatch(setProjectId(response.data[0]?.id));
// }
// } catch (err) {
// setError("Failed to fetch data.");
// setLoading(false);
// }
// };
// useEffect(() => {
// fetchData();
// }, []);
// return { projectNames, loading, Error, fetchData };
// };
// ------------------------------Query-------------------
export const useProjects = () => { export const useProjects = () => {
const loggedUser = useSelector((store) => store.globalVariables.loginUser); const loggedUser = useSelector((store) => store.globalVariables.loginUser);
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const fetchData = async () => { const {
const projectIds = loggedUser?.projects || []; data: projects = [],
isLoading: loading,
const filterProjects = (projectsList) => { error,
return projectsList refetch,
.filter((proj) => projectIds.includes(String(proj.id))) } = useQuery({
.sort((a, b) => a?.name?.localeCompare(b.name)); queryKey: ['ProjectsList'],
}; queryFn: async () => {
const response = await ProjectRepository.getProjectList();
const projects_cache = getCachedData("projectslist"); return response.data;
},
if (!projects_cache) { enabled: !!loggedUser,
setLoading(true); });
try {
const response = await ProjectRepository.getProjectList();
const allProjects = response.data;
const filtered = filterProjects(allProjects);
setProjects(filtered);
cacheData("projectslist", allProjects);
} catch (err) {
setError("Failed to fetch data.");
} finally {
setLoading(false);
}
} else {
if (!projects.length) {
const filtered = filterProjects(projects_cache);
setProjects(filtered);
setLoading(false);
}
}
};
useEffect(() => {
if (loggedUser) {
fetchData();
}
}, [loggedUser]);
return { projects, loading, error, refetch: fetchData };
};
export const useEmployeesByProjectAllocated = (selectedProject) => {
const [projectEmployees, setEmployeeList] = useState([]);
const [loading, setLoading] = useState(true);
const [projects, setProjects] = useState([]);
const fetchData = async (projectid) => {
try {
let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated");
if (
!EmployeeByProject_Cache ||
!EmployeeByProject_Cache.projectId === projectid
) {
let response = await ProjectRepository.getProjectAllocation(projectid);
setEmployeeList(response.data);
cacheData("empListByProjectAllocated", {
data: response.data,
projectId: projectid,
});
setLoading(false);
} else {
setEmployeeList(EmployeeByProject_Cache.data);
setLoading(false);
}
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
}
};
useEffect(() => {
if (selectedProject) {
fetchData(selectedProject);
}
}, [selectedProject]);
return { projectEmployees, loading, projects };
};
export const useProjectDetails = (projectId) => {
const { profile } = useProfile();
const [projects_Details, setProject_Details] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const fetchData = async () => {
setLoading(true);
const project_cache = getCachedData("projectInfo");
if (!project_cache || project_cache?.projectId != projectId) {
ProjectRepository.getProjectByprojectId(projectId)
.then((response) => {
setProject_Details(response.data);
cacheData("projectInfo", {
projectId: projectId,
data: response.data,
});
setLoading(false);
})
.catch((error) => {
console.error(error);
setError("Failed to fetch data.");
setLoading(false);
});
} else {
setProject_Details(project_cache.data);
setLoading(false);
}
};
useEffect(() => {
if (profile && projectId != undefined) {
fetchData();
}
}, [projectId, profile]);
return { projects_Details, loading, error, refetch: fetchData };
};
export const useProjectsByEmployee = (employeeId) => {
const [projectList, setProjectList] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const fetchProjects = async (id) => {
try {
setLoading(true);
setError(""); // clear previous error
const res = await ProjectRepository.getProjectsByEmployee(id);
setProjectList(res.data);
cacheData("ProjectsByEmployee", { data: res.data, employeeId: id });
setLoading(false);
} catch (err) {
setError(err?.message || "Failed to fetch projects");
setLoading(false);
}
};
useEffect(() => {
if (!employeeId) return;
const cache_project = getCachedData("ProjectsByEmployee");
if (!cache_project?.data || cache_project?.employeeId !== employeeId) {
fetchProjects(employeeId);
} else {
setProjectList(cache_project.data);
}
}, [employeeId]);
return { return {
projectList, projects,
loading, loading,
error, error,
refetch: fetchProjects, refetch,
}; };
}; };
export const useProjectName = () => { export const useEmployeesByProjectAllocated = (selectedProject) =>
const [loading, setLoading] = useState(true); {
const [projectNames, setProjectName] = useState([]); const {data = [], isLoading, refetch, error} = useQuery( {
const [Error, setError] = useState(); queryKey: ["empListByProjectAllocated", selectedProject ],
const dispatch = useDispatch(); queryFn: async () =>
{
const res = await ProjectRepository.getProjectAllocation( selectedProject );
return res.data || res
},
enabled:!!selectedProject
} )
const fetchData = async () => { return {
try { projectEmployees: data,
let response = await ProjectRepository.projectNameList(); loading:isLoading,
setProjectName(response.data); error,
cacheData("basicProjectNameList", response.data); refetch
setLoading(false); }
if(response.data.length === 1){ }
dispatch(setProjectId(response.data[0]?.id));
export const useProjectDetails = ( projectId,isAuto = true ) =>
{
const {data: projects_Details, isLoading, error, refetch} = useQuery( {
queryKey: [ "projectInfo", projectId ],
queryFn: async () =>
{
const res = await ProjectRepository.getProjectByprojectId( projectId );
return res.data || res;
},
enabled:!!projectId && isAuto
} )
return { projects_Details, loading:isLoading, error, refetch };
}
export const useProjectsByEmployee = (employeeId) =>
{
const { data:projectNameList =[],isLoading,error,refetch} = useQuery( {
queryKey: [ "ProjectsByEmployee", employeeId ],
queryFn: async () =>
{
const res = await ProjectRepository.getProjectsByEmployee( employeeId );
return res.data || res;
},
enabled: !!employeeId
})
return {projectList, loading:isLoading,error,refetch }
}
export const useProjectName = () =>
{
const {data = [],isLoading,error,refetch} = useQuery( {
queryFn: [ "basicProjectNameList" ],
queryFn: async () =>
{
const res = await ProjectRepository.projectNameList();
return res.data || res;
},
} )
return {projectNames:data,loading:isLoading,Error:error,refetch}
}
// -- Mutation-------------------------------
export const useCreateProject = ({ onSuccessCallback }) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (newProject) => {
const res = await ProjectRepository.manageProject(newProject);
return res.data;
},
onSuccess: (data) => {
// Invalidate the cache
queryClient.invalidateQueries(['ProjectsList']);
// Emit event for consumers (like useProjects or others)
eventBus.emit("project", {
keyword: "Create_Project",
response: data,
});
showToast("Project Created successfully.", "success");
if (onSuccessCallback) {
onSuccessCallback(data);
} }
} catch (err) { },
setError("Failed to fetch data."); onError: (error) => {
setLoading(false); showToast(error.message || "Error while creating project", "error");
} },
}; });
useEffect(() => { };
fetchData();
}, []);
export const useUpdateProject = ({ onSuccessCallback }) => {
return { projectNames, loading, Error, fetchData }; const queryClient = useQueryClient();
const {
mutate,
isPending,
isSuccess,
isError,
} = useMutation({
mutationFn: async ({ projectId, updatedData }) => {
return await ProjectRepository.updateProject(projectId, updatedData);
},
onSuccess: (data, variables) => {
const { projectId } = variables;
// Invalidate queries
queryClient.invalidateQueries(["ProjectsList"]);
queryClient.invalidateQueries(["projectinfo", projectId]);
// Emit update event
eventBus.emit("project", {
keyword: "Update_Project",
response: data,
});
showToast("Project updated successfully.", "success");
if (onSuccessCallback) {
onSuccessCallback(data);
}
},
onError: (error) => {
showToast(error?.message || "Error while updating project", "error");
},
});
return {
mutate,
isPending,
isSuccess,
isError,
};
};
export const useManageProjectInfra = () => {
return useMutation({
mutationFn: async ({infraObject,projectId}) => {
return await ProjectRepository.manageProjectInfra(infraObject);
},
onSuccess: ( response, variables ) =>
{
const { projectId } = variables;
showToast( "Details updated successfully.", "success" );
queryClient.invalidateQueries(["projectinfo", projectId]);
},
onError: (error) => {
showToast(error.message || "Failed to update task details", "error");
},
});
};
export const useManageProjectAllocation = ({
onSuccessCallback,
onErrorCallback,
}) => {
const queryClient = useQueryClient();
const {
mutate,
isPending,
isSuccess,
isError,
} = useMutation({
mutationFn: async ({ items }) => {
const response = await ProjectRepository.manageProjectAllocation(items);
return response.data;
},
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries(['empListByProjectAllocated']);
if (context?.added) {
showToast('Employee Assigned Successfully', 'success');
} else {
showToast('Removed Employee Successfully', 'success');
}
if (onSuccessCallback) onSuccessCallback(data, context);
},
onError: (error) => {
const message =
error?.response?.data?.message || error.message || 'Error occurred during API call';
showToast(message, 'error');
if (onErrorCallback) onErrorCallback(error);
},
});
return {
mutate,
isPending,
isSuccess,
isError,
};
}; };

View File

@ -31,34 +31,35 @@ const ProjectDetails = () => {
projects_Details, projects_Details,
loading: projectLoading, loading: projectLoading,
error: ProjectError, error: ProjectError,
refetch
} = useProjectDetails(projectId); } = useProjectDetails(projectId);
const dispatch = useDispatch(); const dispatch = useDispatch();
const [project, setProject] = useState(null); const [project, setProject] = useState(null);
const [projectDetails, setProjectDetails] = useState(null); // const [projectDetails, setProjectDetails] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); const [error, setError] = useState("");
const fetchData = async () => { // const fetchData = async () => {
const project_cache = getCachedData("projectInfo"); // const project_cache = getCachedData("projectInfo");
if (!project_cache || project_cache?.projectId !== projectId) { // if (!project_cache || project_cache?.projectId !== projectId) {
ProjectRepository.getProjectByprojectId(projectId) // ProjectRepository.getProjectByprojectId(projectId)
.then((response) => { // .then((response) => {
setProjectDetails(response.data); // setProjectDetails(response.data);
setProject(response.data); // setProject(response.data);
cacheData("projectInfo", { projectId, data: response.data }); // cacheData("projectInfo", { projectId, data: response.data });
setLoading(false); // setLoading(false);
}) // })
.catch((error) => { // .catch((error) => {
console.error(error); // console.error(error);
setError("Failed to fetch data."); // setError("Failed to fetch data.");
setLoading(false); // setLoading(false);
}); // });
} else { // } else {
setProjectDetails(project_cache.data); // setProjectDetails(project_cache.data);
setProject(project_cache.data); // setProject(project_cache.data);
setLoading(false); // setLoading(false);
} // }
}; // };
const [activePill, setActivePill] = useState("profile"); const [activePill, setActivePill] = useState("profile");
@ -78,7 +79,7 @@ const ProjectDetails = () => {
<div className="row"> <div className="row">
<div className="col-xl-4 col-lg-5 col-md-5 mt-5"> <div className="col-xl-4 col-lg-5 col-md-5 mt-5">
{/* About User */} {/* About User */}
<AboutProject data={projectDetails}></AboutProject> <AboutProject data={projects_Details}></AboutProject>
{/* About User */} {/* About User */}
</div> </div>
<div className="col-xl-4 col-lg-5 col-md-5 mt-5"> <div className="col-xl-4 col-lg-5 col-md-5 mt-5">
@ -94,7 +95,7 @@ const ProjectDetails = () => {
<div className="row"> <div className="row">
<div className="col-lg-12 col-xl-12"> <div className="col-lg-12 col-xl-12">
{/* Teams */} {/* Teams */}
<Teams project={projectDetails}></Teams> <Teams project={projects_Details}></Teams>
{/* Teams */} {/* Teams */}
</div> </div>
</div> </div>
@ -104,7 +105,7 @@ const ProjectDetails = () => {
case "infra": { case "infra": {
return ( return (
<ProjectInfra <ProjectInfra
data={projectDetails} data={projects_Details}
onDataChange={handleDataChange} onDataChange={handleDataChange}
></ProjectInfra> ></ProjectInfra>
); );
@ -113,7 +114,7 @@ const ProjectDetails = () => {
case "workplan": { case "workplan": {
return ( return (
<WorkPlan <WorkPlan
data={projectDetails} data={projects_Details}
onDataChange={handleDataChange} onDataChange={handleDataChange}
></WorkPlan> ></WorkPlan>
); );
@ -122,7 +123,7 @@ const ProjectDetails = () => {
case "directory": { case "directory": {
return ( return (
<div className="row"> <div className="row">
<Directory IsPage={false} prefernceContacts={projectDetails.id} /> <Directory IsPage={false} prefernceContacts={projects_Details.id} />
</div> </div>
); );
} }
@ -134,26 +135,27 @@ const ProjectDetails = () => {
useEffect(() => { useEffect(() => {
dispatch(setProjectId(projectId)); dispatch(setProjectId(projectId));
setProject(projects_Details); // setProject(projects_Details);
setProjectDetails(projects_Details); // setProjectDetails(projects_Details);
}, [projects_Details, projectId]); }, [projects_Details, projectId]);
const handler = useCallback( const handler = useCallback(
(msg) => { (msg) => {
if (msg.keyword === "Update_Project" && project.id === msg.response.id) { if (msg.keyword === "Update_Project" && project.id === msg.response.id) {
clearCacheKey("projectInfo") // clearCacheKey("projectInfo")
ProjectRepository.getProjectByprojectId(projectId) // ProjectRepository.getProjectByprojectId(projectId)
.then((response) => { // .then((response) => {
setProjectDetails(response.data); // setProjectDetails(response.data);
setProject(response.data); // setProject(response.data);
cacheData("projectInfo", { projectId, data: response.data }); // cacheData("projectInfo", { projectId, data: response.data });
setLoading(false); // setLoading(false);
}) // })
.catch((error) => { // .catch((error) => {
console.error(error); // console.error(error);
setError("Failed to fetch data."); // setError("Failed to fetch data.");
setLoading(false); // setLoading(false);
}); // });
refetch()
} }
}, },
[project,handleDataChange] [project,handleDataChange]

View File

@ -3,10 +3,14 @@ import ProjectCard from "../../components/Project/ProjectCard";
import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import ProjectRepository from "../../repositories/ProjectRepository"; import ProjectRepository from "../../repositories/ProjectRepository";
import { useProjects } from "../../hooks/useProjects"; import { useProjects, useCreateProject } from "../../hooks/useProjects";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { getCachedData, cacheData, clearCacheKey } from "../../slices/apiDataManager"; // import {
// getCachedData,
// cacheData,
// clearCacheKey,
// } from "../../slices/apiDataManager";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants"; import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants";
@ -14,6 +18,9 @@ import ProjectListView from "./ProjectListView";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { clearApiCacheKey } from "../../slices/apiCacheSlice"; import { clearApiCacheKey } from "../../slices/apiCacheSlice";
import { defaultCheckBoxAppearanceProvider } from "pdf-lib"; import { defaultCheckBoxAppearanceProvider } from "pdf-lib";
import { useMutation } from "@tanstack/react-query";
import usePagination from "../../hooks/usePagination";
import GlobalModel from "../../components/common/GlobalModel";
const ProjectList = () => { const ProjectList = () => {
const { profile: loginUser } = useProfile(); const { profile: loginUser } = useProfile();
@ -26,9 +33,12 @@ const ProjectList = () => {
HasManageProjectPermission HasManageProjectPermission
); );
const dispatch = useDispatch(); const dispatch = useDispatch();
const { mutate: createProject } = useCreateProject({
onSuccessCallback: () => {
setShowModal(false);
},
});
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(ITEMS_PER_PAGE);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [selectedStatuses, setSelectedStatuses] = useState([ const [selectedStatuses, setSelectedStatuses] = useState([
"b74da4c2-d07e-46f2-9919-e75e49b12731", "b74da4c2-d07e-46f2-9919-e75e49b12731",
@ -72,29 +82,33 @@ const ProjectList = () => {
} }
}, [loginUser, HasManageProjectPermission]); }, [loginUser, HasManageProjectPermission]);
// const handleSubmitForm = (newProject, setloading, reset) => {
// ProjectRepository.manageProject(newProject)
// .then((response) => {
// const cachedProjects = getCachedData("projectslist") || [];
// const updatedProjects = [...cachedProjects, response.data];
// cacheData("projectslist", updatedProjects);
// setProjectList((prev) => [...prev, response.data]);
// setloading(false);
// reset();
// sortingProject(getCachedData("projectslist"));
// showToast("Project Created successfully.", "success");
// setShowModal(false);
// })
// .catch((error) => {
// showToast(error.message, "error");
// setShowModal(false);
// });
// };
const handleSubmitForm = (newProject, setloading, reset) => { const handleSubmitForm = (newProject, setloading, reset) => {
ProjectRepository.manageProject(newProject) setloading(true);
.then((response) => { createProject(newProject, {
const cachedProjects = getCachedData("projectslist") || []; onSettled: () => {
const updatedProjects = [...cachedProjects, response.data];
cacheData("projectslist", updatedProjects);
setProjectList((prev) => [...prev, response.data]);
setloading(false); setloading(false);
reset(); reset();
sortingProject(getCachedData("projectslist")); },
showToast("Project Created successfully.", "success"); });
setShowModal(false);
})
.catch((error) => {
showToast(error.message, "error");
setShowModal(false);
});
};
const handleReFresh = () => {
if (!projects || projects.length === 0) {
refetch();
}
}; };
const handleStatusChange = (statusId) => { const handleStatusChange = (statusId) => {
@ -118,13 +132,11 @@ const ProjectList = () => {
return matchesStatus && matchesSearch; return matchesStatus && matchesSearch;
}); });
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage; const totalPages = Math.ceil( filteredProjects.length / ITEMS_PER_PAGE );
const currentItems = filteredProjects.slice(
indexOfFirstItem, const {currentItems,currentPage,paginate,setCurrentPage} = usePagination(filteredProjects, ITEMS_PER_PAGE)
indexOfLastItem
);
const totalPages = Math.ceil(filteredProjects.length / itemsPerPage);
useEffect(() => { useEffect(() => {
const tooltipTriggerList = Array.from( const tooltipTriggerList = Array.from(
document.querySelectorAll('[data-bs-toggle="tooltip"]') document.querySelectorAll('[data-bs-toggle="tooltip"]')
@ -132,67 +144,20 @@ const ProjectList = () => {
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []); }, []);
const handler = useCallback(
async (msg) => {
if (HasManageProject && msg.keyword === "Create_Project") {
const updatedProjects = [...projectList, msg.response];
cacheData("projectslist", updatedProjects);
setProjectList(updatedProjects);
sortingProject(updatedProjects);
}
if (
msg.keyword === "Update_Project" &&
projectList.some((item) => item.id === msg.response.id)
) {
ProjectRepository.getProjectList()
.then((response) => {
cacheData("projectslist", response?.data);
sortingProject(response?.data);
})
.catch((e) => {
console.error(e)
});
}
},
[HasManageProject, projectList, sortingProject]
);
useEffect(() => {
eventBus.on("project", handler);
return () => eventBus.off("project", handler);
}, [handler]);
const assignProjectHandler = useCallback(
async (data) => {
clearCacheKey("projectslist");
await refetch();
sortingProject(projects);
},
[refetch]
);
useEffect(() => {
eventBus.on("assign_project_one", assignProjectHandler);
return () => eventBus.off("assign_project_one", assignProjectHandler);
}, [handler]);
return ( return (
<> <>
<div
className={`modal fade ${showModal ? "show" : ""}`} {showModal && (
tabIndex="-1" <GlobalModel isOpen={showModal} closeModal={handleClose}>
role="dialog" <ManageProjectInfo
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<ManageProjectInfo
project={null} project={null}
handleSubmitForm={handleSubmitForm} handleSubmitForm={handleSubmitForm}
onClose={handleClose} onClose={handleClose}
/> />
</div> </GlobalModel>
)}
<div className="container-xxl flex-grow-1 container-p-y"> <div className="container-xxl flex-grow-1 container-p-y">
<Breadcrumb <Breadcrumb

View File

@ -1,6 +1,10 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import moment from "moment"; import moment from "moment";
import { useProjects } from "../../hooks/useProjects"; import {
useProjectDetails,
useProjects,
useUpdateProject,
} from "../../hooks/useProjects";
import { import {
getProjectStatusName, getProjectStatusName,
getProjectStatusColor, getProjectStatusColor,
@ -14,25 +18,35 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { getCachedData, cacheData } from "../../slices/apiDataManager"; import { getCachedData, cacheData } from "../../slices/apiDataManager";
import GlobalModel from "../../components/common/GlobalModel";
const ProjectListView = ({ projectData, recall }) => { const ProjectListView = ({ projectData, recall }) => {
const [projectInfo, setProjectInfo] = useState(projectData); const [projectInfo, setProjectInfo] = useState(projectData);
const [projectDetails, setProjectDetails] = useState(null); const { projects_Details, loading, error, refetch } = useProjectDetails(
projectInfo?.id,false
);
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const ManageProject = useHasUserPermission(MANAGE_PROJECT); const ManageProject = useHasUserPermission(MANAGE_PROJECT);
useEffect(()=>{ useEffect(() => {
setProjectInfo(projectData); setProjectInfo(projectData);
},[projectData]) }, [projectData]);
const {
mutate: updateProject,
isPending,
isSuccess,
isError,
} = useUpdateProject({
onSuccessCallback: () => {
setShowModal(false);
},
})
const handleShow = async () => { const handleShow = async () => {
try { try {
const response = await ProjectRepository.getProjectByprojectId( const { data } = await refetch();
projectInfo.id
);
setProjectDetails(response.data);
setShowModal(true); setShowModal(true);
} catch (error) { } catch (err) {
showToast("Failed to load project details", "error"); showToast("Failed to load project details", "error");
} }
}; };
@ -52,45 +66,54 @@ const ProjectListView = ({ projectData, recall }) => {
const handleFormSubmit = (updatedProject) => { const handleFormSubmit = (updatedProject) => {
if (projectInfo?.id) { if (projectInfo?.id) {
ProjectRepository.updateProject(projectInfo.id, updatedProject) updateProject({
.then((response) => { projectId: projectInfo.id,
const updatedProjectData = { updatedData: updatedProject,
...projectInfo, });
...response.data,
building: projectDetails?.building,
};
setProjectInfo(updatedProjectData);
if (getCachedData(`projectinfo-${projectInfo.id}`)) {
cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
}
const projects_list = getCachedData("projectslist");
if (projects_list) {
const updatedProjectsList = projects_list.map((project) =>
project.id === projectInfo.id
? {
...project,
...response.data,
// tenant: project.tenant
}
: project
);
cacheData("projectslist", updatedProjectsList);
}
recall(getCachedData("projectslist"));
showToast("Project updated successfully.", "success");
setShowModal(false);
})
.catch((error) => {
showToast(error.message, "error");
});
} }
}; };
// const handleFormSubmit = (updatedProject) => {
// if (projectInfo?.id) {
// ProjectRepository.updateProject(projectInfo.id, updatedProject)
// .then((response) => {
// const updatedProjectData = {
// ...projectInfo,
// ...response.data,
// building: projectDetails?.building,
// };
// setProjectInfo(updatedProjectData);
// if (getCachedData(`projectinfo-${projectInfo.id}`)) {
// cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
// }
// const projects_list = getCachedData("projectslist");
// if (projects_list) {
// const updatedProjectsList = projects_list.map((project) =>
// project.id === projectInfo.id
// ? {
// ...project,
// ...response.data,
// // tenant: project.tenant
// }
// : project
// );
// cacheData("projectslist", updatedProjectsList);
// }
// recall(getCachedData("projectslist"));
// showToast("Project updated successfully.", "success");
// setShowModal(false);
// })
// .catch((error) => {
// showToast(error.message, "error");
// });
// }
// };
return ( return (
<> <>
{showModal && projectDetails && ( {/* {showModal && projects_Details && (
<tr> <tr>
<td <td
className="modal fade show" className="modal fade show"
@ -100,15 +123,22 @@ const ProjectListView = ({ projectData, recall }) => {
aria-hidden="false" aria-hidden="false"
> >
<ManageProjectInfo <ManageProjectInfo
project={projectDetails} project={projects_Details}
handleSubmitForm={handleFormSubmit} handleSubmitForm={handleFormSubmit}
onClose={handleClose} onClose={handleClose}
/> />
</td> </td>
</tr> </tr>
)} */}
{showModal && projects_Details && (
<GlobalModel isOpen={showModal} closeModal={handleClose}> <ManageProjectInfo
project={projects_Details}
handleSubmitForm={handleFormSubmit}
onClose={handleClose}
/></GlobalModel>
)} )}
<tr className="py-8"> <tr className={`py-8 ${isPending ? "bg-light opacity-50 pointer-events-none" : ""} `}>
<td className="text-start" colSpan={5}> <td className="text-start" colSpan={5}>
<strong <strong
className="text-primary cursor-pointer" className="text-primary cursor-pointer"
@ -162,14 +192,23 @@ const ProjectListView = ({ projectData, recall }) => {
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false" aria-expanded="false"
> >
<i {loading ? (
className="bx bx-dots-vertical-rounded bx-sm text-muted" <div
data-bs-toggle="tooltip" class="spinner-border spinner-border-sm text-secondary"
data-bs-offset="0,8" role="status"
data-bs-placement="top" >
data-bs-custom-class="tooltip-dark" <span class="visually-hidden">Loading...</span>
title="More Action" </div>
></i> ) : (
<i
className="bx bx-dots-vertical-rounded bx-sm text-muted"
data-bs-toggle="tooltip"
data-bs-offset="0,8"
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
)}
</button> </button>
<ul className="dropdown-menu dropdown-menu-end"> <ul className="dropdown-menu dropdown-menu-end">
<li> <li>