From 21eed5f2d2e570af6a6587fcb76809a8dafe9715 Mon Sep 17 00:00:00 2001 From: sri0606 Date: Thu, 12 Sep 2024 12:17:22 -0700 Subject: [PATCH 1/6] Fix: Set Inactive button does not crash UI anymore and is functional. Reafctored code related to Archive functionality from Projects to Project. --- src/components/Projects/Project/Project.jsx | 60 ++++++++++++++++--- src/components/Projects/Projects.jsx | 64 +++------------------ 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/components/Projects/Project/Project.jsx b/src/components/Projects/Project/Project.jsx index 3121189377..998960a9f7 100644 --- a/src/components/Projects/Project/Project.jsx +++ b/src/components/Projects/Project/Project.jsx @@ -7,6 +7,9 @@ import { connect } from 'react-redux'; import hasPermission from 'utils/permissions'; import { boxStyle } from 'styles'; import { toast } from 'react-toastify'; +import { modifyProject, clearError } from '../../../actions/projects'; +import ModalTemplate from './../../common/Modal'; +import { CONFIRM_ARCHIVE } from './../../../languages/en/messages'; const Project = props => { const { darkMode, index } = props; @@ -14,7 +17,20 @@ const Project = props => { const [projectData, setProjectData] = useState(props.projectData); const { projectName, category, isActive, _id: projectId } = projectData; const [displayName, setDisplayName] = useState(projectName); + const initialModalData = { + showModal: false, + modalMessage: "", + modalTitle: "", + hasConfirmBtn: false, + hasInactiveBtn: false, + }; + + const [modalData, setModalData] = useState(initialModalData); + const onCloseModal = () => { + setModalData(initialModalData); + props.clearError(); + }; const canPutProject = props.hasPermission('putProject'); const canDeleteProject = props.hasPermission('deleteProject'); @@ -51,18 +67,38 @@ const Project = props => { } const onArchiveProject = () => { - props.onClickArchiveBtn(projectData); + setModalData({ + showModal: true, + modalMessage: `

Do you want to archive ${projectData.projectName}?

`, + modalTitle: CONFIRM_ARCHIVE, + hasConfirmBtn: true, + hasInactiveBtn: isActive, + }); } + const setProjectInactive = () => { + updateProject('isActive', !isActive); + onCloseModal(); + } + const confirmArchive = () => { + updateProject('isActive', !isActive); + onCloseModal(); + }; + useEffect(() => { - if (firstLoad) { - setFirstLoad(false); - } else { - props.onUpdateProject(projectData) - } + const onUpdateProject = async () => { + if (firstLoad) { + setFirstLoad(false); + } else { + await props.modifyProject(projectData); + } + }; + + onUpdateProject(); }, [projectData]); return ( + <> @@ -172,7 +208,17 @@ const Project = props => { ) : null} + + ); }; const mapStateToProps = state => state; -export default connect(mapStateToProps, { hasPermission })(Project); +export default connect(mapStateToProps, { hasPermission, modifyProject, clearError })(Project); + diff --git a/src/components/Projects/Projects.jsx b/src/components/Projects/Projects.jsx index 5f4d851927..80e976d844 100644 --- a/src/components/Projects/Projects.jsx +++ b/src/components/Projects/Projects.jsx @@ -3,7 +3,6 @@ import { connect } from 'react-redux'; import { fetchAllProjects, postNewProject, - modifyProject, clearError, } from '../../actions/projects'; import {getProjectsByUsersName} from '../../actions/userProfile'; @@ -12,14 +11,12 @@ import Overview from './Overview'; import AddProject from './AddProject'; import ProjectTableHeader from './ProjectTableHeader'; import Project from './Project'; -import ModalTemplate from './../common/Modal'; -import { CONFIRM_ARCHIVE } from './../../languages/en/messages'; import './projects.css'; import Loading from '../common/Loading'; import hasPermission from '../../utils/permissions'; import EditableInfoModal from '../UserProfile/EditableModal/EditableInfoModal'; import SearchProjectByPerson from 'components/SearchProjectByPerson/SearchProjectByPerson'; -import ProjectsList from 'components/BMDashboard/Projects/ProjectsList'; +import ModalTemplate from './../common/Modal'; const Projects = function(props) { const role = props.state.userProfile.role; @@ -27,24 +24,15 @@ const Projects = function(props) { const numberOfProjects = props.state.allProjects.projects.length; const numberOfActive = props.state.allProjects.projects.filter(project => project.isActive).length; const { fetching, fetched, status, error } = props.state.allProjects; - const initialModalData = { + const initialModalData = { showModal: false, modalMessage: "", - modalTitle: "", - hasConfirmBtn: false, - hasInactiveBtn: false, + modalTitle: "ERROR", }; - const [modalData, setModalData] = useState(initialModalData); const [categorySelectedForSort, setCategorySelectedForSort] = useState(""); const [showStatus, setShowStatus] = useState(""); const [sortedByName, setSortedByName] = useState(""); - const [projectTarget, setProjectTarget] = useState({ - projectName: '', - projectId: -1, - active: false, - category: '', - }); const [projectList, setProjectList] = useState(null); const [searchName, setSearchName] = useState(""); const [allProjects, setAllProjects] = useState(null); @@ -69,21 +57,6 @@ const Projects = function(props) { const canPostProject = props.hasPermission('postProject'); - const onClickArchiveBtn = (projectData) => { - setProjectTarget(projectData); - setModalData({ - showModal: true, - modalMessage: `

Do you want to archive ${projectData.projectName}?

`, - modalTitle: CONFIRM_ARCHIVE, - hasConfirmBtn: true, - hasInactiveBtn: true, - }); - }; - - const onCloseModal = () => { - setModalData(initialModalData); - props.clearError(); - }; const onChangeCategory = (value) => { setCategorySelectedForSort(value); @@ -98,27 +71,15 @@ const Projects = function(props) { setSortedByName(prevState => prevState === clickedId ? "" : clickedId); } - const onUpdateProject = async (updatedProject) => { - await props.modifyProject(updatedProject); - }; - - const confirmArchive = async () => { - const updatedProject = { ...projectTarget, isArchived: true }; - await onUpdateProject(updatedProject); - await props.fetchAllProjects(); - onCloseModal(); - }; - - const setInactiveProject = async () => { - const updatedProject = { ...projectTarget, isActive: !isActive }; - await onUpdateProject(updatedProject); - onCloseModal(); - }; - const postProject = async (name, category) => { await props.postNewProject(name, category); }; + const onCloseModal = () => { + setModalData(initialModalData); + props.clearError(); + }; + const generateProjectList = (categorySelectedForSort, showStatus, sortedByName) => { const { projects } = props.state.allProjects; const projectList = projects.filter(project => { @@ -146,8 +107,6 @@ const Projects = function(props) { key={project._id} index={index} projectData={project} - onUpdateProject={onUpdateProject} - onClickArchiveBtn={onClickArchiveBtn} darkMode={darkMode} /> )); @@ -166,8 +125,6 @@ const Projects = function(props) { showModal: true, modalMessage: error, modalTitle: 'ERROR', - hasConfirmBtn: false, - hasInactiveBtn: false, }); } }, [categorySelectedForSort, showStatus, sortedByName, props.state.allProjects, props.state.theme.darkMode]); @@ -232,15 +189,13 @@ const Projects = function(props) { + - ); } @@ -252,7 +207,6 @@ const mapStateToProps = state => { export default connect(mapStateToProps, { fetchAllProjects, postNewProject, - modifyProject, clearError, getPopupById, hasPermission, From ef9f28e1c8f7da7bea868df06c74f67eae36b517 Mon Sep 17 00:00:00 2001 From: sri0606 Date: Thu, 12 Sep 2024 12:22:29 -0700 Subject: [PATCH 2/6] Tests: Updated tests accordingly with code refactored from Projects to Project component regarding Archive functionality. --- .../Projects/Project/Project.test.jsx | 26 ++++++++++++++----- .../Projects/__tests__/Projects.test.jsx | 15 ++++++----- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/components/Projects/Project/Project.test.jsx b/src/components/Projects/Project/Project.test.jsx index 5a8bb6f95a..4be61c8487 100644 --- a/src/components/Projects/Project/Project.test.jsx +++ b/src/components/Projects/Project/Project.test.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, fireEvent, waitFor } from '@testing-library/react'; +import { render, fireEvent, waitFor,screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { Provider } from 'react-redux'; import configureMockStore from 'redux-mock-store'; @@ -69,15 +69,18 @@ describe('Project Component', () => { }); }); - it('toggles project active status on button click', () => { + it('toggles project active status on button click', async () => { const { getByTestId } = renderProject(sampleProps); // Find the active status button and click it const activeButton = getByTestId('project-active'); fireEvent.click(activeButton); - // Check if the onUpdateProject function has been called - expect(sampleProps.onUpdateProject).toHaveBeenCalledTimes(1); + await waitFor(() => { + // check if the active status is active after clicking + const activeStatus = getByTestId('project-active').querySelector('i'); + expect(activeStatus).toHaveClass('fa-circle'); + }); }); it('triggers delete action on button click', () => { @@ -87,8 +90,19 @@ describe('Project Component', () => { const deleteButton = getByTestId('delete-button'); fireEvent.click(deleteButton); - // Check if the onClickArchiveBtn function has been called - expect(sampleProps.onClickArchiveBtn).toHaveBeenCalledTimes(1); + // Check if the modal is triggered + const modal = document.querySelector('.modal'); + expect(modal).toBeInTheDocument(); + + const archiveButton=screen.getAllByText('Archive')[0]; + fireEvent.click(archiveButton); + + expect(screen.getByText('Confirm Archive')).toBeInTheDocument(); + expect(screen.getByText(`Do you want to archive ${sampleProjectData.projectName}?`)).toBeInTheDocument(); + + const closeButton=screen.getByText('Close') + fireEvent.click(closeButton) + expect(screen.queryByText('Confirm Archive')).not.toBeInTheDocument(); }); }); diff --git a/src/components/Projects/__tests__/Projects.test.jsx b/src/components/Projects/__tests__/Projects.test.jsx index 7e869a88d7..ed448f663e 100644 --- a/src/components/Projects/__tests__/Projects.test.jsx +++ b/src/components/Projects/__tests__/Projects.test.jsx @@ -200,15 +200,16 @@ describe("Projects component",()=>{ const ascendingButton=container.querySelector('[id="Ascending"]') fireEvent.click(ascendingButton) - const archiveButton=screen.getAllByText('Archive')[1] - fireEvent.click(archiveButton) + // Code related to "Archive" functionality is refactored into Project component and will be tested in Project.test.js + // const archiveButton=screen.getAllByText('Archive')[1] + // fireEvent.click(archiveButton) - expect(screen.getByText('Confirm Archive')).toBeInTheDocument(); - expect(screen.getByText(`Do you want to archive ${projects[0].projectName}?`)).toBeInTheDocument(); + // expect(screen.getByText('Confirm Archive')).toBeInTheDocument(); + // expect(screen.getByText(`Do you want to archive ${projects[0].projectName}?`)).toBeInTheDocument(); - const closeButton=screen.getByText('Close') - fireEvent.click(closeButton) - expect(screen.queryByText('Confirm Archive')).not.toBeInTheDocument(); + // const closeButton=screen.getByText('Close') + // fireEvent.click(closeButton) + // expect(screen.queryByText('Confirm Archive')).not.toBeInTheDocument(); }) }) \ No newline at end of file From a6241d25953628127bf3f23d84a4c0f59748c623 Mon Sep 17 00:00:00 2001 From: sri0606 Date: Thu, 12 Sep 2024 12:48:42 -0700 Subject: [PATCH 3/6] Fix: Modified Update project action to check if the project is found or else raise an error. Fixes creating duplicate projects. --- src/reducers/allProjectsReducer.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/reducers/allProjectsReducer.js b/src/reducers/allProjectsReducer.js index 8249786df1..1872b2c3ec 100644 --- a/src/reducers/allProjectsReducer.js +++ b/src/reducers/allProjectsReducer.js @@ -39,13 +39,18 @@ export const allProjectsReducer = (allProjects = allProjectsInital, action) => { case types.UPDATE_PROJECT: if (status !== 200) return updateState({ status, error }); const { updatedProject } = action; - index = allProjects.projects.findIndex(project => project._id === action.projectId); - projects = Object.assign([ - ...allProjects.projects.slice(0, index), - updatedProject, - ...allProjects.projects.slice(index + 1), - ]); - return updateState({ projects, status }); + index = allProjects.projects.findIndex(project => project._id === updatedProject._id); + if (index !== -1) { + projects = [ + ...allProjects.projects.slice(0, index), + updatedProject, + ...allProjects.projects.slice(index + 1), + ]; + return updateState({ projects, status }); + } + else { + return updateState({ status:404, error: "Project not found." }); + } case types.DELETE_PROJECT: if (status !== 200) return updateState({ status, error }); const { projectId } = action; From 65945679eede102f3eb90bbae3a407ae221a99b8 Mon Sep 17 00:00:00 2001 From: sri0606 Date: Fri, 13 Sep 2024 10:28:07 -0700 Subject: [PATCH 4/6] Fix: Project will be archived now on clicking confirm button --- src/components/Projects/Project/Project.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Projects/Project/Project.jsx b/src/components/Projects/Project/Project.jsx index 998960a9f7..a13752afc4 100644 --- a/src/components/Projects/Project/Project.jsx +++ b/src/components/Projects/Project/Project.jsx @@ -15,7 +15,7 @@ const Project = props => { const { darkMode, index } = props; const [firstLoad, setFirstLoad] = useState(true); const [projectData, setProjectData] = useState(props.projectData); - const { projectName, category, isActive, _id: projectId } = projectData; + const { projectName, category, isActive,isArchived, _id: projectId } = projectData; const [displayName, setDisplayName] = useState(projectName); const initialModalData = { showModal: false, @@ -81,7 +81,7 @@ const Project = props => { onCloseModal(); } const confirmArchive = () => { - updateProject('isActive', !isActive); + updateProject('isArchived', !isArchived); onCloseModal(); }; @@ -202,6 +202,7 @@ const Project = props => { className="btn btn-outline-danger" onClick={onArchiveProject} style={darkMode ? {} : boxStyle} + disabled = {isArchived} > {ARCHIVE} From a8c5b59c03d6086cbcd6a79777b96e43d8fa8bdf Mon Sep 17 00:00:00 2001 From: sri0606 Date: Sun, 15 Sep 2024 22:48:30 -0700 Subject: [PATCH 5/6] Update: The archived project will be removed from projects table. --- src/components/Projects/Project/Project.jsx | 1 + src/components/Projects/Projects.jsx | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/Projects/Project/Project.jsx b/src/components/Projects/Project/Project.jsx index ad3976f6fd..e92d65b5e3 100644 --- a/src/components/Projects/Project/Project.jsx +++ b/src/components/Projects/Project/Project.jsx @@ -82,6 +82,7 @@ const Project = props => { } const confirmArchive = () => { updateProject('isArchived', !isArchived); + props.onProjectArchived(); onCloseModal(); }; diff --git a/src/components/Projects/Projects.jsx b/src/components/Projects/Projects.jsx index 80e976d844..03ccb51b66 100644 --- a/src/components/Projects/Projects.jsx +++ b/src/components/Projects/Projects.jsx @@ -80,9 +80,14 @@ const Projects = function(props) { props.clearError(); }; + const handleProjectArchived = () => { + props.fetchAllProjects(); + }; + const generateProjectList = (categorySelectedForSort, showStatus, sortedByName) => { const { projects } = props.state.allProjects; - const projectList = projects.filter(project => { + const filteredProjects = projects.filter(project => !project.isArchived) + .filter(project => { if (categorySelectedForSort && showStatus){ return project.category === categorySelectedForSort && project.isActive === showStatus; } else if (categorySelectedForSort) { @@ -108,10 +113,11 @@ const Projects = function(props) { index={index} projectData={project} darkMode={darkMode} + onProjectArchived={handleProjectArchived} /> )); - setProjectList(projectList); - setAllProjects(projectList); + setProjectList(filteredProjects); + setAllProjects(filteredProjects); } useEffect(() => { From 8962f245ca3abf27d74b050547403b085053733f Mon Sep 17 00:00:00 2001 From: sri0606 Date: Fri, 8 Nov 2024 10:46:59 -0800 Subject: [PATCH 6/6] Fix: Auto-refresh projects table to remove archived project --- src/components/Projects/Projects.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Projects/Projects.jsx b/src/components/Projects/Projects.jsx index ea565d1819..a0faf32fc0 100644 --- a/src/components/Projects/Projects.jsx +++ b/src/components/Projects/Projects.jsx @@ -82,6 +82,7 @@ const Projects = function(props) { const handleProjectArchived = () => { props.fetchAllProjects(); + refreshProjects(); }; const generateProjectList = (categorySelectedForSort, showStatus, sortedByName) => {