From d19652a98ed68fe08b36abbfa43962fb1a86c2c4 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Sat, 7 Jun 2025 00:07:42 +0530 Subject: [PATCH 1/8] added new hook for fetching projects associated by perticular employee --- src/hooks/useProjects.js | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index 09582880..cafb3053 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -4,6 +4,7 @@ import ProjectRepository from "../repositories/ProjectRepository"; import { useProfile } from "./useProfile"; import { useDispatch, useSelector } from "react-redux"; import { setProjectId } from "../slices/localVariablesSlice"; +import EmployeeList from "../components/Directory/EmployeeList"; export const useProjects = () => { @@ -130,3 +131,47 @@ export const useProjectDetails = (projectId) => { 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 }); + } catch (err) { + setError(err?.message || 'Failed to fetch projects'); + } finally { + 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 + + } +}; \ No newline at end of file -- 2.43.0 From a3dcd9bee3f99e55fec60c1abf1d40313363e7a4 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Sat, 7 Jun 2025 00:08:25 +0530 Subject: [PATCH 2/8] created component for assign and unassign projects for employee --- src/pages/employee/AssignToProject.jsx | 165 +++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/pages/employee/AssignToProject.jsx diff --git a/src/pages/employee/AssignToProject.jsx b/src/pages/employee/AssignToProject.jsx new file mode 100644 index 00000000..04d5f7a3 --- /dev/null +++ b/src/pages/employee/AssignToProject.jsx @@ -0,0 +1,165 @@ +import React, { useState, useEffect } from "react"; +import { useProjects, useProjectsByEmployee } from "../../hooks/useProjects"; +import EmployeeList from "./EmployeeList"; +import showToast from "../../services/toastService"; +import ProjectRepository from "../../repositories/ProjectRepository"; + +const AssignToProject = ({ employee, onClose }) => { + const { projects, loading } = useProjects(); + const { projectList } = useProjectsByEmployee(employee?.id); + + const [searchTerm, setSearchTerm] = useState(""); + const [checkedProjects, setCheckedProjects] = useState({}); + const [selectedEmployees, setSelectedEmployees] = useState([]); + + useEffect(() => { + if (projectList && projectList.length > 0) { + const initialChecked = {}; + const initialSelected = []; + + projectList.forEach((project) => { + initialChecked[project.id] = true; + initialSelected.push({ + empID: employee.id, + jobRoleId: employee.jobRoleId, + projectId: project.id, + status: true, + }); + }); + + setCheckedProjects(initialChecked); + setSelectedEmployees(initialSelected); + } else { + setCheckedProjects({}); + setSelectedEmployees([]); + } + }, [projectList, employee?.id]); + + const handleSearchChange = (e) => { + setSearchTerm(e.target.value.toLowerCase()); + }; + + const handleCheckboxChange = (projectId) => { + const isChecked = !checkedProjects[projectId]; + + setCheckedProjects((prev) => ({ + ...prev, + [projectId]: isChecked, + })); + + setSelectedEmployees((prev) => { + const index = prev.findIndex((emp) => emp.projectId === projectId && emp.empID === employee.id); + const newEntry = { + empID: employee.id, + jobRoleId: employee.jobRoleId, + projectId, + status: isChecked, + }; + + if (index === -1) { + return [...prev, newEntry]; + } else { + const updated = [...prev]; + updated[index].status = isChecked; + return updated; + } + }); + }; + + const handleSubmit = async () => + { + console.log(selectedEmployees) + try + { + const resp = await ProjectRepository.updatesAssignProject( employee.id, selectedEmployees ) + console.group(resp.data) + + }catch(error) + { + const msg = error.response.data.message || error.message || "Error occured during API calling."; + showToast( msg, "error" ); +} }; + + const handleClosedModal = () => { + onClose(); + }; + + const filteredProjects = projects.filter((project) => + project.name.toLowerCase().includes(searchTerm) + ); + + return ( +
+

Assign to Project

+ +
+
+ +
+
+ + {loading ? ( +
+
+

Loading projects...

+
+ ) : ( + <> + + + + + + + + {filteredProjects.length > 0 ? ( + filteredProjects.map((project) => ( + + + + )) + ) : ( + + + + )} + +
Select Project
+
+ handleCheckboxChange(project.id)} + /> + +
+
No projects found.
+ +
+ + +
+ + )} +
+ ); +}; + +export default AssignToProject; -- 2.43.0 From 7baffac11a21db9077fbea0507d7037b5e3a10b7 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Sat, 7 Jun 2025 00:09:23 +0530 Subject: [PATCH 3/8] added modal for AssignToProject --- src/pages/employee/EmployeeList.jsx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index f31cff37..ae597378 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -22,6 +22,8 @@ import { import EmployeeRepository from "../../repositories/EmployeeRepository"; import ManageEmployee from "../../components/Employee/ManageEmployee"; import ConfirmModal from "../../components/common/ConfirmModal"; +import GlobalModel from "../../components/common/GlobalModel"; +import AssignToProject from "./AssignToProject"; const EmployeeList = () => { const { profile: loginUser } = useProfile(); @@ -47,7 +49,8 @@ const EmployeeList = () => { const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null); const [employeeLodaing, setemployeeLodaing] = useState(false); - + const [ selectedEmployee, setSelectEmployee ] = useState( null ) + const [IsOpenAsssingModal,setOpenAssignModal] = useState(false) const navigate = useNavigate(); const handleSearch = (e) => { @@ -240,6 +243,11 @@ const EmployeeList = () => {
)} + {IsOpenAsssingModal && ( setOpenAssignModal(false)}> + setOpenAssignModal( false )} /> + )} + +
{ {" "} Manage Role + )}
-- 2.43.0 From 74a5c05481a27298ca3db4679a23e83a1a4b8bb1 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Sat, 7 Jun 2025 00:10:36 +0530 Subject: [PATCH 4/8] added api's for fetching projects by employee and update project assigning for employee --- src/repositories/ProjectRepository.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index 3425d2b0..2ec724ed 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -20,7 +20,10 @@ const ProjectRepository = { deleteProjectTask:(id)=> api.delete(`/api/project/task/${id}`), updateProject: (id, data) => api.put(`/api/project/update/${id}`, data), - deleteProject: (id) => api.delete(`/projects/${id}`), + deleteProject: ( id ) => api.delete( `/projects/${ id }` ), + getProjectsByEmployee: ( id ) => api.get( `/api/project/assigned-projects/${ id }` ), + + updatesAssignProject:(id,data)=>api.post(`/api/project/assign-projects/${id}`,data), }; export const TasksRepository = { -- 2.43.0 From fe87e0dbd4eae95e716f949ad011771a160790cb Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Sun, 8 Jun 2025 18:40:40 +0530 Subject: [PATCH 5/8] added update assign project api --- src/hooks/useProjects.js | 3 ++- src/pages/employee/AssignToProject.jsx | 5 ++--- src/pages/employee/EmployeeList.jsx | 9 +++++++-- src/repositories/ProjectRepository.jsx | 3 +-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index cafb3053..996723ef 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -174,4 +174,5 @@ export const useProjectsByEmployee = ( employeeId ) => error } -}; \ No newline at end of file +}; + diff --git a/src/pages/employee/AssignToProject.jsx b/src/pages/employee/AssignToProject.jsx index 04d5f7a3..7c90d021 100644 --- a/src/pages/employee/AssignToProject.jsx +++ b/src/pages/employee/AssignToProject.jsx @@ -50,7 +50,6 @@ const AssignToProject = ({ employee, onClose }) => { setSelectedEmployees((prev) => { const index = prev.findIndex((emp) => emp.projectId === projectId && emp.empID === employee.id); const newEntry = { - empID: employee.id, jobRoleId: employee.jobRoleId, projectId, status: isChecked, @@ -71,8 +70,8 @@ const AssignToProject = ({ employee, onClose }) => { console.log(selectedEmployees) try { - const resp = await ProjectRepository.updatesAssignProject( employee.id, selectedEmployees ) - console.group(resp.data) + // const resp = await ProjectRepository.updatesAssignProject( employee.id, selectedEmployees ) + // console.group(resp.data) }catch(error) { diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index ae597378..38cee0b7 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -192,7 +192,11 @@ const EmployeeList = () => { setSelectedEmpFordelete(employee); setIsDeleteModalOpen(true); }; - + const handleCloseAssignModal = () => + { + setOpenAssignModal( false ) + setSelectEmployee(null) + } return ( <> {isCreateModalOpen && ( @@ -216,7 +220,8 @@ const EmployeeList = () => { /> - )} + )} + {IsDeleteModalOpen && (
api.put(`/api/project/update/${id}`, data), deleteProject: ( id ) => api.delete( `/projects/${ id }` ), getProjectsByEmployee: ( id ) => api.get( `/api/project/assigned-projects/${ id }` ), - - updatesAssignProject:(id,data)=>api.post(`/api/project/assign-projects/${id}`,data), + updateProjectsByEmployee:(id,data)=>api.post(`/api/project/assign-projects/${id}`,data) }; export const TasksRepository = { -- 2.43.0 From 0b46dc0f1f25ea50378c5cf73025d6d2c4601b31 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Mon, 9 Jun 2025 10:01:58 +0530 Subject: [PATCH 6/8] added refetch function for recall after takng any assign or unassign project for update --- src/hooks/useProjects.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index 996723ef..62641178 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -145,12 +145,12 @@ export const useProjectsByEmployee = ( employeeId ) => setError(''); // clear previous error const res = await ProjectRepository.getProjectsByEmployee(id); setProjectList(res.data); - cacheData('ProjectsByEmployee', { data: res.data, employeeId: id }); + cacheData( 'ProjectsByEmployee', {data: res.data, employeeId: id} ); + setLoading(false) } catch (err) { - setError(err?.message || 'Failed to fetch projects'); - } finally { - setLoading(false); - } + setError( err?.message || 'Failed to fetch projects' ); + setLoading(false) + } }; useEffect(() => { @@ -171,8 +171,8 @@ export const useProjectsByEmployee = ( employeeId ) => return { projectList, loading, - error - + error, + refetch : fetchProjects } }; -- 2.43.0 From f129645386fefe022ad3ccd1841353265697a915 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Mon, 9 Jun 2025 10:03:32 +0530 Subject: [PATCH 7/8] Updated to make sure the client sends the correct request. Unnecessary requests are no longer sent. --- src/pages/employee/AssignToProject.jsx | 100 +++++++++++++++---------- 1 file changed, 59 insertions(+), 41 deletions(-) diff --git a/src/pages/employee/AssignToProject.jsx b/src/pages/employee/AssignToProject.jsx index 7c90d021..891b50df 100644 --- a/src/pages/employee/AssignToProject.jsx +++ b/src/pages/employee/AssignToProject.jsx @@ -6,8 +6,8 @@ import ProjectRepository from "../../repositories/ProjectRepository"; const AssignToProject = ({ employee, onClose }) => { const { projects, loading } = useProjects(); - const { projectList } = useProjectsByEmployee(employee?.id); - + const { projectList,loading:selectedProjectLoding ,refetch} = useProjectsByEmployee(employee?.id); + const [isSubmitting,setSubmitting] = useState(false) const [searchTerm, setSearchTerm] = useState(""); const [checkedProjects, setCheckedProjects] = useState({}); const [selectedEmployees, setSelectedEmployees] = useState([]); @@ -20,7 +20,6 @@ const AssignToProject = ({ employee, onClose }) => { projectList.forEach((project) => { initialChecked[project.id] = true; initialSelected.push({ - empID: employee.id, jobRoleId: employee.jobRoleId, projectId: project.id, status: true, @@ -39,45 +38,62 @@ const AssignToProject = ({ employee, onClose }) => { setSearchTerm(e.target.value.toLowerCase()); }; - const handleCheckboxChange = (projectId) => { - const isChecked = !checkedProjects[projectId]; +const handleCheckboxChange = (projectId) => { + const isChecked = !checkedProjects[projectId]; - setCheckedProjects((prev) => ({ - ...prev, - [projectId]: isChecked, - })); + setCheckedProjects((prev) => ({ + ...prev, + [projectId]: isChecked, + })); +}; - setSelectedEmployees((prev) => { - const index = prev.findIndex((emp) => emp.projectId === projectId && emp.empID === employee.id); - const newEntry = { + +const handleSubmit = async () => { + const initiallyAssigned = new Set(projectList.map((p) => p.id.toString())); + const changes = []; + + Object.entries(checkedProjects).forEach(([projectId, isChecked]) => { + const wasAssigned = initiallyAssigned.has(projectId); + + if (wasAssigned && !isChecked) { + changes.push({ + projectId: projectId, jobRoleId: employee.jobRoleId, - projectId, - status: isChecked, - }; + status: false, + }); + } + + if (!wasAssigned && isChecked) { + changes.push({ + projectId: projectId, + jobRoleId: employee.jobRoleId, + status: true, + }); + } + + }); + + if (changes.length === 0) { + showToast("Make change before.", "info"); + return; + } + + try { + setSubmitting(true) + await ProjectRepository.updateProjectsByEmployee(employee.id, changes) + showToast( "Project assignments updated.", "success" ); + setSubmitting(false) + onClose(); + refetch(employee.id) + } catch (error) { + const msg = error.response?.data?.message || error.message || "Error during API call."; + showToast( msg, "error" ); + setSubmitting(false) + } +}; + - if (index === -1) { - return [...prev, newEntry]; - } else { - const updated = [...prev]; - updated[index].status = isChecked; - return updated; - } - }); - }; - const handleSubmit = async () => - { - console.log(selectedEmployees) - try - { - // const resp = await ProjectRepository.updatesAssignProject( employee.id, selectedEmployees ) - // console.group(resp.data) - - }catch(error) - { - const msg = error.response.data.message || error.message || "Error occured during API calling."; - showToast( msg, "error" ); -} }; const handleClosedModal = () => { onClose(); @@ -127,7 +143,9 @@ const AssignToProject = ({ employee, onClose }) => { type="checkbox" id={`project-${project.id}`} checked={checkedProjects[project.id] || false} - onChange={() => handleCheckboxChange(project.id)} + onChange={() => handleCheckboxChange( project.id )} + disabled={selectedProjectLoding} + />