diff --git a/src/actions/__tests__/weeklySummariesAction.test.js b/src/actions/__tests__/weeklySummariesAction.test.js index bb132a6409..f433e76d1d 100644 --- a/src/actions/__tests__/weeklySummariesAction.test.js +++ b/src/actions/__tests__/weeklySummariesAction.test.js @@ -22,11 +22,10 @@ describe('Weekly Summaries Action', () => { it('Should fetch weekly summaries success', () => { - const weeklySummariesData = { id: 1, - dueDate: "2024-12-29", - summary: "Weekly Summary" + dueDate: '2024-12-29', + summary: 'Weekly Summary', }; const result = fetchWeeklySummariesSuccess(weeklySummariesData); @@ -41,7 +40,6 @@ describe('Weekly Summaries Action', () => { }); it('Should return action FETCH_WEEKLY_SUMMARIES_ERROR ', () => { - const error = {}; const result = fetchWeeklySummariesError(error); @@ -49,8 +47,7 @@ describe('Weekly Summaries Action', () => { type: actions.FETCH_WEEKLY_SUMMARIES_ERROR, payload: { error } }); - }) - + }); }); describe('Weekly Summaries', () => { @@ -70,15 +67,16 @@ describe('Weekly Summaries', () => { weeklySummariesCount: 1, weeklySummaries: [ { - id: "1", - dueDate: "2024-12-29", - summary: "Weekly Summary" - } + id: '1', + dueDate: '2024-12-29', + summary: 'Weekly Summary', + }, ], mediaUrl: 'http://media.com', - adminLinks: [{ Name: 'Media Folder', Link: 'http://newmedia.com' }] - } + adminLinks: [{ Name: 'Media Folder', Link: 'http://newmedia.com' }], //NOSONAR - using http for test data + }; + axios.get.mockResolvedValue({ data: mockData, status: 200 }); axios.get.mockResolvedValue({ data: mockData, status: 200 }); const result = await getWeeklySummaries(1)(dispatch); @@ -98,7 +96,6 @@ describe('Weekly Summaries', () => { expect(dispatch).toHaveBeenCalledWith(getUserProfileActionCreator(mockData)); expect(result).toBe(200); - }); it('Should dispatch an error action when GET request fails', async () => { @@ -130,9 +127,9 @@ describe('Weekly Summaries', () => { weeklySummariesCount: 1, weeklySummaries: [ { - id: "1", - dueDate: "2025-01-05", - summary: "Weekly Summary Week1" + id: '1', + dueDate: '2025-01-05', + summary: 'Weekly Summary Week1', }, ], mediaUrl: 'http://media.com' @@ -154,7 +151,6 @@ describe('Weekly Summaries', () => { }); it('Ensure that if the "Media Folder" link already exists, it should be updated correctly.', async () => { - const mockUserProfile = { firstName: 'User First Name', lastName: 'User Last Name', @@ -167,13 +163,13 @@ describe('Weekly Summaries', () => { weeklySummariesCount: 1, weeklySummaries: [ { - id: "1", - dueDate: "2025-01-05", - summary: "Weekly Summary Week1" + id: '1', + dueDate: '2025-01-05', + summary: 'Weekly Summary Week1', }, ], - mediaUrl: 'http://newmedia.com' - } + mediaUrl: 'http://newmedia.com', // NOSONAR - using http for test data + }; axios.get.mockResolvedValue({ data: mockUserProfile }); axios.put.mockResolvedValue({ status: 200 }); @@ -191,7 +187,6 @@ describe('Weekly Summaries', () => { }); it('Should throw error when API request fails', async () => { - const mockError = { response: { status: 500 } }; axios.get.mockRejectedValueOnce(mockError); @@ -203,9 +198,6 @@ describe('Weekly Summaries', () => { expect(dispatch).not.toHaveBeenCalled(); expect(result).toBe(500); - }); - }); - }); diff --git a/src/actions/__tests__/weeklySummariesReportActions.test.js b/src/actions/__tests__/weeklySummariesReportActions.test.js index ef22bfc982..ab020aaff5 100644 --- a/src/actions/__tests__/weeklySummariesReportActions.test.js +++ b/src/actions/__tests__/weeklySummariesReportActions.test.js @@ -10,44 +10,37 @@ vi.mock('axios'); describe('Weekly Summaries Report Actions', () => { it('Should return action FETCH_SUMMARIES_REPORT_BEGIN', () => { - const data = weeklySummaryReport.fetchWeeklySummariesReportBegin(); expect(data).toEqual({ type: actions.FETCH_SUMMARIES_REPORT_BEGIN }); - }); it('Should return action FETCH_SUMMARIES_REPORT_SUCCESS', () => { - const weeklySummariesData = { id: 1, - dueDate: "2024-12-29", - summary: "Weekly Summary" + dueDate: '2024-12-29', + summary: 'Weekly Summary', }; const data = weeklySummaryReport.fetchWeeklySummariesReportSuccess(weeklySummariesData); const expectedResult = { type: actions.FETCH_SUMMARIES_REPORT_SUCCESS, - payload: { weeklySummariesData } - } + payload: { weeklySummariesData }, + }; expect(data).toEqual(expectedResult); - }); it('Should return action FETCH_SUMMARIES_REPORT_ERROR', () => { const error = {} - const data = weeklySummaryReport.fetchWeeklySummariesReportError(error); expect(data).toEqual({ type: actions.FETCH_SUMMARIES_REPORT_ERROR, payload: { error } }); - - }) - + }); }); describe('Weekly Summary Report', () => { @@ -59,7 +52,6 @@ describe('Weekly Summary Report', () => { }); describe('Get Weekly Summaries Report', () => { - it('Should dispatch actions and return 200 on successful API call ', async () => { const mockData = { @@ -71,68 +63,63 @@ describe('Weekly Summary Report', () => { } ] } - + axios.get.mockResolvedValue({ data: mockData, status: 200 }); const result = await weeklySummaryReport.getWeeklySummariesReport()(dispatch); expect(dispatch).toHaveBeenCalledWith(weeklySummaryReport.fetchWeeklySummariesReportBegin()); - expect(dispatch).toHaveBeenCalledWith(weeklySummaryReport.fetchWeeklySummariesReportSuccess(result.data)); + expect(dispatch).toHaveBeenCalledWith( + weeklySummaryReport.fetchWeeklySummariesReportSuccess(result.data), + ); expect(result.status).toBe(200); - }); it('Should dispatch error action when GET request fails', async () => { - const mockError = { response: { status: 500 } }; axios.get.mockRejectedValueOnce(mockError); const result = await weeklySummaryReport.getWeeklySummariesReport()(dispatch); expect(dispatch).toHaveBeenCalledWith(weeklySummaryReport.fetchWeeklySummariesReportBegin()); - expect(dispatch).toHaveBeenCalledWith(weeklySummaryReport.fetchWeeklySummariesReportError(mockError)) + expect(dispatch).toHaveBeenCalledWith( + weeklySummaryReport.fetchWeeklySummariesReportError(mockError), + ); expect(result).toBe(500); - }); }); - describe('Update One Summary Report', () => { let mockUserProfile; let updatedField; beforeEach(() => { - mockUserProfile = { firstName: 'User First Name', lastName: 'User Last Name', weeklySummariesCount: 0, weeklySummaries: [ { - id: "1", - dueDate: "2024-12-29", - summary: "Weekly Summary Week1" - } - ] - } + id: '1', + dueDate: '2024-12-29', + summary: 'Weekly Summary Week1', + }, + ], + }; updatedField = { - weeklySummaries: [ { - id: "2", - dueDate: "2025-01-05", - summary: "Weekly Summary Week2" + id: '2', + dueDate: '2025-01-05', + summary: 'Weekly Summary Week2', }, - ] - + ], }; - }); it('Should successfully update user profile with weekly summary and dispatch UPDATE_SUMMARY_REPORT action ', async () => { - axios.get.mockResolvedValue({ data: mockUserProfile }); axios.put.mockResolvedValue({ status: 200 @@ -142,12 +129,12 @@ describe('Weekly Summary Report', () => { expect(result.status).toBe(200); expect(axios.get).toHaveBeenCalledWith(ENDPOINTS.USER_PROFILE(1)); expect(axios.put).toHaveBeenCalled(); - expect(dispatch).toHaveBeenCalledWith(weeklySummaryReport.updateSummaryReport({ _id: 1, updatedField })); - + expect(dispatch).toHaveBeenCalledWith( + weeklySummaryReport.updateSummaryReport({ _id: 1, updatedField }), + ); }); it('Should throw error when PUT request fails', async () => { - axios.get.mockResolvedValue({ data: mockUserProfile }); axios.put.mockResolvedValue({ status: 500 @@ -159,13 +146,10 @@ describe('Weekly Summary Report', () => { }); it('Should throw error when GET request fails', async () => { - axios.get.mockRejectedValue(new Error('Failed to fetch user profile')); const result = weeklySummaryReport.updateOneSummaryReport(1, updatedField); await expect(result(dispatch)).rejects.toThrow(new Error('Failed to fetch user profile')); - }); - }); -}); \ No newline at end of file +}); diff --git a/src/components/Badge/BadgeDevelopment.jsx b/src/components/Badge/BadgeDevelopment.jsx index 973efa387b..937fd2e8f9 100644 --- a/src/components/Badge/BadgeDevelopment.jsx +++ b/src/components/Badge/BadgeDevelopment.jsx @@ -1,11 +1,27 @@ import { useState } from 'react'; import { Button, Modal, ModalHeader, ModalBody } from 'reactstrap'; -import { boxStyle, boxStyleDark } from '~/styles'; import BadgeDevelopmentTable from './BadgeDevelopmentTable'; import BadgeTypes from './BadgeTypes'; import CreateNewBadgePopup from './CreateNewBadgePopup'; import '../Header/index.module.css'; -import { matches } from 'lodash'; +import styles from './BadgeDevelopment.module.css'; + +function normalize(str) { + return str.replace(/\s+/g, '').toLowerCase(); +} + +function badgeMatchesFilters(badge, searchName, searchType, rankFilter, chooseRankFilterNumber) { + if (searchName !== '' && !normalize(badge.badgeName).includes(normalize(searchName))) + return false; + if (searchType !== '' && !normalize(badge.type).includes(normalize(searchType))) return false; + if (badge.ranking > rankFilter) return false; + if (chooseRankFilterNumber !== null && badge.ranking !== chooseRankFilterNumber) return false; + return true; +} + +function getDark(darkMode, darkClass, lightClass = '') { + return darkMode ? darkClass : lightClass; +} function BadgeDevelopment(props) { const { darkMode, allBadgeData = [] } = props; @@ -19,64 +35,47 @@ function BadgeDevelopment(props) { const toggle = () => setCreateNewBadgePopupOpen(prevIsOpen => !prevIsOpen); const toggleFilters = () => setAddFiltersOpen(prevState => !prevState); - // convert rank number into integer const chooseRankFilterNumber = chooseRankFilter ? Number(chooseRankFilter) : null; - // filter badge data based on type, report, and rank - const filteredBadgeData = allBadgeData.filter(badge => { - const matchesType = - searchType === '' || - badge.type - .replace(/\s+/g, '') - .toLowerCase() - .includes(searchType.replace(/\s+/g, '').toLowerCase()); - const matchesRank = badge.ranking <= rankFilter; - const matchesChoosenRank = - chooseRankFilterNumber === null || badge.ranking === chooseRankFilterNumber; - const matchesName = - searchName === '' || - badge.badgeName - .replace(/\s+/g, '') - .toLowerCase() - .includes(searchName.replace(/\s+/g, '').toLowerCase()); + const filteredBadgeData = allBadgeData.filter(badge => + badgeMatchesFilters(badge, searchName, searchType, rankFilter, chooseRankFilterNumber), + ); - return matchesType && matchesRank && matchesName && matchesChoosenRank; - }); + const labelClass = `${styles.filterLabel} ${getDark(darkMode, 'text-light', 'text-dark')}`; + const inputClass = `${styles.filterInput} ${getDark(darkMode, styles.filterInputDark)}`; return ( -
+
{isAddFiltersOpen && ( -
-
-

Search for a badge:

+
+
+

Search for a badge:

setSearchName(e.target.value)} - className={darkMode ? 'bg-darkmode-liblack text-light border-0' : ''} + className={inputClass} />
-
-

Filter by type:

+
+

Filter by type:

-
-

- Filter by Rank (0 - {rankFilter}): -

+
+

Filter by Rank (0 - {rankFilter}):

setRankFilter(e.target.value)} /> -
-

Or choose a rank:

- setChooseRankFilter(e.target.value)} - style={{ width: '80px', textAlign: 'center' }} - className={darkMode ? 'bg-darkmode-liblack text-light border-0' : ''} - /> -
+

+ Or choose a rank: +

+ setChooseRankFilter(e.target.value)} + className={`${styles.rankInput} ${getDark(darkMode, styles.filterInputDark)}`} + />
)} - + New Badge - + diff --git a/src/components/Badge/BadgeDevelopment.module.css b/src/components/Badge/BadgeDevelopment.module.css new file mode 100644 index 0000000000..beb6094182 --- /dev/null +++ b/src/components/Badge/BadgeDevelopment.module.css @@ -0,0 +1,73 @@ +.btn { + margin: 20px; + box-shadow: 2px 2px 4px 1px #d3d3d3; +} + +.btnDark { + margin: 20px; + box-shadow: 0.5px 0.5px 5px #000; +} + +.filtersContainer { + margin-top: 20px; + padding-left: 20px; +} + +.filterRow { + display: flex; + align-items: center; + margin-bottom: 10px; +} + +.filterRowLast { + display: flex; + align-items: center; +} + +.filterLabel { + margin: 0; + margin-right: 8px; +} + +.filterLabelMid { + margin: 0; + margin-left: 8px; + margin-right: 8px; +} + +.filterInput { + padding: 5px; + border-radius: 5px; + border: 1px solid #ccc; + outline: none; +} + +.rankInput { + width: 80px; + text-align: center; + padding: 5px; + border-radius: 5px; + border: 1px solid #ccc; + outline: none; +} + +.filtersContainer input::placeholder, +.filtersContainer select::placeholder { + color: inherit; + opacity: 1; +} + +.filterInputDark { + background-color: #1c1c1c; + color: #fff; + border: 1px solid #555; +} + +.filterInputDark::placeholder { + color: #bbb; +} + +.filterInputDark option { + background-color: #1c1c1c; + color: #fff; +} diff --git a/src/components/Badge/BadgeDevelopmentTable.jsx b/src/components/Badge/BadgeDevelopmentTable.jsx index 1fd1a839b3..628f701a1f 100644 --- a/src/components/Badge/BadgeDevelopmentTable.jsx +++ b/src/components/Badge/BadgeDevelopmentTable.jsx @@ -22,6 +22,7 @@ import EditBadgePopup from './EditBadgePopup'; import DeleteBadgePopup from './DeleteBadgePopup'; import hasPermission from '../../utils/permissions'; import './Badge.module.css'; +import styles from './BadgeDevelopmentTable.module.css'; function BadgeDevelopmentTable(props) { const { darkMode } = props; @@ -271,7 +272,9 @@ function BadgeDevelopmentTable(props) { return (
-

Badge Management

+

Badge Management