diff --git a/src/components/UserProfile/TeamsAndProjects/AddProjectPopup.jsx b/src/components/UserProfile/TeamsAndProjects/AddProjectPopup.jsx index 31901680bb..3ef5430073 100644 --- a/src/components/UserProfile/TeamsAndProjects/AddProjectPopup.jsx +++ b/src/components/UserProfile/TeamsAndProjects/AddProjectPopup.jsx @@ -113,7 +113,6 @@ const AddProjectPopup = React.memo(props => { await dispatch(assignProject(selectedProject._id, props.userId, 'Assign')); // ✅ Make sure we're passing the complete project object props.onSelectAssignProject?.(selectedProject); - toast.success(`Assigned to "${selectedProject.projectName}".`); props.onClose?.(); } catch (e) { // eslint-disable-next-line no-console diff --git a/src/components/UserProfile/TeamsAndProjects/UserProjectsTable.jsx b/src/components/UserProfile/TeamsAndProjects/UserProjectsTable.jsx index 14eb0a4c5c..4efcb5ef65 100644 --- a/src/components/UserProfile/TeamsAndProjects/UserProjectsTable.jsx +++ b/src/components/UserProfile/TeamsAndProjects/UserProjectsTable.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useMemo } from 'react'; -import { Button, Col, Tooltip , NavItem, UncontrolledTooltip } from 'reactstrap'; +import PropTypes from "prop-types"; +import { Button, Col, UncontrolledTooltip } from 'reactstrap'; import './TeamsAndProjects.css'; import hasPermission from '../../../utils/permissions'; // import styles from './UserProjectsTable.css'; @@ -33,7 +34,7 @@ const UserProjectsTable = React.memo(props => { const currentRoute = location.pathname; const isUserProfilePage = currentRoute === '/usermanagement'; - const toggleTooltip = () => setTooltip(!tooltipOpen); + // const toggleTooltip = () => setTooltip(!tooltipOpen); //Situation can be all, active, or complete const filterTasksAndUpdateFilter = situation => { @@ -42,7 +43,7 @@ const UserProjectsTable = React.memo(props => { const sortedTasksByNumber = useMemo(() => { return userTasks?.sort((task1, task2) => task1.num - task2.num); - }, [userTasks]); +}, [userTasks]); const tasksByProject = userProjects?.map(project => { const tasks = sortedTasksByNumber?.filter(task => task.projectId?.includes(project.projectId)); @@ -81,26 +82,35 @@ const UserProjectsTable = React.memo(props => { setFilteredTasks(() => filterTasksByUserTaskSituation(actualType)); }, [sortedTasksByNumber, actualType]); - const removeOrAddTaskFromUser = (task, method) => { - const newResources = task.resources?.map(resource => { - if (resource.userID === props.userId) { - return { - ...resource, - completedTask: method === 'remove' - }; +const removeOrAddTaskFromUser = (task, method) => { + + let newResources = task.resources; + + if (method === "remove") { + // REMOVE the user completely + newResources = task.resources.filter(r => r.userID !== props.userId); + } else if (method === "add") { + // RE-ADD user as uncompleted resource + newResources = [ + ...task.resources, + { + userID: props.userId, + completedTask: method === 'remove', + reviewStatus: "Unsubmitted", + startedDatetime: new Date().toISOString() } - return resource; - }); - - const updatedTask = { - ...task, + ]; + } + + const updatedTask = { + ...task, resources: newResources, status: method === 'remove' ? 'Complete' : 'Started' - }; - - props.updateTask(task._id, updatedTask, method); }; + props.updateTask(task._id, updatedTask, method); +}; + //For updating tasks visually but not saving until user clicks save changes const deleteTasksTemporarily = (project_id) => { setFilteredTasks(filteredTasks?.filter(project => project.projectId !== project_id )); @@ -108,7 +118,7 @@ const UserProjectsTable = React.memo(props => { useEffect(()=>{ setFilteredTasks(() => filterTasksByUserTaskSituation('active')); - }, [props.userProjectsById]) + }, [props.userProjectsById], filteredTasks); return ( @@ -126,9 +136,9 @@ const UserProjectsTable = React.memo(props => { {props.disabled ? ( <> - + {/* Please save changes before assign project - + */} @@ -380,7 +390,7 @@ const UserProjectsTable = React.memo(props => { {props.userProjectsById.length > 0 ? ( - tasksByProject?.map((project, index) => ( + filteredTasks?.map((project, index) => ( {index + 1} {`${project.projectName}`} @@ -391,7 +401,6 @@ const UserProjectsTable = React.memo(props => { disabled={!canUpdateTask} onClick={e => { props.onDeleteClick(project.projectId); - deleteTasksTemporarily(project.projectId); }} style={darkMode ? boxStyleDark : boxStyle} > @@ -513,4 +522,15 @@ const UserProjectsTable = React.memo(props => { ); }); -export default connect(null, { hasPermission })(UserProjectsTable); \ No newline at end of file +export default connect(null, { hasPermission })(UserProjectsTable); +UserProjectsTable.propTypes = { + userId: PropTypes.string, + userProjectsById: PropTypes.array, + userTasks: PropTypes.array, + role: PropTypes.string, + edit: PropTypes.bool, + hasPermission: PropTypes.func, + onDeleteClick: PropTypes.func, + updateTask: PropTypes.func, + darkMode: PropTypes.bool, +}; diff --git a/src/components/UserProfile/UserProfile.jsx b/src/components/UserProfile/UserProfile.jsx index 30ba398c3b..82424a49fb 100644 --- a/src/components/UserProfile/UserProfile.jsx +++ b/src/components/UserProfile/UserProfile.jsx @@ -555,62 +555,111 @@ function UserProfile(props) { setTeams(prevTeams => prevTeams.filter(team => team._id !== deletedTeamId)); }; - const onDeleteProject = deletedProjectId => { - setProjects(prevProject => prevProject.filter(project => project._id !== deletedProjectId)); + const onDeleteProject = async (deletedProjectId) => { + + const removedProject = projects.find(p => (p._id || p.projectId) === deletedProjectId); + + const updatedProjects = projects.filter(p => { + return (p._id || p.projectId) !== deletedProjectId; + }); + + setProjects(updatedProjects); + + // Prepare backend payload + const updatedUserProfile = { + ...userProfileRef.current, + projects: updatedProjects.map(p => String(p._id || p.projectId)), }; + try { + await handleSubmit(updatedUserProfile); // this already toasts success + toast.success(`User removed from Project "${removedProject?.projectName || 'Unknown'}"`); + } catch (e) { + toast.error('Failed to remove project, please try again.'); + console.error(e); + } + return updatedProjects; +}; + + const onAssignTeam = assignedTeam => { setTeams(prevState => [...prevState, assignedTeam]); }; -const onAssignProject = assignedProject => { - // eslint-disable-next-line no-console - console.log("Adding project to state:", assignedProject); +const onAssignProject = async (assignedProject) => { + const projectId = assignedProject._id || assignedProject.projectId; + + // Avoid duplicates + const currentProjects = Array.isArray(projects) ? projects : []; + if (currentProjects.some(p => (p._id || p.projectId) === projectId)) { + toast.info(`Project "${assignedProject.projectName || 'Unknown'}" already assigned to this user`); + return; + } + + const updatedProjects = [...currentProjects, assignedProject]; + setProjects(updatedProjects); + + const updatedUserProfile = { + ...userProfileRef.current, + projects: updatedProjects.map(p => String(p._id || p.projectId)), + }; + + try { + await handleSubmit(updatedUserProfile); // reuses same pipeline + toast.success(`User assigned to Project "${assignedProject.projectName || 'Unknown'}"`); + } catch (e) { + toast.error('Failed to assign project, please try again.'); + console.error(e); + } + return updatedProjects; +}; + +const onUpdateTask = async (taskId, updatedTask, method) => { - // Always create a new array to trigger React re-render - setProjects(prevProjects => - { - // Ensure prevProjects is an array - const currentProjects = Array.isArray(prevProjects) ? prevProjects : []; - - if (currentProjects.some(proj => proj._id === assignedProject._id)) { - // eslint-disable-next-line no-console - console.log("Project already exists, not adding duplicate"); - return currentProjects; + let newTasks; + + if (method === 'remove') { + try{ + newTasks = tasks.filter(t => t._id !== taskId); + const res = await axios.delete(ENDPOINTS.TASK_DELETE_BY_ID(taskId, userProfile._id)); + if (res.status === 200) { + setTasks(newTasks); + toast.success('Task removed successfully'); + } else { + toast.error('Failed to remove task'); } - - // Add project and log the new state - // eslint-disable-next-line no-console - console.log("Adding new project:", assignedProject.projectName); - const newProjects = [...currentProjects, assignedProject]; - // eslint-disable-next-line no-console - console.log("Updated projects state:", newProjects); - return newProjects; // Return the new array with the project added - }); + return newTasks; + } catch (e) { + toast.error('Failed to remove task, please try again.'); + console.error(e); + return tasks; + } + } else { + // UPDATE the task normally + newTasks = tasks.map(t => + t._id === taskId ? updatedTask : t + ); + } + setTasks(newTasks); + + const updatedUserProfile = { + ...userProfileRef.current, + tasks: newTasks }; - const onUpdateTask = (taskId, updatedTask) => { - const newTask = { - updatedTask, - taskId, - }; - setTasks(tasks => { - const tasksWithoutTheUpdated = [...tasks]; - const taskIndex = tasks.findIndex(task => task._id === taskId); - tasksWithoutTheUpdated[taskIndex] = updatedTask; - return tasksWithoutTheUpdated; - }); +setUpdatedTasks(prev => { + const others = prev.filter(t => t.taskId !== taskId); + return [...others, { taskId, updatedTask }]; +}); - if (updatedTasks.findIndex(task => task.taskId === taskId) !== -1) { - const taskIndex = updatedTasks.findIndex(task => task.taskId === taskId); - const tasksToUpdate = updatedTasks; - tasksToUpdate.splice(taskIndex, 1); - tasksToUpdate.splice(taskIndex, 0, newTask); - setUpdatedTasks(tasksToUpdate); - } else { - setUpdatedTasks(tasks => [...tasks, newTask]); - } - }; + try { + await handleSubmit(updatedUserProfile); + toast.success("Task updated"); + } catch (e) { + toast.error("Failed to update task"); + console.error(e); + } +}; const handleImageUpload = async evt => { if (evt) evt.preventDefault(); @@ -1382,15 +1431,6 @@ const onAssignProject = assignedProject => {
- {/* } - {!isProfileEqual || - !isTasksEqual || - !isProjectsEqual ? ( - - Please click on "Save changes" to save the changes you have made.{' '} - - ) : null} - */} {!codeValid ? ( NOT SAVED! The code must be between 5 and 7 characters long @@ -1741,12 +1781,13 @@ const onAssignProject = assignedProject => { userId={props.match.params.userId} updateTask={onUpdateTask} handleSubmit={handleSubmit} - disabled={ - !formValid.firstName || - !formValid.lastName || - !formValid.email || - !(isProfileEqual && isTasksEqual && isProjectsEqual) - } + // disabled={ + // !formValid.firstName || + // !formValid.lastName || + // !formValid.email || + // !(isProfileEqual && isTasksEqual && isProjectsEqual) + // } + disabled={false} darkMode={darkMode} /> ) @@ -1797,7 +1838,7 @@ const onAssignProject = assignedProject => { )} - {((canEdit && activeTab) || canEditTeamCode) && ( + {((canEdit && activeTab) || canEditTeamCode) && activeTab !== '4' && ( <> { !formValid.email || !codeValid || (userStartDate > userEndDate && userEndDate !== '') || - // titleOnSet || (isProfileEqual && isTasksEqual && isProjectsEqual) } userProfile={userProfile} @@ -2184,12 +2224,13 @@ const onAssignProject = assignedProject => { userId={props.match.params.userId} updateTask={onUpdateTask} handleSubmit={handleSubmit} - disabled={ - !formValid.firstName || - !formValid.lastName || - !formValid.email || - !(isProfileEqual && isTasksEqual && isProjectsEqual) - } + // disabled={ + // !formValid.firstName || + // !formValid.lastName || + // !formValid.email || + // !(isProfileEqual && isTasksEqual && isProjectsEqual) + // } + disabled={false} darkMode={darkMode} /> diff --git a/src/utils/URL.js b/src/utils/URL.js index 0b6952e83d..45ea4834a1 100644 --- a/src/utils/URL.js +++ b/src/utils/URL.js @@ -101,6 +101,7 @@ export const ENDPOINTS = { TASK_DEL: (taskId, motherId) => `${APIEndpoint}/task/del/${taskId}/${motherId}`, GET_TASK: taskId => `${APIEndpoint}/task/${taskId}`, TASK_UPDATE: taskId => `${APIEndpoint}/task/update/${taskId}`, + TASK_DELETE_BY_ID: (taskId, userId) => `${APIEndpoint}/task/deleteTask/${taskId}/${userId}`, TASK_UPDATE_STATUS: taskId => `${APIEndpoint}/task/updateStatus/${taskId}`, TASK_CHANGE_LOGS: taskId => `${APIEndpoint}/task/${taskId}/changeLogs`, DELETE_CHILDREN: taskId => `${APIEndpoint}/task/delete/children/${taskId}`,