From e97ae9733eb5f9cdb524e152149e83004a731fb2 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 14 Jan 2025 14:26:33 -0600 Subject: [PATCH 01/10] Initial modification of the Check-Ins Report to show by member and optionally sort by PDL. --- .../checkin-report/CheckinReport.css | 24 -- .../checkin-report/CheckinReport.jsx | 219 ------------------ .../checkin-report/TeamMemberMap.css | 7 +- .../checkin-report/TeamMemberMap.jsx | 89 +++++-- web-ui/src/components/routes/Routes.jsx | 2 +- web-ui/src/context/selectors.js | 2 +- web-ui/src/pages/CheckinsReportPage.jsx | 178 ++------------ 7 files changed, 97 insertions(+), 424 deletions(-) delete mode 100644 web-ui/src/components/reports-section/checkin-report/CheckinReport.css delete mode 100644 web-ui/src/components/reports-section/checkin-report/CheckinReport.jsx 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/TeamMemberMap.css b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.css index fc1a8dcd81..de8cfa7fd2 100644 --- a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.css +++ b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.css @@ -10,11 +10,16 @@ .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..8d2fec480c 100644 --- a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx +++ b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useContext, useState } from 'react'; import { Accordion, AccordionSummary, @@ -11,7 +11,10 @@ import { import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { getAvatarURL } from '../../../api/api.js'; import { AppContext } from '../../../context/AppContext.jsx'; -import { selectFilteredCheckinsForTeamMemberAndPDL } from '../../../context/selectors.js'; +import { + selectCheckinsForMember, + pastCheckin, +} from '../../../context/selectors.js'; import { getCheckinDateForPeriod, getLastCheckinDate, @@ -20,25 +23,65 @@ import { import LinkSection from './LinkSection.jsx'; 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 pdls = members.reduce((map, member) => { + if (member.pdlId && !map[member.pdlId]) { + map[member.pdlId] = members.find((m) => m.id === member.pdlId); + } + return map; + }, {}); + + 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(pdls[a.pdlId], pdls[b.pdlId]); + members.sort(sortBy == SortOption.BY_MEMBER ? sortByName : sortByPDLName); + + // TODO: Figure out how to do the column headers correctly. return ( - {members?.length > 0 ? ( + + { setSortBy(SortOption.BY_MEMBER); }}> + Member + + { setSortBy(SortOption.BY_PDL); }}> + PDL + + + Check-In Date + + + Status + + + { members.map(member => { - const checkins = selectFilteredCheckinsForTeamMemberAndPDL( + const pdl = pdls[member.pdlId]; + const checkins = selectCheckinsForMember( state, member.id, - id, - closed, - planned - ); + ).filter(checkin => closed || !checkin.completed) + .filter(checkin => planned || pastCheckin(checkin)); return ( { {member.title} + {pdl + ?
+ +
+ {pdl.name} + + {pdl.title} + +
+
+ : + No PDL Assigned + + } { className="team-member-map-summmary-latest-activity" > - - Check-In date: - {getCheckinDateForPeriod( checkins, reportDate @@ -135,11 +192,7 @@ const TeamMemberMap = ({ members, id, closed, planned, reportDate }) => {
); }) - ) : ( -
- No team members associated with this PDL. -
- )} + }
); }; 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/context/selectors.js b/web-ui/src/context/selectors.js index 9ecfb34ed9..739f314467 100644 --- a/web-ui/src/context/selectors.js +++ b/web-ui/src/context/selectors.js @@ -610,7 +610,7 @@ const getCheckinDate = checkin => { return new Date(year, month - 1, day, hour, minute, 0); }; -const pastCheckin = checkin => Date.now() >= getCheckinDate(checkin).getTime(); +export const pastCheckin = checkin => Date.now() >= getCheckinDate(checkin).getTime(); export const selectFilteredCheckinsForTeamMemberAndPDL = createSelector( selectCheckinsForTeamMemberAndPDL, 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 + ? + : <> + }
) : ( From 8fa49ac4e9f9b2ae02d6626814d7269657a98243 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 14 Jan 2025 14:36:44 -0600 Subject: [PATCH 02/10] Updated the test for TeamMemberMap. --- .../checkin-report/TeamMemberMap.test.jsx | 19 --- .../CheckinsReportPage.test.jsx.snap | 137 +++++------------- 2 files changed, 40 insertions(+), 116 deletions(-) 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/pages/__snapshots__/CheckinsReportPage.test.jsx.snap b/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap index 9553f57e44..2757118875 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`] = `
-
-
-
- -
-
- Select PDLs -
-
- ( - 0 - ) -
-
-
-
-
-
- - - - -
-
-
-
@@ -201,16 +112,48 @@ 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 +
+
+
From 5b75b33da21746d3e09742882acd6bec92d7019a Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 15 Jan 2025 07:58:08 -0600 Subject: [PATCH 03/10] Account for a PDL being different in the past from the current PDL. --- .../checkin-report/TeamMemberMap.jsx | 30 +++++++++++++++---- .../checkin-report/checkin-utils.js | 29 +++++++++++++++--- web-ui/src/context/selectors.js | 2 +- 3 files changed, 50 insertions(+), 11 deletions(-) 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 8d2fec480c..c8a765d4fd 100644 --- a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx +++ b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx @@ -11,11 +11,11 @@ import { import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { getAvatarURL } from '../../../api/api.js'; import { AppContext } from '../../../context/AppContext.jsx'; +import { selectCheckinsForMember } from '../../../context/selectors.js'; import { - selectCheckinsForMember, - pastCheckin, -} from '../../../context/selectors.js'; -import { + isPastCheckin, + getCheckinDate, + getQuarterBeginEndWithGrace, getCheckinDateForPeriod, getLastCheckinDate, statusForPeriodByMemberScheduling @@ -72,12 +72,30 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { { members.map(member => { - const pdl = pdls[member.pdlId]; + let pdl = pdls[member.pdlId]; const checkins = selectCheckinsForMember( state, member.id, ).filter(checkin => closed || !checkin.completed) - .filter(checkin => planned || pastCheckin(checkin)); + .filter(checkin => planned || isPastCheckin(checkin)); + + // 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 instead + // of the current PDL. They may be the same, but again they may not. + if (checkins.length > 0) { + checkins.sort((a, b) => getCheckinDate(b) - getCheckinDate(a)); + const latest = checkins[0]; + const { startOfQuarter, endOfQuarter } = + getQuarterBeginEndWithGrace(reportDate); + const checkinDate = getCheckinDate(latest); + if (checkinDate >= startOfQuarter && checkinDate <= endOfQuarter) { + console.log(member.firstName); + console.log("Current PDL: " + JSON.stringify(pdl)); + pdl = pdls[checkins[0].pdlId]; + console.log("PDL at the time: " + JSON.stringify(pdl)); + } + } return ( { }, new Date(0)); }; +/** + * Get the dates the reporting period including a grace period. + * @param {Date} reportDate - The date of the report. + * @returns {{ startOfQuarter: Date, endOfQuarter: Date }} The start and end dates of the quarter pluas a grace period. + */ +export const getQuarterBeginEndWithGrace = (reportDate) => { + const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(reportDate); + const endOfQuarterWithGrace = new Date(endOfQuarter); + endOfQuarterWithGrace.setDate(endOfQuarter.getDate() + 30); + return { + startOfQuarter: startOfQuarter, + endOfQuarter: endOfQuarterWithGrace + }; +}; + /** * Get the date of the last scheduled check-in for the reporting period. * Include the grace period for the end of the quarter. @@ -36,13 +51,11 @@ export const getLastCheckinDate = checkins => { * @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 { startOfQuarter, endOfQuarter } = getQuarterBeginEndWithGrace(reportDate); const scheduled = checkins.filter(checkin => { const checkinDate = getCheckinDate(checkin); return ( - checkinDate >= startOfQuarter && checkinDate <= endOfQuarterWithGrace // Include grace period + checkinDate >= startOfQuarter && checkinDate <= endOfQuarter // Include grace period ); }); return getLastCheckinDate(scheduled); @@ -74,3 +87,11 @@ export const statusForPeriodByMemberScheduling = ( if (completed.length === scheduled.length) 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/context/selectors.js b/web-ui/src/context/selectors.js index 739f314467..9ecfb34ed9 100644 --- a/web-ui/src/context/selectors.js +++ b/web-ui/src/context/selectors.js @@ -610,7 +610,7 @@ const getCheckinDate = checkin => { return new Date(year, month - 1, day, hour, minute, 0); }; -export const pastCheckin = checkin => Date.now() >= getCheckinDate(checkin).getTime(); +const pastCheckin = checkin => Date.now() >= getCheckinDate(checkin).getTime(); export const selectFilteredCheckinsForTeamMemberAndPDL = createSelector( selectCheckinsForTeamMemberAndPDL, From 584d7c016bbd5b1971d5f1f1c577360a7ca32b44 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 15 Jan 2025 08:41:49 -0600 Subject: [PATCH 04/10] Handle sorting by PDL when the PDL for the report period is not the current PDL. --- .../checkin-report/TeamMemberMap.jsx | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) 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 c8a765d4fd..8d50dc107d 100644 --- a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx +++ b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx @@ -49,7 +49,38 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { } }; - const sortByPDLName = (a, b) => sortByName(pdls[a.pdlId], pdls[b.pdlId]); + const sortByPDLName = (a, b) => + sortByName(pdls[a.reportDatePDLId], pdls[b.reportDatePDLId]); + + // We're going to cache the checkins into the member data structures so that + // we can properly sort by PDL when the PDL, in the past, is different than + // the current PDL. + const { startOfQuarter, endOfQuarter } = getQuarterBeginEndWithGrace(reportDate); + members.map(member => { + const checkins = selectCheckinsForMember( + state, + member.id, + ).filter(checkin => closed || !checkin.completed) + .filter(checkin => planned || isPastCheckin(checkin)); + member.checkins = checkins; + + // 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 instead + // of the current PDL. They may be the same, but again they may not. + member.reportDatePDLId = member.pdlId; + if (checkins.length > 0) { + checkins.sort((a, b) => getCheckinDate(b) - getCheckinDate(a)); + + for(let checkin of checkins) { + const checkinDate = getCheckinDate(checkin); + if (checkinDate >= startOfQuarter && checkinDate <= endOfQuarter) { + member.reportDatePDLId = checkin.pdlId; + break; + } + } + } + }); members.sort(sortBy == SortOption.BY_MEMBER ? sortByName : sortByPDLName); @@ -72,30 +103,8 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { { members.map(member => { - let pdl = pdls[member.pdlId]; - const checkins = selectCheckinsForMember( - state, - member.id, - ).filter(checkin => closed || !checkin.completed) - .filter(checkin => planned || isPastCheckin(checkin)); - - // 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 instead - // of the current PDL. They may be the same, but again they may not. - if (checkins.length > 0) { - checkins.sort((a, b) => getCheckinDate(b) - getCheckinDate(a)); - const latest = checkins[0]; - const { startOfQuarter, endOfQuarter } = - getQuarterBeginEndWithGrace(reportDate); - const checkinDate = getCheckinDate(latest); - if (checkinDate >= startOfQuarter && checkinDate <= endOfQuarter) { - console.log(member.firstName); - console.log("Current PDL: " + JSON.stringify(pdl)); - pdl = pdls[checkins[0].pdlId]; - console.log("PDL at the time: " + JSON.stringify(pdl)); - } - } + const pdl = pdls[member.reportDatePDLId]; + const checkins = member.checkins; return ( Date: Wed, 15 Jan 2025 09:06:07 -0600 Subject: [PATCH 05/10] Handle smaller screens and change the mouse pointer for the sortable column headings. --- .../reports-section/checkin-report/TeamMemberMap.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 8d50dc107d..31b8f4f0c1 100644 --- a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx +++ b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx @@ -84,17 +84,19 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { members.sort(sortBy == SortOption.BY_MEMBER ? sortByName : sortByPDLName); - // TODO: Figure out how to do the column headers correctly. return ( - { setSortBy(SortOption.BY_MEMBER); }}> + { setSortBy(SortOption.BY_MEMBER); }} + style={{ cursor: 'pointer' }}> Member - { setSortBy(SortOption.BY_PDL); }}> + { setSortBy(SortOption.BY_PDL); }} + style={{ cursor: 'pointer' }}> PDL - + Check-In Date @@ -147,7 +149,7 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { variant="caption" component={'time'} dateTime={getLastCheckinDate(checkins).toISOString()} - sx={{ display: { xs: 'none', sm: 'grid' } }} + sx={{ display: { xs: 'none', sm: 'none', md: 'grid' } }} className="team-member-map-summmary-latest-activity" > From bf023ebb8f98ea2fdebbac92c3229567691eb011 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 15 Jan 2025 09:16:29 -0600 Subject: [PATCH 06/10] Updated snapshot. --- .../src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap b/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap index 2757118875..0e5e20ec58 100644 --- a/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap +++ b/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap @@ -119,6 +119,7 @@ exports[`renders correctly 1`] = ` >
Date: Wed, 15 Jan 2025 13:06:27 -0600 Subject: [PATCH 07/10] Removed the need for an accordion and display the latest checkin for the quarter (no grace period) and, for members without activity during the quarter, a link to the last known checkin. --- .../checkin-report/LinkSection.css | 6 - .../checkin-report/LinkSection.jsx | 35 ------ .../checkin-report/TeamMemberMap.css | 5 +- .../checkin-report/TeamMemberMap.jsx | 111 ++++++++---------- .../checkin-report/checkin-utils.js | 45 ++----- 5 files changed, 59 insertions(+), 143 deletions(-) delete mode 100644 web-ui/src/components/reports-section/checkin-report/LinkSection.css delete mode 100644 web-ui/src/components/reports-section/checkin-report/LinkSection.jsx 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 de8cfa7fd2..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,12 +1,11 @@ -.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; 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 31b8f4f0c1..eedbb8537f 100644 --- a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx +++ b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx @@ -6,8 +6,11 @@ import { Chip, Typography, AccordionDetails, - Box + Box, + Card, + CardContent, } from '@mui/material'; +import { Link } from 'react-router-dom'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { getAvatarURL } from '../../../api/api.js'; import { AppContext } from '../../../context/AppContext.jsx'; @@ -15,12 +18,9 @@ import { selectCheckinsForMember } from '../../../context/selectors.js'; import { isPastCheckin, getCheckinDate, - getQuarterBeginEndWithGrace, - getCheckinDateForPeriod, - getLastCheckinDate, statusForPeriodByMemberScheduling } from './checkin-utils.js'; -import LinkSection from './LinkSection.jsx'; +import { getQuarterBeginEnd } from '../../../helpers'; import './TeamMemberMap.css'; const SortOption = { @@ -33,13 +33,14 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { const [sortBy, setSortBy] = useState(SortOption.BY_MEMBER); const epoch = new Date(0); - const pdls = members.reduce((map, member) => { - if (member.pdlId && !map[member.pdlId]) { - map[member.pdlId] = members.find((m) => m.id === member.pdlId); - } + 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( @@ -50,34 +51,32 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { }; const sortByPDLName = (a, b) => - sortByName(pdls[a.reportDatePDLId], pdls[b.reportDatePDLId]); + sortByName(memberMap[a.reportDatePDLId], memberMap[b.reportDatePDLId]); // We're going to cache the checkins into the member data structures so that // we can properly sort by PDL when the PDL, in the past, is different than // the current PDL. - const { startOfQuarter, endOfQuarter } = getQuarterBeginEndWithGrace(reportDate); + const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(reportDate); members.map(member => { - const checkins = selectCheckinsForMember( + member.checkins = selectCheckinsForMember( state, member.id, ).filter(checkin => closed || !checkin.completed) - .filter(checkin => planned || isPastCheckin(checkin)); - member.checkins = checkins; + .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 instead // of the current PDL. They may be the same, but again they may not. + member.checkin = null; member.reportDatePDLId = member.pdlId; - if (checkins.length > 0) { - checkins.sort((a, b) => getCheckinDate(b) - getCheckinDate(a)); - - for(let checkin of checkins) { - const checkinDate = getCheckinDate(checkin); - if (checkinDate >= startOfQuarter && checkinDate <= endOfQuarter) { - member.reportDatePDLId = checkin.pdlId; - break; - } + 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; } } }); @@ -105,18 +104,10 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { { members.map(member => { - const pdl = pdls[member.reportDatePDLId]; - const checkins = member.checkins; - + const pdl = memberMap[member.reportDatePDLId]; return ( - - } - className="team-member-map-accordion-summary" - > + + { - {getCheckinDateForPeriod( - checkins, - reportDate - ).getFullYear() === epoch.getFullYear() ? ( + {member.checkin == null ? ( No activity yet{' '} @@ -164,20 +152,17 @@ const TeamMemberMap = ({ members, 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() ? ( 📆 @@ -187,18 +172,27 @@ const TeamMemberMap = ({ members, 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 => ( - - ))} - - + + ); }) } 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 032b4dd398..8c07f83ae8 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); }; @@ -28,35 +28,17 @@ export const getLastCheckinDate = checkins => { }, new Date(0)); }; -/** - * Get the dates the reporting period including a grace period. - * @param {Date} reportDate - The date of the report. - * @returns {{ startOfQuarter: Date, endOfQuarter: Date }} The start and end dates of the quarter pluas a grace period. - */ -export const getQuarterBeginEndWithGrace = (reportDate) => { - const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(reportDate); - const endOfQuarterWithGrace = new Date(endOfQuarter); - endOfQuarterWithGrace.setDate(endOfQuarter.getDate() + 30); - return { - startOfQuarter: startOfQuarter, - endOfQuarter: endOfQuarterWithGrace - }; -}; - /** * 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 } = getQuarterBeginEndWithGrace(reportDate); + const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(reportDate); const scheduled = checkins.filter(checkin => { const checkinDate = getCheckinDate(checkin); - return ( - checkinDate >= startOfQuarter && checkinDate <= endOfQuarter // Include grace period - ); + return (checkinDate >= startOfQuarter && checkinDate <= endOfQuarter); }); return getLastCheckinDate(scheduled); }; @@ -64,27 +46,20 @@ export const getCheckinDateForPeriod = (checkins, reportDate) => { /** * 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'; }; From 6fd71c8646191b6536c0bdd207b53f75b3d159a2 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 15 Jan 2025 13:27:12 -0600 Subject: [PATCH 08/10] Update the test to reflect the changes. --- .../checkin-report/checkin-utils.test.js | 43 +++++-------------- 1 file changed, 10 insertions(+), 33 deletions(-) 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..2fe4052406 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,11 @@ 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', () => { +console.log(mockReportDate); + 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'); }); }); From a6516e2c9bf7bd5cfc9eede5652f8ae45764d8ba Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 15 Jan 2025 13:55:50 -0600 Subject: [PATCH 09/10] Fixed the column alignment. --- .../checkin-report/TeamMemberMap.jsx | 36 +++++---- .../CheckinsReportPage.test.jsx.snap | 77 +++++++++++-------- 2 files changed, 64 insertions(+), 49 deletions(-) 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 eedbb8537f..d09eb6c165 100644 --- a/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx +++ b/web-ui/src/components/reports-section/checkin-report/TeamMemberMap.jsx @@ -85,21 +85,27 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { return ( - - { setSortBy(SortOption.BY_MEMBER); }} - style={{ cursor: 'pointer' }}> - Member - - { setSortBy(SortOption.BY_PDL); }} - style={{ cursor: 'pointer' }}> - PDL - - - Check-In Date - - - Status + {/* 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 + +
{ diff --git a/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap b/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap index 0e5e20ec58..aead1181ef 100644 --- a/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap +++ b/web-ui/src/pages/__snapshots__/CheckinsReportPage.test.jsx.snap @@ -115,45 +115,54 @@ exports[`renders correctly 1`] = ` class="team-member-map MuiBox-root css-0" >
-
- Member -
-
-
-
- PDL -
-
-
-
- Check-In Date -
-
-
-
+ Member +
+
+
- Status -
+
+
+
+ PDL +
+
+
+
+ Check-In Date +
+
+
+
+ Status +
+
+
From b4d3b38f2d3fdceb5b1f99831fae2aebcf31c6d1 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 15 Jan 2025 14:08:19 -0600 Subject: [PATCH 10/10] Cleanup from self-review. --- .../reports-section/checkin-report/TeamMemberMap.jsx | 12 ++++-------- .../reports-section/checkin-report/checkin-utils.js | 1 - .../checkin-report/checkin-utils.test.js | 1 - 3 files changed, 4 insertions(+), 10 deletions(-) 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 d09eb6c165..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,17 +1,13 @@ import React, { useContext, useState } from 'react'; import { - Accordion, - AccordionSummary, Avatar, Chip, Typography, - AccordionDetails, Box, Card, CardContent, } from '@mui/material'; import { Link } from 'react-router-dom'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { getAvatarURL } from '../../../api/api.js'; import { AppContext } from '../../../context/AppContext.jsx'; import { selectCheckinsForMember } from '../../../context/selectors.js'; @@ -54,8 +50,7 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { sortByName(memberMap[a.reportDatePDLId], memberMap[b.reportDatePDLId]); // We're going to cache the checkins into the member data structures so that - // we can properly sort by PDL when the PDL, in the past, is different than - // the current PDL. + // we only have to do this once per member. const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(reportDate); members.map(member => { member.checkins = selectCheckinsForMember( @@ -67,8 +62,9 @@ const TeamMemberMap = ({ members, closed, planned, reportDate }) => { // 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 instead - // of the current PDL. They may be the same, but again they may not. + // 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) { 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 8c07f83ae8..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 @@ -45,7 +45,6 @@ export const getCheckinDateForPeriod = (checkins, reportDate) => { /** * Determine check-in status for a member during the reporting period. - * Include the grace period for the end of the quarter. * @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. 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 2fe4052406..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 @@ -173,7 +173,6 @@ describe('statusForPeriodByMemberScheduling', () => { }); test('returns "Scheduled" when the check-in is within the reporting period are not completed', () => { -console.log(mockReportDate); const checkin = { checkInDate: [2024, 4, 10, 10, 0], completed: false }; // April 1, 2024 (within reporting period, not completed) const result = statusForPeriodByMemberScheduling(checkin, mockReportDate);