diff --git a/web-ui/src/components/reports-section/checkin-report/CheckinReport.css b/web-ui/src/components/reports-section/checkin-report/CheckinReport.css
deleted file mode 100644
index 3a9ffedbb0..0000000000
--- a/web-ui/src/components/reports-section/checkin-report/CheckinReport.css
+++ /dev/null
@@ -1,24 +0,0 @@
-.checkin-report-card {
- display: flex;
- flex-direction: column;
- margin: 1rem 0;
- width: 100vw;
-
- .checkin-report-stepper {
- margin-bottom: 1rem;
- }
-
- .checkin-report-card-name {
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- max-width: 20rem;
- }
-
- .checkin-report-card-actions {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 1rem;
- }
-}
diff --git a/web-ui/src/components/reports-section/checkin-report/CheckinReport.jsx b/web-ui/src/components/reports-section/checkin-report/CheckinReport.jsx
deleted file mode 100644
index f4fded03d5..0000000000
--- a/web-ui/src/components/reports-section/checkin-report/CheckinReport.jsx
+++ /dev/null
@@ -1,219 +0,0 @@
-import React, { useContext, useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
-
-import { getAvatarURL } from '../../../api/api.js';
-import { AppContext } from '../../../context/AppContext.jsx';
-import { selectFilteredCheckinsForTeamMemberAndPDL } from '../../../context/selectors.js';
-
-import ExpandMore from '../../expand-more/ExpandMore.jsx';
-import HorizontalLinearStepper from './HorizontalLinearStepper.jsx';
-import TeamMemberMap from './TeamMemberMap.jsx';
-import { statusForPeriodByMemberScheduling } from './checkin-utils.js';
-
-import {
- Avatar,
- Box,
- Card,
- CardContent,
- CardHeader,
- Chip,
- Collapse,
- Container,
- Divider,
- Typography,
- Badge
-} from '@mui/material';
-
-import './CheckinReport.css';
-import dayjs from 'dayjs';
-
-const CheckinsReport = ({ closed, pdl, planned, reportDate }) => {
- const { state } = useContext(AppContext);
- const [expanded, setExpanded] = useState(true);
- const [statusForPeriod, setStatusForPeriod] = useState(
- /** @type CheckinStatus */ ('Not Started')
- );
-
- const { name, id, members, workEmail, title, terminationDate } = pdl;
- const handleExpandClick = () => setExpanded(!expanded);
-
- /**
- * Determine the status of the check-ins for the period based on the members.
- * @param {MemberProfile[]} members - The members of the PDL.
- * @returns {CheckinStatus} The status of the check-ins for the period.
- */
- const statusForPeriodByMembers = (members = []) => {
- if (members.length === 0) return 'No Members';
-
- const allMembersCompleted = members.every(
- member =>
- statusForPeriodByMemberScheduling(
- selectFilteredCheckinsForTeamMemberAndPDL(
- state,
- member.id,
- id,
- closed,
- planned
- ),
- reportDate
- ) === 'Completed'
- );
-
- // Done when all PDL team members have completed check-ins
- if (allMembersCompleted) return 'Done';
-
- const anyMembersCompleted = members.some(
- member =>
- statusForPeriodByMemberScheduling(
- selectFilteredCheckinsForTeamMemberAndPDL(
- state,
- member.id,
- id,
- closed,
- planned
- ),
- reportDate
- ) === 'Completed'
- );
-
- const anyInProgress = members.some(
- member =>
- statusForPeriodByMemberScheduling(
- selectFilteredCheckinsForTeamMemberAndPDL(
- state,
- member.id,
- id,
- closed,
- planned
- ),
- reportDate
- ) === 'Scheduled'
- );
-
- // In progress when there is at least one scheduled check-in
- if (anyMembersCompleted || anyInProgress) return 'In Progress';
-
- // Not started when no check-ins are scheduled
- return 'Not Started';
- };
-
- /**
- * Set the expanded state based on the status of the check-ins and number of members.
- * @param {CheckinStatus} status - The status of the check-ins.
- * @param {PDLProfile} pdl - The PDL object.
- * @modifies {expanded}
- */
- const setExpandedByStatusAndMembers = (status, pdl) => {
- const isStatusDone = status === 'Done';
- const hasMembers = pdl.members && pdl.members.length !== 0;
-
- if (isStatusDone || !hasMembers) {
- setExpanded(false);
- } else {
- setExpanded(true);
- }
- };
-
- // Set status for the period based on members and termination date
- useEffect(() => {
- terminationDate
- ? setStatusForPeriod('Terminated')
- : setStatusForPeriod(statusForPeriodByMembers(members));
- }, [members, reportDate]);
-
- // Set expanded state based on status and number of members
- useEffect(() => {
- setExpandedByStatusAndMembers(statusForPeriod, pdl);
- }, [statusForPeriod, pdl]);
-
- return (
-
-
-
-
- {name}
-
-
- {title}
-
-
- }
- disableTypography
- avatar={}
- action={
-
-
- {statusForPeriod === 'Terminated' ? (
-
- Effective {dayjs(terminationDate).format('MMM D, YYYY')}
-
-
- ) : (
-
- )}
-
-
-
- }
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-const propTypes = {
- closed: PropTypes.bool,
- pdl: PropTypes.shape({
- name: PropTypes.string,
- id: PropTypes.string,
- members: PropTypes.array,
- workEmail: PropTypes.string,
- title: PropTypes.string,
- terminationDate: PropTypes.instanceOf(Date)
- }),
- planned: PropTypes.bool,
- reportDate: PropTypes.instanceOf(Date)
-};
-
-CheckinsReport.propTypes = propTypes;
-export default CheckinsReport;
diff --git a/web-ui/src/components/reports-section/checkin-report/LinkSection.css b/web-ui/src/components/reports-section/checkin-report/LinkSection.css
deleted file mode 100644
index 8a8fb2676c..0000000000
--- a/web-ui/src/components/reports-section/checkin-report/LinkSection.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.link-section-link-body {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- margin-bottom: 0.5rem;
-}
diff --git a/web-ui/src/components/reports-section/checkin-report/LinkSection.jsx b/web-ui/src/components/reports-section/checkin-report/LinkSection.jsx
deleted file mode 100644
index 0ffef2e7c2..0000000000
--- a/web-ui/src/components/reports-section/checkin-report/LinkSection.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { Chip, Typography } from '@mui/material';
-import { getCheckinDate } from './checkin-utils';
-import './LinkSection.css';
-
-const LinkSection = ({ checkin, member }) => {
- const now = new Date();
- let checkinDate = new Date(getCheckinDate(checkin));
- let dateString = new Date(getCheckinDate(checkin)).toString();
- dateString = dateString.split(' ').slice(0, 5).join(' ');
-
- return (
-
-
- {dateString}
- now
- ? 'Planned'
- : 'Open'
- }
- />
-
-
- );
-};
-
-export default LinkSection;
diff --git a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.css b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.css
index fc1a8dcd81..68795ba397 100644
--- a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.css
+++ b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.css
@@ -1,20 +1,24 @@
-.team-member-map-accordion {
- .team-member-map-accordion-summary {
+.team-member-map-row {
+ .team-member-map-row-summary {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0.5rem 1.5rem;
margin-top: 0.5rem;
- cursor: pointer;
.team-member-map-summmary-content {
display: grid;
- grid-template-columns: 2fr 2fr 1fr;
+ grid-template-columns: 2fr 2fr 2fr 1fr;
gap: 1rem;
align-items: center;
width: 100%;
margin: 0 0.75rem;
+
+ .team-member-map-pdl {
+ display: flex;
+ gap: 1rem;
+ }
}
.team-member-map-summmary-latest-activity {
diff --git a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx
index b74b0e835f..596138c0bc 100644
--- a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx
+++ b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx
@@ -1,50 +1,115 @@
-import React, { useContext } from 'react';
+import React, { useContext, useState } from 'react';
import {
- Accordion,
- AccordionSummary,
Avatar,
Chip,
Typography,
- AccordionDetails,
- Box
+ Box,
+ Card,
+ CardContent,
} from '@mui/material';
-import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import { Link } from 'react-router-dom';
import { getAvatarURL } from '../../../api/api.js';
import { AppContext } from '../../../context/AppContext.jsx';
-import { selectFilteredCheckinsForTeamMemberAndPDL } from '../../../context/selectors.js';
+import { selectCheckinsForMember } from '../../../context/selectors.js';
import {
- getCheckinDateForPeriod,
- getLastCheckinDate,
+ isPastCheckin,
+ getCheckinDate,
statusForPeriodByMemberScheduling
} from './checkin-utils.js';
-import LinkSection from './LinkSection.jsx';
+import { getQuarterBeginEnd } from '../../../helpers';
import './TeamMemberMap.css';
-const TeamMemberMap = ({ members, id, closed, planned, reportDate }) => {
+const SortOption = {
+ BY_MEMBER: 0,
+ BY_PDL: 1,
+};
+
+const TeamMemberMap = ({ members, closed, planned, reportDate }) => {
const { state } = useContext(AppContext);
+ const [sortBy, setSortBy] = useState(SortOption.BY_MEMBER);
+
const epoch = new Date(0);
+ const memberMap = members.reduce((map, member) => {
+ map[member.id] = member;
+ return map;
+ }, {});
+
+ const linkStyle={ textDecoration: 'none' };
+ const checkinPath = (member, checkin) => `/checkins/${member.id}/${checkin.id}`;
+
+ const sortByName = (left, right) => {
+ if (left && right) {
+ return `${left.lastName} ${left.firstName}`.localeCompare(
+ `${right.lastName} ${right.firstName}`);
+ } else {
+ return left ? -1 : 1;
+ }
+ };
+
+ const sortByPDLName = (a, b) =>
+ sortByName(memberMap[a.reportDatePDLId], memberMap[b.reportDatePDLId]);
+
+ // We're going to cache the checkins into the member data structures so that
+ // we only have to do this once per member.
+ const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(reportDate);
+ members.map(member => {
+ member.checkins = selectCheckinsForMember(
+ state,
+ member.id,
+ ).filter(checkin => closed || !checkin.completed)
+ .filter(checkin => planned || isPastCheckin(checkin))
+ .filter(checkin => (getCheckinDate(checkin) <= endOfQuarter));
+
+ // If there are checkins, we're going to sort them with the latest
+ // first. Since the member's PDL could have changed since the last
+ // checkin, we are going to use the PDL id of the checkin with the report
+ // date range instead of the current PDL. They may be the same, but again
+ // they may not.
+ member.checkin = null;
+ member.reportDatePDLId = member.pdlId;
+ if (member.checkins.length > 0) {
+ member.checkins.sort((a, b) => getCheckinDate(b) - getCheckinDate(a));
+ const checkin = member.checkins[0];
+ if (getCheckinDate(checkin) >= startOfQuarter) {
+ member.checkin = checkin;
+ member.reportDatePDLId = member.checkin.pdlId;
+ }
+ }
+ });
+
+ members.sort(sortBy == SortOption.BY_MEMBER ? sortByName : sortByPDLName);
return (
- {members?.length > 0 ? (
+ {/* This header row mimics the configuration of the individual member
+ rows so that each header lines up exactly with the columns. */}
+
+
+ { setSortBy(SortOption.BY_MEMBER); }}
+ style={{ cursor: 'pointer' }}>
+ Member
+
+
+ {/* This forces the PDL header over. */}
+ { setSortBy(SortOption.BY_PDL); }}
+ style={{ cursor: 'pointer' }}>
+ PDL
+
+
+ Check-In Date
+
+
+ Status
+
+
+
+
+ {
members.map(member => {
- const checkins = selectFilteredCheckinsForTeamMemberAndPDL(
- state,
- member.id,
- id,
- closed,
- planned
- );
-
+ const pdl = memberMap[member.reportDatePDLId];
return (
-
- }
- className="team-member-map-accordion-summary"
- >
+
+
{
{member.title}
+ {pdl
+ ?
+
+
+ {pdl.name}
+
+ {pdl.title}
+
+
+
+ :
+ No PDL Assigned
+
+ }
-
- Check-In date:
-
- {getCheckinDateForPeriod(
- checkins,
- reportDate
- ).getFullYear() === epoch.getFullYear() ? (
+ {member.checkin == null ? (
No activity yet{' '}
@@ -78,20 +154,17 @@ const TeamMemberMap = ({ members, id, closed, planned, reportDate }) => {
) : (
- <>
+
- {getCheckinDateForPeriod(
- checkins,
- reportDate
- ).toLocaleDateString(navigator.language, {
+ {getCheckinDate(member.checkin)
+ .toLocaleDateString(navigator.language, {
year: 'numeric',
month: '2-digit',
day: 'numeric'
})}{' '}
- {getCheckinDateForPeriod(
- checkins,
- reportDate
- ).getTime() > new Date().getTime() ? (
+ {getCheckinDate(member.checkin)
+ .getTime() > new Date().getTime() ? (
📆
@@ -101,18 +174,27 @@ const TeamMemberMap = ({ members, id, closed, planned, reportDate }) => {
)}
- >
+
)}
+ {member.checkin == null && member.checkins.length > 0 &&
+ Last Activity: {
+
+ {getCheckinDate(member.checkins[0]).toString()
+ .split(' ').slice(0, 5).join(' ')}
+ }
+
+ }
{
}
/>
-
-
- {checkins.length === 0
- ? 'No check-in activity found for this member and PDL.'
- : checkins.map(checkin => (
-
- ))}
-
-
+
+
);
})
- ) : (
-
- No team members associated with this PDL.
-
- )}
+ }
);
};
diff --git a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.test.jsx b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.test.jsx
index bb54fbc8e1..3b7d21c597 100644
--- a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.test.jsx
+++ b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.test.jsx
@@ -39,25 +39,6 @@ adminState.state.userProfile = { ...adminState.state.userProfile };
adminState.state.userProfile.role = ['MEMBER', 'ADMIN'];
describe('TeamMemberMap', () => {
- it('should render the component without team members', () => {
- const withoutTeamMembers = {
- members: [],
- id: '2c1b77e2-e2fc-46d1-92f2-beabbd28ee3d',
- closed: true,
- planned: true,
- reportDate: new Date()
- };
- render(
-
-
-
- );
- const message = screen.getByText(
- 'No team members associated with this PDL.'
- );
- expect(message).toBeInTheDocument();
- });
-
it('should render the component with team members', () => {
const withTeamMembers = {
members,
diff --git a/web-ui/src/components/reports-section/checkin-report/checkin-utils.js b/web-ui/src/components/reports-section/checkin-report/checkin-utils.js
index e6f28af499..ded0d2e9fa 100644
--- a/web-ui/src/components/reports-section/checkin-report/checkin-utils.js
+++ b/web-ui/src/components/reports-section/checkin-report/checkin-utils.js
@@ -10,7 +10,7 @@ import { getQuarterBeginEnd } from '../../../helpers';
* @returns {Date} The date of the check-in.
*/
export const getCheckinDate = checkin => {
- if (!checkin || !checkin.checkInDate) return;
+ if (!checkin || !checkin.checkInDate) return new Date(0);
const [year, month, day, hour, minute] = checkin.checkInDate;
return new Date(year, month - 1, day, hour, minute, 0);
};
@@ -30,47 +30,42 @@ export const getLastCheckinDate = checkins => {
/**
* Get the date of the last scheduled check-in for the reporting period.
- * Include the grace period for the end of the quarter.
* @param {Checkin[]} checkins - Check-ins for a member.
* @param {Date} reportDate - The date of the report.
* @returns {Date} The date of the last scheduled check-in.
*/
export const getCheckinDateForPeriod = (checkins, reportDate) => {
const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(reportDate);
- const endOfQuarterWithGrace = new Date(endOfQuarter);
- endOfQuarterWithGrace.setDate(endOfQuarter.getDate() + 30);
const scheduled = checkins.filter(checkin => {
const checkinDate = getCheckinDate(checkin);
- return (
- checkinDate >= startOfQuarter && checkinDate <= endOfQuarterWithGrace // Include grace period
- );
+ return (checkinDate >= startOfQuarter && checkinDate <= endOfQuarter);
});
return getLastCheckinDate(scheduled);
};
/**
* Determine check-in status for a member during the reporting period.
- * Include the grace period for the end of the quarter.
- * @param {Checkin[]} checkins - Check-ins for a member.
+ * @param {Checkin} checkin - Latest Check-in for a member.
* @param {Date} reportDate - The date of the report.
* @returns {SchedulingStatus} The status of the check-ins.
*/
export const statusForPeriodByMemberScheduling = (
- checkins = [],
+ checkin,
reportDate
) => {
- if (checkins.length === 0) return 'Not Scheduled';
+ if (!checkin) return 'Not Scheduled';
const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(reportDate);
- const endOfQuarterWithGrace = new Date(endOfQuarter);
- endOfQuarterWithGrace.setMonth(endOfQuarter.getMonth() + 1);
- const scheduled = checkins.filter(checkin => {
- const checkinDate = getCheckinDate(checkin);
- return (
- checkinDate >= startOfQuarter && checkinDate <= endOfQuarterWithGrace // Include grace period
- );
- });
- if (scheduled.length === 0) return 'Not Scheduled';
- const completed = scheduled.filter(checkin => checkin.completed);
- if (completed.length === scheduled.length) return 'Completed';
+ const checkinDate = getCheckinDate(checkin);
+ const scheduled = checkinDate >= startOfQuarter && checkinDate <= endOfQuarter;
+ if (!scheduled) return 'Not Scheduled';
+ if (checkin.completed) return 'Completed';
return 'Scheduled';
};
+
+/**
+ * Returns true if the checkin was in the past.
+ * @param {Checkin} checkin - A check-in.
+ * @returns {bool} Status of boolean check.
+ */
+export const isPastCheckin = (checkin) => Date.now() >= getCheckinDate(checkin).getTime();
+
diff --git a/web-ui/src/components/reports-section/checkin-report/checkin-utils.test.js b/web-ui/src/components/reports-section/checkin-report/checkin-utils.test.js
index e779e1eaf4..fa6cc95179 100644
--- a/web-ui/src/components/reports-section/checkin-report/checkin-utils.test.js
+++ b/web-ui/src/components/reports-section/checkin-report/checkin-utils.test.js
@@ -21,15 +21,15 @@ describe('getCheckinDate', () => {
expect(result.getMinutes()).toBe(30);
});
- test('returns undefined when check-in is not provided', () => {
+ test('returns epoch when check-in is not provided', () => {
const result = getCheckinDate(undefined);
- expect(result).toBeUndefined();
+ expect(result).toEqual(new Date(0)); // Date at epoch (Jan 1, 1970)
});
- test('returns undefined when check-in date is not available', () => {
+ test('returns epoch when check-in date is not available', () => {
const checkin = {};
const result = getCheckinDate(checkin);
- expect(result).toBeUndefined();
+ expect(result).toEqual(new Date(0)); // Date at epoch (Jan 1, 1970)
});
});
@@ -158,7 +158,7 @@ describe('statusForPeriodByMemberScheduling', () => {
const mockReportDate = new Date(2024, 3, 15); // April 15, 2024 (example report date)
test('returns "Not Scheduled" when no check-ins are provided', () => {
- const result = statusForPeriodByMemberScheduling([], mockReportDate);
+ const result = statusForPeriodByMemberScheduling(null, mockReportDate);
expect(result).toBe('Not Scheduled');
});
@@ -172,34 +172,10 @@ describe('statusForPeriodByMemberScheduling', () => {
expect(result).toBe('Not Scheduled');
});
- // There is a grace period of one month after the quarter in which we consider a check-in "In Progress"
- test('returns "Scheduled" when at least one check-in falls within the reporting grace period', () => {
- const checkins = [
- { checkInDate: [2024, 2, 1, 10, 30], completed: false }, // Feb 1, 2024
- { checkInDate: [2024, 3, 31, 9, 0], completed: false }, // March 31, 2024
- { checkInDate: [2024, 7, 10, 14, 0], completed: false } // Jul 10, 2024
- ];
- const result = statusForPeriodByMemberScheduling(checkins, mockReportDate);
- expect(result).toBe('Scheduled');
- });
-
- test('returns "Scheduled" when some check-ins within the reporting period are completed', () => {
- const checkins = [
- { checkInDate: [2024, 3, 1, 10, 0], completed: true }, // March 1, 2024 (within reporting period, completed)
- { checkInDate: [2024, 4, 1, 9, 0], completed: false }, // April 1, 2024 (within reporting period, not completed)
- { checkInDate: [2024, 4, 15, 14, 30], completed: false } // April 15, 2024 (within reporting period, not completed)
- ];
- const result = statusForPeriodByMemberScheduling(checkins, mockReportDate);
- expect(result).toBe('Scheduled');
- });
-
- test('returns "Scheduled" when all check-ins within the reporting period are not completed', () => {
- const checkins = [
- { checkInDate: [2024, 3, 1, 10, 0], completed: false }, // March 1, 2024 (within reporting period, not completed)
- { checkInDate: [2024, 4, 1, 9, 0], completed: false }, // April 1, 2024 (within reporting period, not completed)
- { checkInDate: [2024, 4, 15, 14, 30], completed: false } // April 15, 2024 (within reporting period, not completed)
- ];
- const result = statusForPeriodByMemberScheduling(checkins, mockReportDate);
+ test('returns "Scheduled" when the check-in is within the reporting period are not completed', () => {
+ const checkin =
+ { checkInDate: [2024, 4, 10, 10, 0], completed: false }; // April 1, 2024 (within reporting period, not completed)
+ const result = statusForPeriodByMemberScheduling(checkin, mockReportDate);
expect(result).toBe('Scheduled');
});
});
diff --git a/web-ui/src/components/routes/Routes.jsx b/web-ui/src/components/routes/Routes.jsx
index 6f6a10e51d..25bc64ee75 100644
--- a/web-ui/src/components/routes/Routes.jsx
+++ b/web-ui/src/components/routes/Routes.jsx
@@ -130,7 +130,7 @@ export default function Routes() {
-
+
diff --git a/web-ui/src/pages/CheckinsReportPage.jsx b/web-ui/src/pages/CheckinsReportPage.jsx
index f2f95c14d7..8653c7f1c4 100644
--- a/web-ui/src/pages/CheckinsReportPage.jsx
+++ b/web-ui/src/pages/CheckinsReportPage.jsx
@@ -2,9 +2,6 @@ import React, { useContext, useEffect, useState, useRef } from 'react';
import { AppContext } from '../context/AppContext';
import {
- selectTerminatedMembersAsOfDateWithPDLRole,
- selectCheckinPDLS,
- selectMappedPdls,
selectNormalizedMembers,
selectHasCheckinsReportPermission,
noPermission,
@@ -21,72 +18,27 @@ import {
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
-import CheckinReport from '../components/reports-section/checkin-report/CheckinReport';
+import TeamMemberMap from '../components/reports-section/checkin-report/TeamMemberMap';
import MemberSelector from '../components/member_selector/MemberSelector';
import { FilterType } from '../components/member_selector/member_selector_dialog/MemberSelectorDialog';
import {
getQuarterBeginEnd,
- useQueryParameters,
isArrayPresent,
getQuarterDisplay
} from '../helpers';
import './CheckinsReportPage.css';
-/**
- * Sort Members by the last name extracted from the full name.
- * @param {MemberProfile} a - First Member object
- * @param {MemberProfile} b - Second Member object
- * @returns {number} - Comparison result for sorting
- */
-function sortByLastName(a, b) {
- if (!a.name || !b.name) return 0;
- const lastNameA = a.name.split(' ').slice(-1)[0];
- const lastNameB = b.name.split(' ').slice(-1)[0];
- return lastNameA.localeCompare(lastNameB);
-}
-
-/**
- * Sort PDLs by the number of members they have (if available),
- * with PDLs having more members sorted first.
- * PDLs without a 'members' property are treated as having 0 members.
- * @param {PDLProfile} a - First PDL object
- * @param {PDLProfile} b - Second PDL object
- * @returns {number} - Comparison result for sorting
- */
-function sortByMembers(a, b) {
- const membersA = a.members ? a.members.length : 0;
- const membersB = b.members ? b.members.length : 0;
- return membersB - membersA;
-}
-
-/**
- * Sort PDLs by whether they have terminated members,
- * with PDLs having terminated members sorted last.
- * PDLs without a 'terminationDate' property are treated as not terminated.
- * @param {PDLProfile} a - First PDL object
- * @param {PDLProfile} b - Second PDL object
- * @returns {number} - Comparison result for sorting
- */
-function sortByTerminated(a, b) {
- const terminatedA = a.terminationDate ? 1 : 0;
- const terminatedB = b.terminationDate ? 1 : 0;
- return terminatedA - terminatedB;
-}
-
const CheckinsReportPage = () => {
const { state } = useContext(AppContext);
- const [selectedPdls, setSelectedPdls] = useState(
- /** @type {PDLProfile[]} */ ([])
- );
const [planned, setPlanned] = useState(true);
const [closed, setClosed] = useState(true);
-
const [searchText, setSearchText] = useState('');
-
const [reportDate, setReportDate] = useState(new Date());
const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(reportDate);
+ const members = selectNormalizedMembers(state, searchText);
+
// Set the report date to today less one month on first load
useEffect(() => {
setReportDate(new Date(new Date().setMonth(new Date().getMonth() - 1)));
@@ -96,88 +48,18 @@ const CheckinsReportPage = () => {
/** @type {HTMLButtonElement} */
const button = evt.currentTarget;
const isNextButton = button.attributes
- .getNamedItem('aria-label')
- .value.includes('Next');
- isNextButton
- ? setReportDate(new Date(reportDate.setMonth(reportDate.getMonth() + 3)))
- : setReportDate(new Date(reportDate.setMonth(reportDate.getMonth() - 3)));
+ .getNamedItem('aria-label')
+ .value.includes('Next');
+ setReportDate(new Date(reportDate.setMonth(reportDate.getMonth() +
+ (isNextButton ? 3 : -3))));
};
- /** @type {PDLProfile[]} */
- const pdls = selectCheckinPDLS(state, closed, planned);
-
- const processedQPs = useRef(false);
- useQueryParameters(
- [
- {
- name: 'pdls',
- default: [],
- value: selectedPdls,
- setter(ids) {
- const newPdls = ids
- .map(id => pdls.find(pdl => pdl.id === id))
- .filter(Boolean);
- newPdls.length > 0 && setSelectedPdls(newPdls);
- },
- toQP(newPdls) {
- if (isArrayPresent(newPdls)) {
- const ids = newPdls.map(pdl => pdl.id);
- return ids.join(',');
- } else {
- return [];
- }
- }
- }
- ],
- [pdls],
- processedQPs
- );
-
- // Update selected PDLs when processedQPs is updated
- useEffect(() => {
- if (selectedPdls.length === 0 && processedQPs.current) {
- setSelectedPdls(selectMappedPdls(state));
- }
- }, [processedQPs.current]);
-
- // Set the mapped PDLs to a full list of their members
- useEffect(() => {
- if (!pdls) return;
- pdls.forEach(pdl => {
- const allMembers = selectNormalizedMembers(state, searchText).filter(
- member => member.pdlId === pdl.id
- );
- pdl.members = allMembers;
- });
- pdls.filter(pdl => pdl.members.length > 0);
- }, [pdls, state]);
-
- // Include PDLs who have terminated within the quarter
- useEffect(() => {
- const pdlsTerminatedInQuarter = selectTerminatedMembersAsOfDateWithPDLRole(
- state,
- startOfQuarter
- );
- pdlsTerminatedInQuarter.forEach(pdl => {
- const allMembers = selectNormalizedMembers(state, searchText).filter(
- member => member.pdlId === pdl.id
- );
- pdl.members = allMembers;
- });
- pdlsTerminatedInQuarter.forEach(pdl => {
- if (!pdls.find(p => p.id === pdl.id)) {
- pdls.push(pdl);
- }
- });
- }, [state, startOfQuarter]);
-
// Keyboard navigation for changing quarters.
useEffect(() => {
const handleKeyDown = evt => {
if (evt.key === 'ArrowLeft') {
- document
- .querySelector('button[aria-label="Previous quarter`"]')
- .click();
+ document.querySelector('button[aria-label="Previous quarter`"]')
+ .click();
} else if (evt.key === 'ArrowRight') {
document.querySelector('button[aria-label="Next quarter`"]').click();
}
@@ -188,14 +70,6 @@ const CheckinsReportPage = () => {
return selectHasCheckinsReportPermission(state) ? (
-
@@ -243,31 +117,15 @@ const CheckinsReportPage = () => {
- {pdls.length > 0 ? (
- pdls
- .concat(selectedPdls.filter(pdl => !pdls.includes(pdl)))
- .sort(sortByLastName)
- .sort(sortByMembers)
- .sort(sortByTerminated)
- .map(pdl => {
- return (
-
- );
- })
- ) : (
-
-
No PDLs selected
-
- Please select some PDLs using the Member Selector.
-
-
- )}
+ {members
+ ?
+ : <>>
+ }
) : (
diff --git a/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap b/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap
index 9553f57e44..aead1181ef 100644
--- a/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap
+++ b/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap
@@ -13,95 +13,6 @@ exports[`renders correctly 1`] = `
-
-
-
@@ -201,16 +112,59 @@ exports[`renders correctly 1`] = `
class="checkins-report-page-reports"
>
-
- No PDLs selected
-
-
- Please select some PDLs using the Member Selector.
-
+
+
+
+ Member
+
+
+
+
+
+
+ PDL
+
+
+
+
+ Check-In Date
+
+
+
+
+ Status
+
+
+
+
+