Skip to content

Commit 9c082ee

Browse files
Merge pull request #3499 from OneCommunityGlobal/Siva_fix_member_column_filter
Siva - fix: member filter and project filter, added member counts
2 parents 88f40f4 + 20aa00f commit 9c082ee

11 files changed

Lines changed: 387 additions & 1444 deletions

File tree

src/components/BMDashboard/Equipment/Detail/EquipmentDetailPage.module.css

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,7 @@ button.descriptionItem_button:hover {
224224

225225
/* Single arrow in dark mode - white chevron for visibility */
226226
:global(body.dark-mode) .editableField select,
227-
:global(body.bm-dashboard-dark) .editableField select,
228-
:global(body.dark-mode) .editableFieldInput,
229-
:global(body.bm-dashboard-dark) .editableFieldInput {
227+
:global(body.bm-dashboard-dark) .editableField select {
230228
appearance: none;
231229
-webkit-appearance: none;
232230
-moz-appearance: none;

src/components/Projects/Project/Project.jsx

Lines changed: 46 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,17 @@ import { connect } from 'react-redux';
1010
import hasPermission from '~/utils/permissions';
1111
import { boxStyle } from '~/styles';
1212
import { toast } from 'react-toastify';
13-
import { modifyProject, clearError } from '../../../actions/projects';
14-
import ModalTemplate from './../../common/Modal';
15-
import { CONFIRM_ARCHIVE, CONFIRM_UNARCHIVE } from './../../../languages/en/messages';
13+
import { modifyProject } from '../../../actions/projects';
14+
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
1615

1716
const Project = props => {
1817
const { darkMode, index } = props;
1918
const [projectData, setProjectData] = useState(props.projectData);
20-
const { projectName, isActive,isArchived = false, _id: projectId } = projectData;
21-
// const { projectName, isActive, isArchived, _id: projectId } = projectData;
19+
const { projectName = '', isActive = false, _id: projectId } = projectData || {};
2220
const [displayName, setDisplayName] = useState(projectName);
23-
const initialModalData = {
24-
showModal: false,
25-
modalMessage: "",
26-
modalTitle: "",
27-
hasConfirmBtn: false,
28-
hasInactiveBtn: false,
29-
};
30-
const [category, setCategory] = useState(props.category || 'Unspecified'); // Initialize with props or default
31-
32-
const [modalData, setModalData] = useState(initialModalData);
33-
34-
const onCloseModal = () => {
35-
setModalData(initialModalData);
36-
if(props.clearError) props.clearError();
37-
};
21+
const [category, setCategory] = useState(
22+
props.projectData?.category || props.category || 'Unspecified',
23+
);
3824

3925
const canPutProject = props.hasPermission('putProject');
4026
const canDeleteProject = props.hasPermission('deleteProject');
@@ -100,74 +86,20 @@ const Project = props => {
10086
};
10187

10288
const onArchiveProject = () => {
103-
if(isArchived){
104-
setModalData({
105-
showModal: true,
106-
modalMessage: `<p>Do you want to unarchive this ${projectData.projectName}?</p>`,
107-
modalTitle: CONFIRM_UNARCHIVE,
108-
hasConfirmBtn: true,
109-
hasInactiveBtn: isActive,
110-
});
111-
} else {
112-
setModalData({
113-
showModal: true,
114-
modalMessage: `<p>Do you want to archive ${projectData.projectName}?</p>`,
115-
modalTitle: CONFIRM_ARCHIVE,
116-
hasConfirmBtn: true,
117-
hasInactiveBtn: isActive,
118-
});
119-
}
120-
}
121-
122-
const setProjectInactive = () => {
123-
updateProject('isActive', !isActive);
124-
onCloseModal();
125-
}
126-
// const confirmArchive = () => {
127-
// updateProject('isArchived', !isArchived);
128-
// props.onProjectArchived(projectData);
129-
// onCloseModal();
130-
// };
131-
const confirmArchive = async () => {
132-
// build the new project object we want on the server
133-
const updatedProject = { ...projectData, isArchived: !projectData.isArchived };
134-
135-
// If parent provided an onProjectArchived handler, call it (parent will modify + refresh)
136-
if (typeof props.onProjectArchived === 'function') {
137-
try {
138-
await props.onProjectArchived(updatedProject);
139-
} catch (err) {
140-
// eslint-disable-next-line no-console
141-
console.error('Error archiving/unarchiving project:', err);
142-
}
143-
} else if (typeof props.modifyProject === 'function') {
144-
// fallback: if parent didn't pass handler, call modifyProject directly
145-
try {
146-
await props.modifyProject(updatedProject);
147-
} catch (err) {
148-
// eslint-disable-next-line no-console
149-
console.error('Fallback modifyProject error:', err);
150-
}
151-
}
152-
153-
onCloseModal();
89+
props.onClickArchiveBtn(projectData);
15490
};
15591

15692
useEffect(() => {
15793
setProjectData(props.projectData);
15894
setDisplayName(props.projectData?.projectName || '');
15995
setCategory(props.projectData?.category || props.category || 'Unspecified');
160-
if (props.projectData.category) {
161-
setCategory(props.projectData.category);
162-
}
16396
}, [props.projectData, props.category]);
16497

16598
return (
166-
<>
167-
<tr
168-
className={styles['projects__tr']}
169-
id={`tr_${props.projectId}`}
170-
>
99+
<tr
100+
className={styles['projects__tr']}
101+
id={`tr_${props.projectId}`}
102+
>
171103
<th className={styles['projects__order--input']} scope="row">
172104
<div className={darkMode ? 'text-light' : ''}>{index + 1}</div>
173105
</th>
@@ -245,15 +177,25 @@ const Project = props => {
245177
</td>
246178

247179
<td>
248-
<NavItem tag={Link} to={`/project/members/${projectId}`}>
249-
<button
250-
type="button"
251-
className="btn btn-outline-info"
252-
style={darkMode ? {} : boxStyle}
180+
<div style={{ position: 'relative', display: 'inline-block' }}>
181+
<NavItem tag={Link} to={`/project/members/${projectId}`} className="d-flex align-items-center">
182+
<button
183+
type="button"
184+
className="btn btn-outline-info d-flex align-items-center project-member-btn"
185+
style={darkMode ? {} : boxStyle}
186+
>
187+
<i className="fa fa-users" aria-hidden="true" />
188+
</button>
189+
</NavItem>
190+
<OverlayTrigger
191+
placement="top"
192+
overlay={<Tooltip>Active members</Tooltip>}
253193
>
254-
<i className="fa fa-users" aria-hidden="true" />
255-
</button>
256-
</NavItem>
194+
<span className={styles['project-member-badge']}>
195+
{props.activeMemberCounts}
196+
</span>
197+
</OverlayTrigger>
198+
</div>
257199
</td>
258200

259201
<td>
@@ -268,46 +210,20 @@ const Project = props => {
268210
</NavItem>
269211
</td>
270212

271-
272-
{(canDeleteProject) ? (
273-
274-
// <td>
275-
// <button
276-
// data-testid="delete-button"
277-
// type="button"
278-
// className="btn btn-outline-danger"
279-
// onClick={onArchiveProject}
280-
// style={darkMode ? {} : boxStyle}
281-
// disabled={isArchived}
282-
// >
283-
// {ARCHIVE}
284-
// </button>
285-
// </td>
286-
// ) : null}
287-
// </tr>
288-
// </>
289-
<td>
290-
<button
291-
data-testid="delete-button"
292-
type="button"
293-
className="btn btn-outline-danger"
294-
style={darkMode ? {borderColor: '#D2042D'} : boxStyle}
295-
onClick={onArchiveProject}>
296-
{/* {ARCHIVE} */}
297-
{isArchived ? "UNARCHIVE":"Archive"}
298-
</button>
299-
</td>
300-
) : null}
301-
</tr>
302-
<ModalTemplate
303-
isOpen={modalData.showModal}
304-
closeModal={onCloseModal}
305-
confirmModal={modalData.hasConfirmBtn ? confirmArchive : null}
306-
setInactiveModal={modalData.hasInactiveBtn ? setProjectInactive : null}
307-
modalMessage={modalData.modalMessage}
308-
modalTitle={modalData.modalTitle}
309-
/>
310-
</>
213+
{canDeleteProject ? (
214+
<td>
215+
<button
216+
data-testid="delete-button"
217+
type="button"
218+
className="btn btn-outline-danger"
219+
style={darkMode ? { borderColor: '#D2042D' } : boxStyle}
220+
onClick={onArchiveProject}
221+
>
222+
{ARCHIVE}
223+
</button>
224+
</td>
225+
) : null}
226+
</tr>
311227
);
312228
};
313229

@@ -329,6 +245,7 @@ Project.propTypes = {
329245
onClickProjectStatusBtn: PropTypes.func,
330246
onClickArchiveBtn: PropTypes.func,
331247
projectId: PropTypes.string,
248+
activeMemberCounts: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
332249
};
333250

334251
// Default props
@@ -341,7 +258,8 @@ Project.defaultProps = {
341258
onClickProjectStatusBtn: () => {},
342259
onClickArchiveBtn: () => {},
343260
projectId: '',
261+
activeMemberCounts: '',
344262
};
345263

346264
const mapStateToProps = state => state;
347-
export default connect(mapStateToProps, { hasPermission, modifyProject, clearError })(Project);
265+
export default connect(mapStateToProps, { hasPermission, modifyProject })(Project);

src/components/Projects/Project/Project.test.jsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { render, fireEvent, waitFor,screen } from '@testing-library/react';
2+
import { render, fireEvent, waitFor } from '@testing-library/react';
33
import '@testing-library/jest-dom/extend-expect';
44
import { Provider } from 'react-redux';
55
import configureMockStore from 'redux-mock-store';
@@ -106,40 +106,46 @@ describe('Project Component', () => {
106106
// _id: sampleProjectData._id,
107107
// }));
108108
// });
109-
it('shows archive modal when delete button is clicked', () => {
109+
it('calls archive handler when delete button is clicked', () => {
110+
const mockOnClickArchiveBtn = vi.fn();
110111
const { getByTestId } = renderProject({
111112
...sampleProps,
113+
onClickArchiveBtn: mockOnClickArchiveBtn,
112114
});
113115

114116
// eslint-disable-next-line testing-library/prefer-screen-queries
115117
const deleteButton = getByTestId('delete-button');
116118
fireEvent.click(deleteButton);
117119

118-
// Test that the modal appears (this is what actually happens)
119-
expect(screen.getByRole('dialog')).toBeInTheDocument();
120-
expect(screen.getByText('Confirm Archive')).toBeInTheDocument();
121-
expect(screen.getByText(/Do you want to archive Sample Project/)).toBeInTheDocument();
120+
expect(mockOnClickArchiveBtn).toHaveBeenCalledWith(
121+
expect.objectContaining({
122+
_id: sampleProjectData._id,
123+
}),
124+
);
122125
});
123126

124-
// Optional: Adding test for unarchive scenario
125-
it('shows unarchive modal for archived projects', () => {
127+
it('calls archive handler for archived projects as well', () => {
126128
const archivedProjectData = {
127129
...sampleProjectData,
128130
isArchived: true,
129131
};
132+
const mockOnClickArchiveBtn = vi.fn();
130133

131134
const { getByTestId } = renderProject({
132135
...sampleProps,
133136
projectData: archivedProjectData,
137+
onClickArchiveBtn: mockOnClickArchiveBtn,
134138
});
135139

136140
// eslint-disable-next-line testing-library/prefer-screen-queries
137141
const deleteButton = getByTestId('delete-button');
138142
fireEvent.click(deleteButton);
139143

140-
// Test that it shows unarchive modal
141-
expect(screen.getByRole('dialog')).toBeInTheDocument();
142-
expect(screen.getByText('Confirm UnArchive')).toBeInTheDocument();
143-
expect(screen.getByText(/Do you want to unarchive this Sample Project/)).toBeInTheDocument();
144+
expect(mockOnClickArchiveBtn).toHaveBeenCalledWith(
145+
expect.objectContaining({
146+
_id: sampleProjectData._id,
147+
isArchived: true,
148+
}),
149+
);
144150
});
145151
});

0 commit comments

Comments
 (0)