Skip to content

Commit

Permalink
Merge pull request #2673 from OneCommunityGlobal/sriram-fix-projects-…
Browse files Browse the repository at this point in the history
…archive-button

Sriram - fix projects archive functionality and Set Inactive button crashing UI
  • Loading branch information
one-community authored Dec 4, 2024
2 parents 47f9fcb + e7f8b0f commit 2ea8d03
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 84 deletions.
69 changes: 57 additions & 12 deletions src/components/Projects/Project/Project.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,30 @@ 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;
const [firstLoad, setFirstLoad] = useState(true);
const [projectData, setProjectData] = useState(props.projectData);
const { projectName, isActive, _id: projectId } = projectData;
const { projectName, isActive,isArchived, _id: projectId } = projectData;
const [displayName, setDisplayName] = useState(projectName);
const [category, setCategory] = useState(props.category || 'Unspecified'); // Initialize with props or default
const initialModalData = {
showModal: false,
modalMessage: "",
modalTitle: "",
hasConfirmBtn: false,
hasInactiveBtn: false,
};

const [modalData, setModalData] = useState(initialModalData);

const onCloseModal = () => {
setModalData(initialModalData);
props.clearError();
}; const [category, setCategory] = useState(props.category || 'Unspecified'); // Initialize with props or default

const canPutProject = props.hasPermission('putProject');
const canDeleteProject = props.hasPermission('deleteProject');
Expand Down Expand Up @@ -53,21 +69,39 @@ const Project = props => {
};

const onArchiveProject = () => {
props.onClickArchiveBtn(projectData);
setModalData({
showModal: true,
modalMessage: `<p>Do you want to archive ${projectData.projectName}?</p>`,
modalTitle: CONFIRM_ARCHIVE,
hasConfirmBtn: true,
hasInactiveBtn: isActive,
});
}

const setProjectInactive = () => {
updateProject('isActive', !isActive);
onCloseModal();
}
const confirmArchive = () => {
updateProject('isArchived', !isArchived);
props.onProjectArchived();
onCloseModal();
};

useEffect(() => {
if (firstLoad) {
setFirstLoad(false);
} else {
props.onUpdateProject(projectData)
}
if (props.projectData.category) {
setCategory(props.projectData.category);
}
const onUpdateProject = async () => {
if (firstLoad) {
setFirstLoad(false);
} else {
await props.modifyProject(projectData);
}
};

onUpdateProject();
}, [projectData]);

return (
<>
<tr className="projects__tr" id={'tr_' + props.projectId}>

<th className="projects__order--input" scope="row">
Expand Down Expand Up @@ -165,13 +199,24 @@ const Project = props => {
className="btn btn-outline-danger"
onClick={onArchiveProject}
style={darkMode ? {} : boxStyle}
disabled = {isArchived}
>
{ARCHIVE}
</button>
</td>
) : null}
</tr>
<ModalTemplate
isOpen={modalData.showModal}
closeModal={onCloseModal}
confirmModal={modalData.hasConfirmBtn ? confirmArchive : null}
setInactiveModal={modalData.hasInactiveBtn ? setProjectInactive : null}
modalMessage={modalData.modalMessage}
modalTitle={modalData.modalTitle}
/>
</>
);
};
const mapStateToProps = state => state;
export default connect(mapStateToProps, { hasPermission })(Project);
export default connect(mapStateToProps, { hasPermission, modifyProject, clearError })(Project);

26 changes: 20 additions & 6 deletions src/components/Projects/Project/Project.test.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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', () => {
Expand All @@ -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();
});
});

70 changes: 19 additions & 51 deletions src/components/Projects/Projects.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useState, useCallback, useEffect } from 'react';
import { connect } from 'react-redux';
import {
fetchAllProjects,
modifyProject,
clearError,
} from '../../actions/projects';
import {getProjectsByUsersName, getUserByAutocomplete } from '../../actions/userProfile';
Expand All @@ -11,38 +10,28 @@ 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 ModalTemplate from './../common/Modal';

const Projects = function(props) {
const role = props.state.userProfile.role;
const { darkMode } = props.state.theme;
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([]);
Expand All @@ -69,21 +58,6 @@ const Projects = function(props) {

const canPostProject = props.hasPermission('postProject');

const onClickArchiveBtn = (projectData) => {
setProjectTarget(projectData);
setModalData({
showModal: true,
modalMessage: `<p>Do you want to archive ${projectData.projectName}?</p>`,
modalTitle: CONFIRM_ARCHIVE,
hasConfirmBtn: true,
hasInactiveBtn: true,
});
};

const onCloseModal = () => {
setModalData(initialModalData);
props.clearError();
};

const onChangeCategory = (value) => {
setCategorySelectedForSort(value);
Expand All @@ -98,19 +72,6 @@ const Projects = function(props) {
setSortedByName(prevState => prevState === clickedId ? "" : clickedId);
}

const onUpdateProject = async (updatedProject) => {
await props.modifyProject(updatedProject);
/* refresh the page after updating the project */
await props.fetchAllProjects();
};

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);
Expand All @@ -122,6 +83,16 @@ const Projects = function(props) {
await props.fetchAllProjects();
};

const onCloseModal = () => {
setModalData(initialModalData);
props.clearError();
};

const handleProjectArchived = () => {
props.fetchAllProjects();
refreshProjects();
};

// Fetch autocomplete suggestions
const fetchSuggestions = useCallback(async () => {
try {
Expand Down Expand Up @@ -181,7 +152,8 @@ const Projects = function(props) {

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) {
Expand All @@ -206,13 +178,12 @@ const Projects = function(props) {
key={project._id}
index={index}
projectData={project}
onUpdateProject={onUpdateProject}
onClickArchiveBtn={onClickArchiveBtn}
darkMode={darkMode}
onProjectArchived={handleProjectArchived}
/>
));
setProjectList(projectList);
setAllProjects(projectList);
setProjectList(filteredProjects);
setAllProjects(filteredProjects);
}

const refreshProjects = async () => {
Expand Down Expand Up @@ -283,15 +254,13 @@ const Projects = function(props) {
</table>
</div>

</div>
<ModalTemplate
isOpen={modalData.showModal}
closeModal={onCloseModal}
confirmModal={modalData.hasConfirmBtn ? confirmArchive : null}
setInactiveModal={modalData.hasInactiveBtn ? setInactiveProject : null}
modalMessage={modalData.modalMessage}
modalTitle={modalData.modalTitle}
/>
</div>
</>
);
}
Expand All @@ -302,7 +271,6 @@ const mapStateToProps = state => {

export default connect(mapStateToProps, {
fetchAllProjects,
modifyProject,
clearError,
getPopupById,
hasPermission,
Expand Down
15 changes: 8 additions & 7 deletions src/components/Projects/__tests__/Projects.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,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();
})

})
3 changes: 2 additions & 1 deletion src/components/common/Modal/Modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import '../../Header/DarkMode.css';

// eslint-disable-next-line react/function-component-definition
const ModalExample = props => {
const darkMode = useSelector(state => state.theme.darkMode);
// const darkMode = useSelector(state => state.theme.darkMode);
const {
isOpen,
closeModal,
Expand All @@ -27,6 +27,7 @@ const ModalExample = props => {
modalMessage,
type,
linkType,
darkMode,
} = props;

const [linkName, setLinkName] = useState('');
Expand Down
19 changes: 12 additions & 7 deletions src/reducers/allProjectsReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 2ea8d03

Please sign in to comment.