Skip to content

Commit 1738c8d

Browse files
Merge pull request #4603 from OneCommunityGlobal/shashank-madan-redo-pr-3668
Shashank madan redo pr 3668
2 parents 118168f + d697200 commit 1738c8d

19 files changed

Lines changed: 2936 additions & 1129 deletions

package-lock.json

Lines changed: 1070 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/actions/__tests__/userManagement.test.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const mockStore = configureMockStore(middlewares);
1414

1515
describe('User Management Actions', () => {
1616
let store;
17-
17+
1818
beforeEach(() => {
1919
store = mockStore({});
2020
vi.clearAllMocks();
@@ -46,7 +46,7 @@ describe('User Management Actions', () => {
4646

4747
it('should update user to active status', async () => {
4848
const reactivationDate = null;
49-
49+
5050
axios.patch.mockResolvedValueOnce({ data: {} });
5151

5252
await store.dispatch(actions.updateUserStatus(mockUser, UserStatus.Active, reactivationDate));
@@ -143,7 +143,7 @@ describe('User Management Actions', () => {
143143
);
144144
});
145145

146-
146+
147147
});
148148

149149
describe('updateUserFinalDayStatus', () => {
@@ -205,6 +205,7 @@ describe('User Management Actions', () => {
205205
{ id: 1, name: 'John Doe', email: 'john@example.com' },
206206
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
207207
];
208+
const mockSource = 'Report';
208209

209210
axios.get.mockResolvedValueOnce({ data: mockBasicInfo });
210211

@@ -213,20 +214,20 @@ describe('User Management Actions', () => {
213214
{ type: 'RECEIVE_USER_PROFILE_BASIC_INFO', payload: mockBasicInfo }
214215
];
215216

216-
await store.dispatch(actions.getUserProfileBasicInfo());
217+
await store.dispatch(actions.getUserProfileBasicInfo({source: mockSource}));
217218
expect(store.getActions()).toEqual(expectedActions);
218-
expect(axios.get).toHaveBeenCalledWith(ENDPOINTS.USER_PROFILE_BASIC_INFO);
219+
expect(axios.get).toHaveBeenCalledWith(ENDPOINTS.USER_PROFILE_BASIC_INFO(mockSource));
219220
});
220221

221222
it('should handle errors when fetching basic info', async () => {
222223
axios.get.mockRejectedValueOnce(new Error('Network error'));
223-
224+
const mockSource = '';
224225
const expectedActions = [
225226
{ type: 'FETCH_USER_PROFILE_BASIC_INFO' },
226227
{ type: 'FETCH_USER_PROFILE_BASIC_INFO_ERROR' }
227228
];
228229

229-
await store.dispatch(actions.getUserProfileBasicInfo());
230+
await store.dispatch(actions.getUserProfileBasicInfo({ source: mockSource }));
230231
expect(store.getActions()).toEqual(expectedActions);
231232
});
232233
});
@@ -276,4 +277,4 @@ describe('User Management Actions', () => {
276277
expect(store.getActions()).toContainEqual(expectedAction);
277278
});
278279
});
279-
});
280+
});

src/actions/userManagement.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -309,15 +309,18 @@ export const updateUserFinalDay = (user, finalDayDate, isSet) => {
309309

310310
/**
311311
* fetching all user profiles basic info
312+
* Added `source` parameter to identify the calling component.
312313
*/
313-
export const getUserProfileBasicInfo = (userId) => {
314+
export const getUserProfileBasicInfo = ({ userId, source }) => {
314315
// API request to fetch basic user profile information
315-
let userProfileBasicInfoPromise;
316-
if (userId)
316+
let userProfileBasicInfoPromise;
317+
if (userId)
317318
userProfileBasicInfoPromise = axios.get(`${ENDPOINTS.USER_PROFILE_BASIC_INFO}?userId=${userId}`);
318-
else
319+
else if (source)
320+
userProfileBasicInfoPromise = axios.get(ENDPOINTS.USER_PROFILE_BASIC_INFO(source));
321+
else
319322
userProfileBasicInfoPromise = axios.get(ENDPOINTS.USER_PROFILE_BASIC_INFO);
320-
323+
321324
return async dispatch => {
322325
// Dispatch action indicating the start of the fetch process
323326
await dispatch(userProfilesBasicInfoFetchStartAction());

src/components/HGNForm/pages/Page1.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function Page1() {
1414

1515
useEffect(() => {
1616
if (user?.userid) {
17-
dispatch(getUserProfileBasicInfo(user.userid));
17+
dispatch(getUserProfileBasicInfo({ userId: user.userid }));
1818
}
1919
}, [dispatch, user?.userid]);
2020

src/components/Reports/ReportFilter/ReportFilter.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class ReportFilter extends Component {
3838
return (
3939
<div>
4040
<div>
41-
<button type="button" style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', color: 'inherit', textDecoration: 'none' }}>
41+
<button type="button" style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', color: '#007bff', textDecoration: 'none' }}>
4242
Select a Filter
4343
</button>
4444
</div>

src/components/Reports/Reports.jsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ class ReportsPage extends Component {
115115
async componentDidMount() {
116116
const fetchProjects = this.props.fetchAllProjects();
117117
const fetchTeams = this.props.getAllUserTeams();
118-
const fetchUserProfile = this.props.getUserProfileBasicInfo();
118+
// Added 'Report' parameter to identify the source for fetching all user basic info
119+
const fetchUserProfile = this.props.getUserProfileBasicInfo({source:'Report'});
119120

120121
// parallel api calls
121122
await Promise.all([fetchProjects, fetchTeams, fetchUserProfile]);
@@ -505,7 +506,7 @@ class ReportsPage extends Component {
505506
this.state.peopleSearchData = this.filteredPeopleList(this.state.peopleSearchData);
506507
}
507508

508-
const isOxfordBlue = darkMode ? 'bg-oxford-blue' : '';
509+
const isOxfordBlue = darkMode ? 'bg-oxford-blue text-light' : 'bg-white-smoke';
509510
const isYinmnBlue = darkMode ? 'bg-yinmn-blue' : '';
510511
const textColor = darkMode ? 'text-blue-400' : 'text-dark';
511512
const boxStyling = darkMode ? boxStyleDark : boxStyle;
@@ -531,15 +532,15 @@ class ReportsPage extends Component {
531532
type="button"
532533
>
533534
<div className="container-component-category">
534-
<h2 className="mt-3 mb-5">
535+
<h2 className="mt-3 ">
535536
{/* Loading spinner at the top */}
536537
{this.state.loading && (
537538
<div className="loading-spinner-top">
538539
<Loading align="center" darkMode={darkMode} />
539540
</div>
540541
)}
541542
<div className="d-flex align-items-center">
542-
<h2 className="mr-2">Reports Page</h2>
543+
<span className={`mr-2 `}>Reports Page</span>
543544
<EditableInfoModal
544545
areaName="ReportsPage"
545546
areaTitle="Reports Page"
@@ -552,7 +553,7 @@ class ReportsPage extends Component {
552553
</div>
553554
</h2>
554555
<div>
555-
<div className={darkMode ? `text-white` : ``}>Select a Category</div>
556+
<p >Select a Category</p>
556557
</div>
557558
<div className='report-container-data'>
558559
<div className='data-container' style={this.state.showCharts ? {width: '50%'} : {width: '100%'}}>
@@ -602,7 +603,7 @@ class ReportsPage extends Component {
602603
style={darkMode ? boxStyleDark : boxStyle}
603604
>
604605
<ReportFilter
605-
filterStatus={this.state.filterStatus}
606+
filterStatus={this.state.filterStatus}
606607
setFilterStatus={this.setFilterStatus}
607608
onWildCardSearch={this.onWildCardSearch}
608609
onCreateNewTeamShow={this.onCreateNewTeamShow}
@@ -670,9 +671,9 @@ class ReportsPage extends Component {
670671

671672
</div>
672673
<div className="total-report-item">
673-
<Button
674-
type="button"
675-
color="info"
674+
<Button
675+
type="button"
676+
color="info"
676677
onClick={this.showContributorsReport}
677678
>
678679
{this.state.showContributorsReport

src/components/Reports/TotalReport/TotalContributorsReport.jsx

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import { useEffect, useState, useMemo, useCallback } from 'react';
22
import axios from 'axios';
33
import { ENDPOINTS } from '~/utils/URL';
4-
import './TotalReport.css';
4+
import styles from './TotalReport.module.css';
55
import TotalReportBarGraph from './TotalReportBarGraph';
66
import Loading from '../../common/Loading';
77
import EditableInfoModal from '../../UserProfile/EditableModal/EditableInfoModal';
8+
import { generateBarData as generateBarDataUtil } from './generateBarData';
9+
import {
10+
getCachedData,
11+
setCachedData,
12+
validateUserList,
13+
logApiRequest,
14+
logApiResponse,
15+
} from './cacheUtils';
816

917
function TotalContributorsReport({ startDate, endDate, userProfiles, darkMode, userRole }) {
1018
const [contributors, setContributors] = useState([]);
@@ -17,37 +25,73 @@ function TotalContributorsReport({ startDate, endDate, userProfiles, darkMode, u
1725

1826
const fromDate = useMemo(() => startDate.toLocaleDateString('en-CA'), [startDate]);
1927
const toDate = useMemo(() => endDate.toLocaleDateString('en-CA'), [endDate]);
20-
const userList = useMemo(() => userProfiles.map(({ _id }) => _id), [userProfiles]);
28+
const userList = useMemo(() => {
29+
const list = userProfiles?.map(({ _id }) => _id) || [];
30+
// eslint-disable-next-line no-console
31+
console.log('TotalContributorsReport userList created:', {
32+
userProfilesLength: userProfiles?.length,
33+
userListLength: list.length,
34+
});
35+
return list;
36+
}, [userProfiles]);
2137

2238
// Fetch time entries for the selected period
2339
const loadTimeEntriesForPeriod = useCallback(async (controller) => {
40+
const reportName = 'TotalContributorsReport';
2441
const url = ENDPOINTS.TIME_ENTRIES_REPORTS;
2542

2643
if (!url) {
2744
return;
2845
}
46+
47+
// Validate userList
48+
if (!validateUserList(userList, userProfiles, reportName)) {
49+
setTimeEntries([]);
50+
setLoading(false);
51+
return;
52+
}
53+
54+
// Check cache with date range key
55+
const cacheKey = `${reportName}_${fromDate}_${toDate}`;
56+
const cached = getCachedData(cacheKey, reportName);
57+
if (cached.data) {
58+
setTimeEntries(cached.data);
59+
setLoading(false);
60+
return;
61+
}
62+
2963
try {
64+
logApiRequest(reportName, url, { users: userList, fromDate, toDate }, {
65+
usersCount: userList?.length,
66+
});
67+
3068
const response = await axios.post(
3169
url,
3270
{ users: userList, fromDate, toDate },
3371
{ signal: controller.signal }
3472
);
73+
74+
logApiResponse(reportName, response.data?.length);
75+
3576
const mappedTimeEntries = response.data.map(entry => ({
3677
userId: entry.personId,
3778
hours: entry.hours,
3879
minutes: entry.minutes,
3980
isTangible: entry.isTangible,
4081
date: entry.dateOfWork,
4182
}));
83+
4284
setTimeEntries(mappedTimeEntries);
85+
setCachedData(cacheKey, mappedTimeEntries, reportName);
4386
} catch (error) {
4487
// eslint-disable-next-line import/no-named-as-default-member
4588
if (!axios.isCancel(error)) {
46-
// Handle error silently or show user-friendly message
89+
// eslint-disable-next-line no-console
90+
console.error(`${reportName} API Error:`, error);
4791
setTimeEntries([]);
4892
}
4993
}
50-
}, [fromDate, toDate, userList]);
94+
}, [fromDate, toDate, userList, userProfiles]);
5195

5296
// Group time entries by user and calculate total hours
5397
const sumByUser = useCallback((entries) => {
@@ -102,25 +146,7 @@ function TotalContributorsReport({ startDate, endDate, userProfiles, darkMode, u
102146

103147
// Generate bar chart data
104148
const generateBarData = useCallback((groupedDate, isYear = false) => {
105-
if (isYear) {
106-
const startMonth = startDate.getMonth();
107-
const endMonth = endDate.getMonth();
108-
const sumData = groupedDate.map(range => ({
109-
label: range.timeRange,
110-
value: range.usersOfTime.length,
111-
months: 12,
112-
}));
113-
if (sumData.length > 1) {
114-
sumData[0].months = 12 - startMonth;
115-
sumData[sumData.length - 1].months = endMonth + 1;
116-
}
117-
const filteredData = sumData.filter(data => data.value > 0);
118-
return filteredData;
119-
}
120-
return groupedDate.map(range => ({
121-
label: range.timeRange,
122-
value: range.usersOfTime.length,
123-
}));
149+
return generateBarDataUtil(groupedDate, isYear, startDate, endDate, 'usersOfTime');
124150
}, [startDate, endDate]);
125151

126152
// Check if we should show monthly/yearly summaries
@@ -141,13 +167,23 @@ function TotalContributorsReport({ startDate, endDate, userProfiles, darkMode, u
141167

142168
// Load data when date range changes
143169
useEffect(() => {
170+
// Only make API call if userList has data
171+
if (!userList || userList.length === 0) {
172+
// eslint-disable-next-line no-console
173+
console.log('TotalContributorsReport: Waiting for userProfiles to load...', {
174+
userProfilesLength: userProfiles?.length,
175+
userListLength: userList?.length,
176+
});
177+
return;
178+
}
179+
144180
setLoading(true);
145181
const controller = new AbortController();
146182
loadTimeEntriesForPeriod(controller).then(() => {
147183
setLoading(false);
148184
});
149185
return () => controller.abort();
150-
}, [loadTimeEntriesForPeriod]);
186+
}, [loadTimeEntriesForPeriod, userList]);
151187

152188
// Process data when time entries are loaded
153189
useEffect(() => {
@@ -168,9 +204,9 @@ function TotalContributorsReport({ startDate, endDate, userProfiles, darkMode, u
168204
const totalTangibleTime = contributors.reduce((acc, obj) => acc + Number(obj.tangibleTime), 0);
169205

170206
return (
171-
<div className={`total-container ${darkMode ? 'bg-yinmn-blue text-light' : ''}`}>
207+
<div className={`${styles.totalContainer} ${darkMode ? 'bg-yinmn-blue text-light' : ''}`}>
172208
<div className="d-flex align-items-center">
173-
<h2 className={`total-title ${darkMode ? 'text-azure' : ''}`}>Contributors Report</h2>
209+
<h2 className={`${styles.totalTitle} ${darkMode ? 'text-azure' : ''}`}>Contributors Report</h2>
174210
<EditableInfoModal
175211
areaName="contributorsReportInfo"
176212
areaTitle="Contributors Report"
@@ -181,16 +217,16 @@ function TotalContributorsReport({ startDate, endDate, userProfiles, darkMode, u
181217
darkMode={darkMode}
182218
/>
183219
</div>
184-
<div className="total-period">
220+
<div className={styles.totalPeriod}>
185221
In the period from {startDate.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' })} to {endDate.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' })}:
186222
</div>
187-
<div className="total-item">
188-
<div className="total-number">{contributors.length}</div>
189-
<div className="total-text">members have contributed more than 10 hours.</div>
223+
<div className={styles.totalItem}>
224+
<div className={styles.totalNumber}>{contributors.length}</div>
225+
<div className={styles.totalText}>members have contributed more than 10 hours.</div>
190226
</div>
191-
<div className="total-item">
192-
<div className="total-number">{totalTangibleTime.toFixed(2)}</div>
193-
<div className="total-text">hours of tangible time have been logged.</div>
227+
<div className={styles.totalItem}>
228+
<div className={styles.totalNumber}>{totalTangibleTime.toFixed(2)}</div>
229+
<div className={styles.totalText}>hours of tangible time have been logged.</div>
194230
</div>
195231
<div>
196232
{showMonthly && contributorsInMonth.length > 0 && (

0 commit comments

Comments
 (0)