From b6d5e161f8961414069130eb3c0a1e9bc4a950e2 Mon Sep 17 00:00:00 2001 From: Josh Habdas Date: Mon, 6 May 2024 15:38:08 -0400 Subject: [PATCH 01/10] chore: introduce app-wide type definitions --- web-ui/src/app.d.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 web-ui/src/app.d.js diff --git a/web-ui/src/app.d.js b/web-ui/src/app.d.js new file mode 100644 index 0000000000..661066e54e --- /dev/null +++ b/web-ui/src/app.d.js @@ -0,0 +1,28 @@ +// Type definitions specified here will be availalbe app-wide + +/** + * @typedef {Object} Member + * @property {?string} bioText + * @property {?Array} birthday + * @property {string} empolyeeId + * @property {?string} excluded + * @property {string} firstName + * @property {!string} id + * @property {string} lastName + * @property {string} location + * @property {?string} middleName + * @property {string} name + * @property {?string} pdlId + * @property {Array} startDate + * @property {?string} suffix + * @property {?string} supervisorid + * @property {?Array} terminationDate + * @property {string} title + * @property {?string} voluntary + * @property {string} workEmail + */ + +/** + * @typedef {Member} PDL + * @property {string} pdlId + */ From 0bb86697c649eec35c9c63d87e70774f8255c9d4 Mon Sep 17 00:00:00 2001 From: Josh Habdas Date: Thu, 2 May 2024 16:46:07 -0400 Subject: [PATCH 02/10] chore: create enhanced checkins comparison page --- .../reports-section/CheckinReport.css | 27 ++++++ .../reports-section/CheckinReport.jsx | 81 +++++++++++++--- web-ui/src/components/routes/Routes.jsx | 5 + .../src/pages/CheckinsReportEnhancedPage.css | 33 +++++++ .../src/pages/CheckinsReportEnhancedPage.jsx | 97 +++++++++++++++++++ 5 files changed, 230 insertions(+), 13 deletions(-) create mode 100644 web-ui/src/pages/CheckinsReportEnhancedPage.css create mode 100644 web-ui/src/pages/CheckinsReportEnhancedPage.jsx diff --git a/web-ui/src/components/reports-section/CheckinReport.css b/web-ui/src/components/reports-section/CheckinReport.css index 8067ca872f..cf564eb866 100644 --- a/web-ui/src/components/reports-section/CheckinReport.css +++ b/web-ui/src/components/reports-section/CheckinReport.css @@ -1,3 +1,30 @@ +.checkin-report-card { + display: flex; + flex-direction: column; + margin: 1rem 0; + width: 100vw; + + .checkin-report-card-title-container { + display: flex; + flex-direction: column; + } + + .checkin-report-card-name { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 20rem; + } +} + +.checkin-report-no-data { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 10rem; +} + #accordion-checkin-date { flex-direction: column; } diff --git a/web-ui/src/components/reports-section/CheckinReport.jsx b/web-ui/src/components/reports-section/CheckinReport.jsx index fcbfbd3e06..ea51a2d48d 100644 --- a/web-ui/src/components/reports-section/CheckinReport.jsx +++ b/web-ui/src/components/reports-section/CheckinReport.jsx @@ -1,10 +1,13 @@ -import React, { useContext } from 'react'; +import React, { useContext, useState } from 'react'; import { Link } from 'react-router-dom'; +import PropTypes from 'prop-types'; import { getAvatarURL } from '../../api/api.js'; import { AppContext } from '../../context/AppContext'; import { selectFilteredCheckinsForTeamMemberAndPDL } from '../../context/selectors'; +import ExpandMore from '../expand-more/ExpandMore'; + import { Accordion, AccordionDetails, @@ -12,18 +15,36 @@ import { Avatar, Box, Card, - CardHeader, CardContent, + CardHeader, Chip, + Collapse, Container, + Divider, Typography } from '@mui/material'; import './CheckinReport.css'; +const propTypes = { + closed: PropTypes.bool, + pdl: PropTypes.shape({ + name: PropTypes.string, + id: PropTypes.string, + members: PropTypes.array, + workEmail: PropTypes.string, + title: PropTypes.string + }), + planned: PropTypes.bool +}; + const CheckinsReport = ({ closed, pdl, planned }) => { const { state } = useContext(AppContext); - const { name, id, members, workEmail } = pdl; + const [expanded, setExpanded] = useState(true); + + const { name, id, members, workEmail, title } = pdl; + + const handleExpandClick = () => setExpanded(!expanded); const getCheckinDate = checkin => { if (!checkin || !checkin.checkInDate) return; @@ -82,6 +103,7 @@ const CheckinsReport = ({ closed, pdl, planned }) => { return ( } aria-controls="panel1a-content" id="accordion-summary" > @@ -103,28 +125,61 @@ const CheckinsReport = ({ closed, pdl, planned }) => { ); }); - } else return null; + } else + return ( +
+ + No assigned check-ins available for display during this period. + +
+ ); }; return ( - + - {name} - +
+ + {name} + + + {title} + +
} disableTypography avatar={} + action={ + + } /> - - - - - + + + + + + + +
); }; + +CheckinsReport.propTypes = propTypes; export default CheckinsReport; diff --git a/web-ui/src/components/routes/Routes.jsx b/web-ui/src/components/routes/Routes.jsx index a8c5c08b9d..2f300ff227 100644 --- a/web-ui/src/components/routes/Routes.jsx +++ b/web-ui/src/components/routes/Routes.jsx @@ -7,6 +7,7 @@ import AnniversaryReportPage from '../../pages/AnniversaryReportPage'; import BirthdayReportPage from '../../pages/BirthdayReportPage'; import CheckinsPage from '../../pages/CheckinsPage'; import CheckinsReportPage from '../../pages/CheckinsReportPage'; +import CheckinsReportEnhancedPage from '../../pages/CheckinsReportEnhancedPage'; import EditSkillsPage from '../../pages/EditSkillsPage'; import EditPermissionsPage from '../../pages/PermissionsPage'; import GroupIcon from '@mui/icons-material/Group'; @@ -117,6 +118,10 @@ export default function Routes() {
+ +
+ +
diff --git a/web-ui/src/pages/CheckinsReportEnhancedPage.css b/web-ui/src/pages/CheckinsReportEnhancedPage.css new file mode 100644 index 0000000000..22e118d9d7 --- /dev/null +++ b/web-ui/src/pages/CheckinsReportEnhancedPage.css @@ -0,0 +1,33 @@ +.checkins-report-page { + display: flex; + flex-direction: column; + margin: 1rem; +} + +.checkbox-row { + display: flex; + align-items: center; + margin-left: 0.5rem; + margin-top: 0.5rem; +} + +.checkbox-row label { + margin-left: 0.5rem; +} + +.filter-pdls-and-members { + display: flex; + justify-content: space-between; + align-items: center; + margin-left: 12px; + margin-right: 12px; + margin-bottom: 1rem; +} + +.filter-pdls-and-members > * { + width: 15%; +} + +.MuiFormControl-root { + margin-left: 1rem; +} diff --git a/web-ui/src/pages/CheckinsReportEnhancedPage.jsx b/web-ui/src/pages/CheckinsReportEnhancedPage.jsx new file mode 100644 index 0000000000..03a43ab231 --- /dev/null +++ b/web-ui/src/pages/CheckinsReportEnhancedPage.jsx @@ -0,0 +1,97 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import { AppContext } from '../context/AppContext'; +import { + selectCheckinPDLS, + selectTeamMembersWithCheckinPDL, + selectMappedPdls +} from '../context/selectors'; + +import CheckinReport from '../components/reports-section/CheckinReport'; +import MemberSelector from '../components/member_selector/MemberSelector'; +import { FilterType } from '../components/member_selector/member_selector_dialog/MemberSelectorDialog'; + +import { Typography } from '@mui/material'; + +import './CheckinsReportEnhancedPage.css'; + +/** + * @typedef {Object} PDL + * @property {string} id + * @property {string} name + * @property {string} title + */ + +/** + * Sort PDLS by the last name extracted from the full name. + * @param {PDL} a - First PDL object + * @param {PDL} b - Second PDL object + * @returns {number} - Comparison result for sorting + */ +function sortByLastName(a, b) { + if (!a.name || !b.name) return; + const lastNameA = a.name.split(' ').slice(-1)[0]; + const lastNameB = b.name.split(' ').slice(-1)[0]; + return lastNameA.localeCompare(lastNameB); +} + +const CheckinsReportEnhancedPage = () => { + const { state } = useContext(AppContext); + const [mappedPDLs, setMappedPDLs] = useState(/** @type {PDL[]} */ ([])); + const [selectedPdls, setSelectedPdls] = useState(/** @type {PDL[]} */ ([])); + const [planned, setPlanned] = useState(false); + const [closed, setClosed] = useState(false); + + /** @type {PDL[]} */ + const pdls = selectCheckinPDLS(state, closed, planned).sort(sortByLastName); + + // Set the selected PDLs to the mapped PDLs + useEffect(() => { + const mapped = selectMappedPdls(state); + setSelectedPdls(mapped); + }, [state]); + + // Set the mapped PDLs to the PDLs with members + useEffect(() => { + if (!pdls) return; + pdls.forEach( + pdl => (pdl.members = selectTeamMembersWithCheckinPDL(state, pdl.id)) + ); + const pdlsWithMembers = pdls.filter(pdl => pdl.members.length > 0); + setMappedPDLs(pdlsWithMembers); + }, [pdls, state]); + + // console.log({ selectedPdls }); + + return ( +
+ + Assigned check-ins by selected PDL + {selectedPdls.length > 0 ? ( + selectedPdls.sort(sortByLastName).map(pdl => { + return ( + + ); + }) + ) : ( +
+

No data to display

+
+ )} +
+ ); +}; + +export default CheckinsReportEnhancedPage; From 7fa6096420f592bbb842ac5b9827f80ca286b3e2 Mon Sep 17 00:00:00 2001 From: Josh Habdas Date: Fri, 3 May 2024 10:09:23 -0400 Subject: [PATCH 03/10] feat: enhance check-ins reporting, unfiltered --- web-ui/src/app.d.js | 8 +- .../reports-section/CheckinReport.css | 98 +++++---- .../reports-section/CheckinReport.jsx | 203 ++++++++++++++++-- web-ui/src/helpers/datetime.js | 11 + web-ui/src/helpers/datetime.test.js | 70 ++++++ .../src/pages/CheckinsReportEnhancedPage.css | 44 ++-- .../src/pages/CheckinsReportEnhancedPage.jsx | 137 +++++++++--- web-ui/src/pages/CheckinsReportPage.jsx | 38 ++-- 8 files changed, 471 insertions(+), 138 deletions(-) create mode 100644 web-ui/src/helpers/datetime.js create mode 100644 web-ui/src/helpers/datetime.test.js diff --git a/web-ui/src/app.d.js b/web-ui/src/app.d.js index 661066e54e..4f1c64f760 100644 --- a/web-ui/src/app.d.js +++ b/web-ui/src/app.d.js @@ -1,10 +1,10 @@ -// Type definitions specified here will be availalbe app-wide +// Type definitions specified here will be available app-wide /** - * @typedef {Object} Member + * @typedef {Object} MemberProfile * @property {?string} bioText * @property {?Array} birthday - * @property {string} empolyeeId + * @property {string} employeeId * @property {?string} excluded * @property {string} firstName * @property {!string} id @@ -23,6 +23,6 @@ */ /** - * @typedef {Member} PDL + * @typedef {MemberProfile} PDLProfile * @property {string} pdlId */ diff --git a/web-ui/src/components/reports-section/CheckinReport.css b/web-ui/src/components/reports-section/CheckinReport.css index cf564eb866..f4020fbbce 100644 --- a/web-ui/src/components/reports-section/CheckinReport.css +++ b/web-ui/src/components/reports-section/CheckinReport.css @@ -1,8 +1,7 @@ .checkin-report-card { display: flex; flex-direction: column; - margin: 1rem 0; - width: 100vw; + width: 100%; .checkin-report-card-title-container { display: flex; @@ -15,55 +14,70 @@ text-overflow: ellipsis; max-width: 20rem; } -} -.checkin-report-no-data { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: 10rem; -} + .checkin-report-card-actions { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + } -#accordion-checkin-date { - flex-direction: column; -} + .member-sub-card { + background-color: #fafafa; + margin-top: 1rem; + padding: 0.5rem; -.checkin-report-link { - display: flex; - align-items: center; - margin-bottom: 0.5rem; -} + .member-sub-card-accordion-details { + display: flex; + flex-direction: column; + margin: 0.5rem 0; + } -.checkin-report-link .MuiChip-colorPrimary { - height: 1.5rem; - margin-left: 0.5rem; -} + .member-sub-card-summmary-content { + display: flex; + flex: 1; + align-items: center; + margin-right: 1rem; -.checkin-report-link .MuiChip-colorSecondary { - height: 1.5rem; - margin-left: 0.5rem; -} + &:first-child { + margin-top: 0; + } -.MuiAccordionSummary-content { - align-items: center; -} + p { + flex: 1; + margin: 0.5rem; + } -.MuiAccordionSummary-content > p { - margin-left: 1rem; -} + .last-connected { + color: #a0a0a0; + /* background-color: #f44336; */ + padding: 0.5rem; + /* border-radius: 0.5rem; */ + margin-right: 1rem; + } + } -#member-sub-card { - background-color: var(--checkins-palette-background-default); - margin-top: 1rem; -} + .checkin-report-link { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.5rem; + + :not(:first-child) { + margin-top: 0.5rem; + } -#pdl-card { - margin: 1rem; - width: 100vw; + p { + flex: 1; + } + } + } } -#pdl-large { - height: 4rem; - width: 4rem; +.checkin-report-no-data { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 10rem; } diff --git a/web-ui/src/components/reports-section/CheckinReport.jsx b/web-ui/src/components/reports-section/CheckinReport.jsx index ea51a2d48d..3e9ede5c45 100644 --- a/web-ui/src/components/reports-section/CheckinReport.jsx +++ b/web-ui/src/components/reports-section/CheckinReport.jsx @@ -6,6 +6,7 @@ import { getAvatarURL } from '../../api/api.js'; import { AppContext } from '../../context/AppContext'; import { selectFilteredCheckinsForTeamMemberAndPDL } from '../../context/selectors'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMore from '../expand-more/ExpandMore'; import { @@ -21,9 +22,64 @@ import { Collapse, Container, Divider, + Stepper, + Step, + StepLabel, Typography } from '@mui/material'; +/** + * @typedef {Object} Checkin + * @property {string} id - The ID of the check-in. + * @property {boolean} completed - Indicates whether the check-in is completed. + * @property {Array} checkinDate - The date of the check-in. + * @property {string} pdlId - The ID of the PDL. + * @property {string} teamMemberId - The ID of the team member. + */ + +/** + * @typedef {("Done" | "In Progress" | "Not Started")} CheckinStatus + * @typedef {("Not Yet Scheduled" | "Scheduled" | "Completed")} SchedulingStatus + */ + +/** @type {CheckinStatus} */ +const steps = ['Not Started', 'In Progress', 'Done']; + +/** @type {SchedulingStatus} */ +const schedulingSteps = ['Not Yet Scheduled', 'Scheduled', 'Completed']; + +function HorizontalLinearStepper({ step = 0 }) { + const [activeStep, setActiveStep] = React.useState(step); + const [skipped, setSkipped] = React.useState(new Set()); + + const isStepOptional = step => step === -1; + const isStepSkipped = step => skipped.has(step); + + return ( + + + {steps.map((label, index) => { + const stepProps = {}; + const labelProps = {}; + if (isStepOptional(index)) { + labelProps.optional = ( + Optional + ); + } + if (isStepSkipped(index)) { + stepProps.completed = false; + } + return ( + + {label} + + ); + })} + + + ); +} + import './CheckinReport.css'; const propTypes = { @@ -41,6 +97,9 @@ const propTypes = { const CheckinsReport = ({ closed, pdl, planned }) => { const { state } = useContext(AppContext); const [expanded, setExpanded] = useState(true); + const [statusForPeriod, setStatusForPeriod] = useState( + /** @type CheckinStatus */ ('Not Started') + ); const { name, id, members, workEmail, title } = pdl; @@ -52,6 +111,78 @@ const CheckinsReport = ({ closed, pdl, planned }) => { return new Date(year, month - 1, day, hour, minute, 0); }; + /** + * Determine the status of the check-ins for a member. + * @param {Object} params - The parameters object. + * @param {Checkin[]} params.checkins - Checkins for a member. + * @returns {CheckinStatus} The status of check-ins. + */ + const statusForPeriodByMember = ({ checkins = [] }) => { + const now = new Date(); + if (checkins.length === 0) return 'Not Started'; + const completed = checkins.filter(checkin => checkin.completed); + if (completed.length === checkins.length) return 'Done'; + const inProgress = checkins.filter( + checkin => !checkin.completed && getCheckinDate(checkin) < now + ); + if (inProgress.length > 0) return 'Open'; + return 'Not Started'; + }; + + /** + * Get the date of the last check-in. + * @param {Checkin[]} checkins - Check-ins for a member. + * @returns {Date} The date of the last check-in. + */ + const getLastCheckinDate = checkins => { + if (checkins.length === 0) return; + return checkins.reduce((acc, checkin) => { + const checkinDate = getCheckinDate(checkin); + return checkinDate > acc ? checkinDate : acc; + }, new Date(0)); + }; + + /** + * Determine the status of the check-ins for a PDL. + * @param {Object} params - The parameters object. + * @param {Array} params.members - Members of the PDL. + * @returns {CheckinStatus} The status of check-ins. + */ + const statusForPeriodByPDL = ({ members = [] }) => { + // const debug = ['Done', 'In Progress', 'Not Started'][0]; + const now = new Date(); + if (members.length === 0) return 'Not Started'; + const completed = members.filter(member => { + const checkins = selectFilteredCheckinsForTeamMemberAndPDL( + state, + member.id, + id, + closed, + planned + ); + return ( + checkins.filter(checkin => checkin.completed).length === checkins.length + ); + }); + if (completed.length === members.length) return 'Done'; + const inProgress = members.filter(member => { + const checkins = selectFilteredCheckinsForTeamMemberAndPDL( + state, + member.id, + id, + closed, + planned + ); + return ( + checkins.filter( + checkin => !checkin.completed && getCheckinDate(checkin) < now + ).length > 0 + ); + }); + if (inProgress.length > 0) return 'In Progress'; + return 'Not Started'; + }; + const LinkSection = ({ checkin, member }) => { const now = new Date(); let checkinDate = new Date(getCheckinDate(checkin)); @@ -78,6 +209,7 @@ const CheckinsReport = ({ closed, pdl, planned }) => { ); }; + const TeamMemberMap = () => { const filtered = members && @@ -101,19 +233,50 @@ const CheckinsReport = ({ closed, pdl, planned }) => { planned ); return ( - + } + expandIcon={} aria-controls="panel1a-content" id="accordion-summary" + className="checkin-report-accordion-summary" > - - {member.name} +
+ + {member.name} + + Last connected:{' '} + {getLastCheckinDate(checkins).toLocaleDateString( + navigator.language, + { + year: 'numeric', + month: '2-digit', + day: 'numeric' + } + )} + + +
- + {checkins.map(checkin => ( { disableTypography avatar={} action={ - +
+ + +
} /> + diff --git a/web-ui/src/helpers/datetime.js b/web-ui/src/helpers/datetime.js new file mode 100644 index 0000000000..ce56d2abe9 --- /dev/null +++ b/web-ui/src/helpers/datetime.js @@ -0,0 +1,11 @@ +import { startOfQuarter, endOfQuarter } from 'date-fns'; + +/** + * Returns the start and end dates of the quarter that the given date falls in. + * @param {Date} inputDate The date to get the quarter duration for. + * @returns {{ startOfQuarter: Date, endOfQuarter: Date }} The start and end dates of the quarter. + */ +export const getQuarterDuration = inputDate => ({ + startOfQuarter: startOfQuarter(inputDate), + endOfQuarter: endOfQuarter(inputDate) +}); diff --git a/web-ui/src/helpers/datetime.test.js b/web-ui/src/helpers/datetime.test.js new file mode 100644 index 0000000000..68df9e5d81 --- /dev/null +++ b/web-ui/src/helpers/datetime.test.js @@ -0,0 +1,70 @@ +import { format } from 'date-fns'; +import { getQuarterDuration } from './datetime'; + +describe('getQuarterDuration', () => { + it('returns the start and end dates of the current quarter', () => { + const inputDate = new Date('2024-04-15'); + const expectedStart = '2024-04-01'; + const expectedEnd = '2024-06-30'; + + const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + + expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); + expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); + }); + + it('returns the start and end dates of the first quarter', () => { + const inputDate = new Date('2024-01-15'); + const expectedStart = '2024-01-01'; + const expectedEnd = '2024-03-31'; + + const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + + expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); + expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); + }); + + it('returns the start and end dates of the fourth quarter', () => { + const inputDate = new Date('2023-10-20'); + const expectedStart = '2023-10-01'; + const expectedEnd = '2023-12-31'; + + const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + + expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); + expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); + }); + + it('returns the start and end dates of the first quarter in a leap year', () => { + const inputDate = new Date('2020-02-15'); + const expectedStart = '2020-01-01'; + const expectedEnd = '2020-03-31'; + + const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + + expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); + expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); + }); + + it('returns the start and end dates of the second quarter in a non-leap year', () => { + const inputDate = new Date('2021-05-20'); // Non-leap year (2021) + const expectedStart = '2021-04-01'; + const expectedEnd = '2021-06-30'; + + const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + + expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); + expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); + }); + + it('returns the start and end dates of the first quarter in a different time zone', () => { + const inputDate = new Date('2024-01-15T12:00:00-05:00'); // Eastern Standard Time (UTC-5) + const expectedStart = '2024-01-01'; + const expectedEnd = '2024-03-31'; + + const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + + expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); + expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); + }); +}); diff --git a/web-ui/src/pages/CheckinsReportEnhancedPage.css b/web-ui/src/pages/CheckinsReportEnhancedPage.css index 22e118d9d7..2e9dc40fca 100644 --- a/web-ui/src/pages/CheckinsReportEnhancedPage.css +++ b/web-ui/src/pages/CheckinsReportEnhancedPage.css @@ -2,32 +2,26 @@ display: flex; flex-direction: column; margin: 1rem; -} -.checkbox-row { - display: flex; - align-items: center; - margin-left: 0.5rem; - margin-top: 0.5rem; -} - -.checkbox-row label { - margin-left: 0.5rem; -} + .checkins-report-page-reports { + display: flex; + flex-direction: column; + margin-top: 1rem; + } -.filter-pdls-and-members { - display: flex; - justify-content: space-between; - align-items: center; - margin-left: 12px; - margin-right: 12px; - margin-bottom: 1rem; -} - -.filter-pdls-and-members > * { - width: 15%; -} + .checkins-report-page-dates { + display: flex; + justify-content: flex-end; + text-align: right; + gap: 2rem; + margin: 0; + } -.MuiFormControl-root { - margin-left: 1rem; + .checkins-report-page-no-data { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 10rem; + } } diff --git a/web-ui/src/pages/CheckinsReportEnhancedPage.jsx b/web-ui/src/pages/CheckinsReportEnhancedPage.jsx index 03a43ab231..4af7e59d99 100644 --- a/web-ui/src/pages/CheckinsReportEnhancedPage.jsx +++ b/web-ui/src/pages/CheckinsReportEnhancedPage.jsx @@ -7,25 +7,27 @@ import { selectMappedPdls } from '../context/selectors'; +import { + Grid, + Typography, + IconButton, + Box, + ButtonGroup, + Tooltip +} from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + import CheckinReport from '../components/reports-section/CheckinReport'; import MemberSelector from '../components/member_selector/MemberSelector'; import { FilterType } from '../components/member_selector/member_selector_dialog/MemberSelectorDialog'; - -import { Typography } from '@mui/material'; +import { getQuarterDuration } from '../helpers/datetime'; import './CheckinsReportEnhancedPage.css'; - -/** - * @typedef {Object} PDL - * @property {string} id - * @property {string} name - * @property {string} title - */ - /** - * Sort PDLS by the last name extracted from the full name. - * @param {PDL} a - First PDL object - * @param {PDL} b - Second PDL object + * 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) { @@ -37,12 +39,35 @@ function sortByLastName(a, b) { const CheckinsReportEnhancedPage = () => { const { state } = useContext(AppContext); - const [mappedPDLs, setMappedPDLs] = useState(/** @type {PDL[]} */ ([])); - const [selectedPdls, setSelectedPdls] = useState(/** @type {PDL[]} */ ([])); + const [mappedPDLs, setMappedPDLs] = useState( + /** @type {PDLProfile[]} */ ([]) + ); + const [selectedPdls, setSelectedPdls] = useState( + /** @type {PDLProfile[]} */ ([]) + ); const [planned, setPlanned] = useState(false); const [closed, setClosed] = useState(false); - /** @type {PDL[]} */ + const [reportDate, setReportDate] = useState(new Date()); + const { startOfQuarter, endOfQuarter } = getQuarterDuration(reportDate); + + // Set the report date to today less one month on first load + useEffect(() => { + setReportDate(new Date(new Date().setMonth(new Date().getMonth() - 1))); + }, []); + + const handleQuarterClick = evt => { + /** @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))); + }; + + /** @type {PDLProfile[]} */ const pdls = selectCheckinPDLS(state, closed, planned).sort(sortByLastName); // Set the selected PDLs to the mapped PDLs @@ -61,8 +86,6 @@ const CheckinsReportEnhancedPage = () => { setMappedPDLs(pdlsWithMembers); }, [pdls, state]); - // console.log({ selectedPdls }); - return (
{ listHeight={180} exportable /> - Assigned check-ins by selected PDL - {selectedPdls.length > 0 ? ( - selectedPdls.sort(sortByLastName).map(pdl => { - return ( - - ); - }) - ) : ( -
-

No data to display

-
- )} + + + + + + + + + <> + = new Date()} + aria-label="Next quarter`" + onClick={handleQuarterClick} + size="large" + > + + + + + + + + + Start of Quarter + + + {startOfQuarter.toDateString()} + + + + + End of Quarter + + + {endOfQuarter.toDateString()} + + + + +
+ {selectedPdls.length > 0 ? ( + selectedPdls.sort(sortByLastName).map(pdl => { + return ( + + ); + }) + ) : ( +
+

No data to display

+
+ )} +
); }; diff --git a/web-ui/src/pages/CheckinsReportPage.jsx b/web-ui/src/pages/CheckinsReportPage.jsx index 4ec599df8a..77edde9df3 100644 --- a/web-ui/src/pages/CheckinsReportPage.jsx +++ b/web-ui/src/pages/CheckinsReportPage.jsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; -import { TextField } from '@mui/material'; +import { Box, TextField } from '@mui/material'; import Autocomplete from '@mui/material/Autocomplete'; import { AppContext } from '../context/AppContext'; @@ -127,23 +127,25 @@ const CheckinsReportPage = () => { - {selectedPdls.length - ? selectedPdls.map(pdl => ( - - )) - : filteredPdls.map(pdl => ( - - ))} + + {selectedPdls.length + ? selectedPdls.map(pdl => ( + + )) + : filteredPdls.map(pdl => ( + + ))} + ); }; From 442f03d5665d388b0bc14ecee1c33cc7af68afb9 Mon Sep 17 00:00:00 2001 From: Josh Habdas Date: Tue, 7 May 2024 13:38:50 -0400 Subject: [PATCH 04/10] chore: swap out old page for enhanced --- web-ui/src/components/routes/Routes.jsx | 5 - .../src/pages/CheckinsReportEnhancedPage.css | 27 -- .../src/pages/CheckinsReportEnhancedPage.jsx | 164 ------------ web-ui/src/pages/CheckinsReportPage.css | 44 +-- web-ui/src/pages/CheckinsReportPage.jsx | 253 +++++++++--------- 5 files changed, 154 insertions(+), 339 deletions(-) delete mode 100644 web-ui/src/pages/CheckinsReportEnhancedPage.css delete mode 100644 web-ui/src/pages/CheckinsReportEnhancedPage.jsx diff --git a/web-ui/src/components/routes/Routes.jsx b/web-ui/src/components/routes/Routes.jsx index 2f300ff227..a8c5c08b9d 100644 --- a/web-ui/src/components/routes/Routes.jsx +++ b/web-ui/src/components/routes/Routes.jsx @@ -7,7 +7,6 @@ import AnniversaryReportPage from '../../pages/AnniversaryReportPage'; import BirthdayReportPage from '../../pages/BirthdayReportPage'; import CheckinsPage from '../../pages/CheckinsPage'; import CheckinsReportPage from '../../pages/CheckinsReportPage'; -import CheckinsReportEnhancedPage from '../../pages/CheckinsReportEnhancedPage'; import EditSkillsPage from '../../pages/EditSkillsPage'; import EditPermissionsPage from '../../pages/PermissionsPage'; import GroupIcon from '@mui/icons-material/Group'; @@ -118,10 +117,6 @@ export default function Routes() {
- -
- -
diff --git a/web-ui/src/pages/CheckinsReportEnhancedPage.css b/web-ui/src/pages/CheckinsReportEnhancedPage.css deleted file mode 100644 index 2e9dc40fca..0000000000 --- a/web-ui/src/pages/CheckinsReportEnhancedPage.css +++ /dev/null @@ -1,27 +0,0 @@ -.checkins-report-page { - display: flex; - flex-direction: column; - margin: 1rem; - - .checkins-report-page-reports { - display: flex; - flex-direction: column; - margin-top: 1rem; - } - - .checkins-report-page-dates { - display: flex; - justify-content: flex-end; - text-align: right; - gap: 2rem; - margin: 0; - } - - .checkins-report-page-no-data { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: 10rem; - } -} diff --git a/web-ui/src/pages/CheckinsReportEnhancedPage.jsx b/web-ui/src/pages/CheckinsReportEnhancedPage.jsx deleted file mode 100644 index 4af7e59d99..0000000000 --- a/web-ui/src/pages/CheckinsReportEnhancedPage.jsx +++ /dev/null @@ -1,164 +0,0 @@ -import React, { useContext, useEffect, useState } from 'react'; - -import { AppContext } from '../context/AppContext'; -import { - selectCheckinPDLS, - selectTeamMembersWithCheckinPDL, - selectMappedPdls -} from '../context/selectors'; - -import { - Grid, - Typography, - IconButton, - Box, - ButtonGroup, - Tooltip -} from '@mui/material'; -import ArrowBackIcon from '@mui/icons-material/ArrowBack'; -import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; - -import CheckinReport from '../components/reports-section/CheckinReport'; -import MemberSelector from '../components/member_selector/MemberSelector'; -import { FilterType } from '../components/member_selector/member_selector_dialog/MemberSelectorDialog'; -import { getQuarterDuration } from '../helpers/datetime'; - -import './CheckinsReportEnhancedPage.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; - const lastNameA = a.name.split(' ').slice(-1)[0]; - const lastNameB = b.name.split(' ').slice(-1)[0]; - return lastNameA.localeCompare(lastNameB); -} - -const CheckinsReportEnhancedPage = () => { - const { state } = useContext(AppContext); - const [mappedPDLs, setMappedPDLs] = useState( - /** @type {PDLProfile[]} */ ([]) - ); - const [selectedPdls, setSelectedPdls] = useState( - /** @type {PDLProfile[]} */ ([]) - ); - const [planned, setPlanned] = useState(false); - const [closed, setClosed] = useState(false); - - const [reportDate, setReportDate] = useState(new Date()); - const { startOfQuarter, endOfQuarter } = getQuarterDuration(reportDate); - - // Set the report date to today less one month on first load - useEffect(() => { - setReportDate(new Date(new Date().setMonth(new Date().getMonth() - 1))); - }, []); - - const handleQuarterClick = evt => { - /** @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))); - }; - - /** @type {PDLProfile[]} */ - const pdls = selectCheckinPDLS(state, closed, planned).sort(sortByLastName); - - // Set the selected PDLs to the mapped PDLs - useEffect(() => { - const mapped = selectMappedPdls(state); - setSelectedPdls(mapped); - }, [state]); - - // Set the mapped PDLs to the PDLs with members - useEffect(() => { - if (!pdls) return; - pdls.forEach( - pdl => (pdl.members = selectTeamMembersWithCheckinPDL(state, pdl.id)) - ); - const pdlsWithMembers = pdls.filter(pdl => pdl.members.length > 0); - setMappedPDLs(pdlsWithMembers); - }, [pdls, state]); - - return ( -
- - - - - - - - - - <> - = new Date()} - aria-label="Next quarter`" - onClick={handleQuarterClick} - size="large" - > - - - - - - - - - Start of Quarter - - - {startOfQuarter.toDateString()} - - - - - End of Quarter - - - {endOfQuarter.toDateString()} - - - - -
- {selectedPdls.length > 0 ? ( - selectedPdls.sort(sortByLastName).map(pdl => { - return ( - - ); - }) - ) : ( -
-

No data to display

-
- )} -
-
- ); -}; - -export default CheckinsReportEnhancedPage; diff --git a/web-ui/src/pages/CheckinsReportPage.css b/web-ui/src/pages/CheckinsReportPage.css index 3dbab8769a..2e9dc40fca 100644 --- a/web-ui/src/pages/CheckinsReportPage.css +++ b/web-ui/src/pages/CheckinsReportPage.css @@ -1,27 +1,27 @@ -.checkbox-row { +.checkins-report-page { display: flex; - align-items: center; - margin-left: 0.5rem; - margin-top: 0.5rem; -} - -.checkbox-row label { - margin-left: 0.5rem; -} + flex-direction: column; + margin: 1rem; -.filter-pdls-and-members { - display: flex; - justify-content: space-between; - align-items: center; - margin-left: 12px; - margin-right: 12px; - margin-bottom: 1rem; -} + .checkins-report-page-reports { + display: flex; + flex-direction: column; + margin-top: 1rem; + } -.filter-pdls-and-members > * { - width: 15%; -} + .checkins-report-page-dates { + display: flex; + justify-content: flex-end; + text-align: right; + gap: 2rem; + margin: 0; + } -.MuiFormControl-root { - margin-left: 1rem; + .checkins-report-page-no-data { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 10rem; + } } diff --git a/web-ui/src/pages/CheckinsReportPage.jsx b/web-ui/src/pages/CheckinsReportPage.jsx index 77edde9df3..0def0b10f0 100644 --- a/web-ui/src/pages/CheckinsReportPage.jsx +++ b/web-ui/src/pages/CheckinsReportPage.jsx @@ -1,151 +1,162 @@ -import React, { useContext, useEffect, useRef, useState } from 'react'; - -import { Box, TextField } from '@mui/material'; -import Autocomplete from '@mui/material/Autocomplete'; +import React, { useContext, useEffect, useState } from 'react'; import { AppContext } from '../context/AppContext'; -import CheckinReport from '../components/reports-section/CheckinReport'; import { selectCheckinPDLS, - selectTeamMembersWithCheckinPDL + selectTeamMembersWithCheckinPDL, + selectMappedPdls } from '../context/selectors'; -import { isArrayPresent } from '../helpers/checks'; -import { useQueryParameters } from '../helpers/query-parameters'; + +import { + Grid, + Typography, + IconButton, + Box, + ButtonGroup, + Tooltip +} from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + +import CheckinReport from '../components/reports-section/CheckinReport'; +import MemberSelector from '../components/member_selector/MemberSelector'; +import { FilterType } from '../components/member_selector/member_selector_dialog/MemberSelectorDialog'; +import { getQuarterDuration } from '../helpers/datetime'; 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; + const lastNameA = a.name.split(' ').slice(-1)[0]; + const lastNameB = b.name.split(' ').slice(-1)[0]; + return lastNameA.localeCompare(lastNameB); +} const CheckinsReportPage = () => { const { state } = useContext(AppContext); - const [selectedPdls, setSelectedPdls] = useState([]); + const [mappedPDLs, setMappedPDLs] = useState( + /** @type {PDLProfile[]} */ ([]) + ); + const [selectedPdls, setSelectedPdls] = useState( + /** @type {PDLProfile[]} */ ([]) + ); const [planned, setPlanned] = useState(false); const [closed, setClosed] = useState(false); - const [searchText, setSearchText] = useState(''); - const pdls = selectCheckinPDLS(state, closed, planned).sort((a, b) => { - const aPieces = a.name.split(' ').slice(-1); - const bPieces = b.name.split(' ').slice(-1); - return aPieces.toString().localeCompare(bPieces); - }); - const [filteredPdls, setFilteredPdls] = useState(pdls); - - const processedQPs = useRef(false); - useQueryParameters( - [ - { - name: 'pdls', - default: [], - value: selectedPdls, - setter(ids) { - const newPdls = ids.map(id => pdls.find(pdl => pdl.id === id)); - setSelectedPdls(newPdls); - }, - toQP(newPdls) { - if (isArrayPresent(newPdls)) { - const ids = newPdls.map(pdl => pdl.id); - return ids.join(','); - } else { - return []; - } - } - } - ], - [pdls], - processedQPs - ); + const [reportDate, setReportDate] = useState(new Date()); + const { startOfQuarter, endOfQuarter } = getQuarterDuration(reportDate); + // Set the report date to today less one month on first load useEffect(() => { - if (!pdls) return; - pdls.map( - pdl => (pdl.members = selectTeamMembersWithCheckinPDL(state, pdl.id)) - ); - let newPdlList = pdls.filter(pdl => { - pdl.members = - pdl.members && - pdl.members.filter(member => - member?.name?.toLowerCase().includes(searchText.toLowerCase()) - ); - return pdl.members.length > 0; - }); + setReportDate(new Date(new Date().setMonth(new Date().getMonth() - 1))); + }, []); - setFilteredPdls(newPdlList); - }, [pdls, searchText, state]); - - const onPdlChange = (event, newValue) => { - let extantPdls = filteredPdls || []; - newValue.forEach(val => { - extantPdls = extantPdls.filter(pdl => pdl.id !== val.id); - }); - extantPdls = [...new Set(extantPdls)]; - newValue = [...new Set(newValue)]; - if (newValue.length > 0) { - setSelectedPdls(newValue); - setFilteredPdls([...newValue]); - } else { - setSelectedPdls([]); - setFilteredPdls(pdls); - } + const handleQuarterClick = evt => { + /** @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))); }; - const handleClosed = () => { - setClosed(!closed); - }; + /** @type {PDLProfile[]} */ + const pdls = selectCheckinPDLS(state, closed, planned).sort(sortByLastName); - const handlePlanned = () => { - setPlanned(!planned); - }; + // Set the selected PDLs to the mapped PDLs + useEffect(() => { + const mapped = selectMappedPdls(state); + setSelectedPdls(mapped); + }, [state]); + + // Set the mapped PDLs to the PDLs with members + useEffect(() => { + if (!pdls) return; + pdls.forEach( + pdl => (pdl.members = selectTeamMembersWithCheckinPDL(state, pdl.id)) + ); + const pdlsWithMembers = pdls.filter(pdl => pdl.members.length > 0); + setMappedPDLs(pdlsWithMembers); + }, [pdls, state]); return ( -
-
- option.name} - renderInput={params => ( - - )} - /> - { - setSearchText(e.target.value); - }} - /> -
-
- - - - -
- - {selectedPdls.length - ? selectedPdls.map(pdl => ( - - )) - : filteredPdls.map(pdl => ( +
+ + + + + + + + + + <> + = new Date()} + aria-label="Next quarter`" + onClick={handleQuarterClick} + size="large" + > + + + + + + + + + Start of Quarter + + + {startOfQuarter.toDateString()} + + + + + End of Quarter + + + {endOfQuarter.toDateString()} + + + + +
+ {selectedPdls.length > 0 ? ( + selectedPdls.sort(sortByLastName).map(pdl => { + return ( - ))} - + ); + }) + ) : ( +
+

No data to display

+
+ )} +
); }; From 41ff6f91afc44130d4ee37f818d4952bcffedadc Mon Sep 17 00:00:00 2001 From: Josh Habdas Date: Tue, 7 May 2024 13:44:06 -0400 Subject: [PATCH 05/10] chore: disable inactive reporting buttons --- web-ui/src/pages/CheckinsReportPage.jsx | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/web-ui/src/pages/CheckinsReportPage.jsx b/web-ui/src/pages/CheckinsReportPage.jsx index 0def0b10f0..8df459cb1f 100644 --- a/web-ui/src/pages/CheckinsReportPage.jsx +++ b/web-ui/src/pages/CheckinsReportPage.jsx @@ -39,9 +39,6 @@ function sortByLastName(a, b) { const CheckinsReportPage = () => { const { state } = useContext(AppContext); - const [mappedPDLs, setMappedPDLs] = useState( - /** @type {PDLProfile[]} */ ([]) - ); const [selectedPdls, setSelectedPdls] = useState( /** @type {PDLProfile[]} */ ([]) ); @@ -82,8 +79,7 @@ const CheckinsReportPage = () => { pdls.forEach( pdl => (pdl.members = selectTeamMembersWithCheckinPDL(state, pdl.id)) ); - const pdlsWithMembers = pdls.filter(pdl => pdl.members.length > 0); - setMappedPDLs(pdlsWithMembers); + pdls.filter(pdl => pdl.members.length > 0); }, [pdls, state]); return ( @@ -99,18 +95,21 @@ const CheckinsReportPage = () => { - - - + <> + + + + <> = new Date()} + disabled={true || reportDate >= new Date()} aria-label="Next quarter`" onClick={handleQuarterClick} size="large" From ab3a75f7f172079a2a646c74121e37e1f5981b2f Mon Sep 17 00:00:00 2001 From: Josh Habdas Date: Tue, 7 May 2024 14:14:02 -0400 Subject: [PATCH 06/10] refactor: adapt page for dark mode support --- .../reports-section/CheckinReport.css | 84 ++++++------------- .../reports-section/CheckinReport.jsx | 24 +++--- 2 files changed, 37 insertions(+), 71 deletions(-) diff --git a/web-ui/src/components/reports-section/CheckinReport.css b/web-ui/src/components/reports-section/CheckinReport.css index f4020fbbce..5840bb7d72 100644 --- a/web-ui/src/components/reports-section/CheckinReport.css +++ b/web-ui/src/components/reports-section/CheckinReport.css @@ -1,76 +1,42 @@ .checkin-report-card { display: flex; flex-direction: column; - width: 100%; + margin: 1rem 0; + width: 100vw; - .checkin-report-card-title-container { + .checkin-report-accordion-summary { display: flex; - flex-direction: column; - } - - .checkin-report-card-name { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - max-width: 20rem; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0.5rem 1rem; + margin-top: 0.5rem; + cursor: pointer; } - .checkin-report-card-actions { + .member-sub-card-summmary-content { display: flex; + flex-direction: row; justify-content: space-between; align-items: center; - gap: 1rem; + width: 100%; } - .member-sub-card { - background-color: #fafafa; - margin-top: 1rem; - padding: 0.5rem; - - .member-sub-card-accordion-details { - display: flex; - flex-direction: column; - margin: 0.5rem 0; - } - - .member-sub-card-summmary-content { - display: flex; - flex: 1; - align-items: center; - margin-right: 1rem; - - &:first-child { - margin-top: 0; - } - - p { - flex: 1; - margin: 0.5rem; - } - - .last-connected { - color: #a0a0a0; - /* background-color: #f44336; */ - padding: 0.5rem; - /* border-radius: 0.5rem; */ - margin-right: 1rem; - } - } - - .checkin-report-link { - display: flex; - flex-direction: row; - align-items: center; - gap: 0.5rem; + .checkin-report-stepper { + margin-bottom: 1rem; + } - :not(:first-child) { - margin-top: 0.5rem; - } + .checkin-report-link { + display: flex; + flex-direction: row; + justify-content: space-between; + } - p { - flex: 1; - } - } + .checkin-report-card-name { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 20rem; } } diff --git a/web-ui/src/components/reports-section/CheckinReport.jsx b/web-ui/src/components/reports-section/CheckinReport.jsx index 3e9ede5c45..ee24aa88c9 100644 --- a/web-ui/src/components/reports-section/CheckinReport.jsx +++ b/web-ui/src/components/reports-section/CheckinReport.jsx @@ -236,8 +236,6 @@ const CheckinsReport = ({ closed, pdl, planned }) => { } - aria-controls="panel1a-content" - id="accordion-summary" className="checkin-report-accordion-summary" >
@@ -321,7 +319,7 @@ const CheckinsReport = ({ closed, pdl, planned }) => {
} disableTypography - avatar={} + avatar={} action={
@@ -338,15 +336,17 @@ const CheckinsReport = ({ closed, pdl, planned }) => { - +
+ +
From 30913e68514c103a1033d89b2fa11aab3a98f5e0 Mon Sep 17 00:00:00 2001 From: Josh Habdas Date: Tue, 7 May 2024 14:25:02 -0400 Subject: [PATCH 07/10] chore: incorporate feedback --- web-ui/src/components/reports-section/CheckinReport.jsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/web-ui/src/components/reports-section/CheckinReport.jsx b/web-ui/src/components/reports-section/CheckinReport.jsx index ee24aa88c9..2dc1d206c6 100644 --- a/web-ui/src/components/reports-section/CheckinReport.jsx +++ b/web-ui/src/components/reports-section/CheckinReport.jsx @@ -28,6 +28,8 @@ import { Typography } from '@mui/material'; +import './CheckinReport.css'; + /** * @typedef {Object} Checkin * @property {string} id - The ID of the check-in. @@ -80,8 +82,6 @@ function HorizontalLinearStepper({ step = 0 }) { ); } -import './CheckinReport.css'; - const propTypes = { closed: PropTypes.bool, pdl: PropTypes.shape({ @@ -118,10 +118,10 @@ const CheckinsReport = ({ closed, pdl, planned }) => { * @returns {CheckinStatus} The status of check-ins. */ const statusForPeriodByMember = ({ checkins = [] }) => { - const now = new Date(); if (checkins.length === 0) return 'Not Started'; const completed = checkins.filter(checkin => checkin.completed); if (completed.length === checkins.length) return 'Done'; + const now = new Date(); const inProgress = checkins.filter( checkin => !checkin.completed && getCheckinDate(checkin) < now ); @@ -149,8 +149,6 @@ const CheckinsReport = ({ closed, pdl, planned }) => { * @returns {CheckinStatus} The status of check-ins. */ const statusForPeriodByPDL = ({ members = [] }) => { - // const debug = ['Done', 'In Progress', 'Not Started'][0]; - const now = new Date(); if (members.length === 0) return 'Not Started'; const completed = members.filter(member => { const checkins = selectFilteredCheckinsForTeamMemberAndPDL( @@ -173,6 +171,7 @@ const CheckinsReport = ({ closed, pdl, planned }) => { closed, planned ); + const now = new Date(); return ( checkins.filter( checkin => !checkin.completed && getCheckinDate(checkin) < now From 2f709f7319de474df8de0b887ede8b96cbc86c40 Mon Sep 17 00:00:00 2001 From: Josh Habdas Date: Tue, 7 May 2024 14:30:25 -0400 Subject: [PATCH 08/10] refactor: rename utility method --- web-ui/src/helpers/datetime.js | 2 +- web-ui/src/helpers/datetime.test.js | 16 ++++++++-------- web-ui/src/pages/CheckinsReportPage.jsx | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/web-ui/src/helpers/datetime.js b/web-ui/src/helpers/datetime.js index ce56d2abe9..621e137b02 100644 --- a/web-ui/src/helpers/datetime.js +++ b/web-ui/src/helpers/datetime.js @@ -5,7 +5,7 @@ import { startOfQuarter, endOfQuarter } from 'date-fns'; * @param {Date} inputDate The date to get the quarter duration for. * @returns {{ startOfQuarter: Date, endOfQuarter: Date }} The start and end dates of the quarter. */ -export const getQuarterDuration = inputDate => ({ +export const getQuarterBeginEnd = inputDate => ({ startOfQuarter: startOfQuarter(inputDate), endOfQuarter: endOfQuarter(inputDate) }); diff --git a/web-ui/src/helpers/datetime.test.js b/web-ui/src/helpers/datetime.test.js index 68df9e5d81..55267bcd28 100644 --- a/web-ui/src/helpers/datetime.test.js +++ b/web-ui/src/helpers/datetime.test.js @@ -1,13 +1,13 @@ import { format } from 'date-fns'; -import { getQuarterDuration } from './datetime'; +import { getQuarterBeginEnd } from './datetime'; -describe('getQuarterDuration', () => { +describe('getQuarterBeginEnd', () => { it('returns the start and end dates of the current quarter', () => { const inputDate = new Date('2024-04-15'); const expectedStart = '2024-04-01'; const expectedEnd = '2024-06-30'; - const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(inputDate); expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); @@ -18,7 +18,7 @@ describe('getQuarterDuration', () => { const expectedStart = '2024-01-01'; const expectedEnd = '2024-03-31'; - const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(inputDate); expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); @@ -29,7 +29,7 @@ describe('getQuarterDuration', () => { const expectedStart = '2023-10-01'; const expectedEnd = '2023-12-31'; - const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(inputDate); expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); @@ -40,7 +40,7 @@ describe('getQuarterDuration', () => { const expectedStart = '2020-01-01'; const expectedEnd = '2020-03-31'; - const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(inputDate); expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); @@ -51,7 +51,7 @@ describe('getQuarterDuration', () => { const expectedStart = '2021-04-01'; const expectedEnd = '2021-06-30'; - const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(inputDate); expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); @@ -62,7 +62,7 @@ describe('getQuarterDuration', () => { const expectedStart = '2024-01-01'; const expectedEnd = '2024-03-31'; - const { startOfQuarter, endOfQuarter } = getQuarterDuration(inputDate); + const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(inputDate); expect(format(startOfQuarter, 'yyyy-MM-dd')).toBe(expectedStart); expect(format(endOfQuarter, 'yyyy-MM-dd')).toBe(expectedEnd); diff --git a/web-ui/src/pages/CheckinsReportPage.jsx b/web-ui/src/pages/CheckinsReportPage.jsx index 8df459cb1f..a88a5f4da4 100644 --- a/web-ui/src/pages/CheckinsReportPage.jsx +++ b/web-ui/src/pages/CheckinsReportPage.jsx @@ -21,7 +21,7 @@ import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import CheckinReport from '../components/reports-section/CheckinReport'; import MemberSelector from '../components/member_selector/MemberSelector'; import { FilterType } from '../components/member_selector/member_selector_dialog/MemberSelectorDialog'; -import { getQuarterDuration } from '../helpers/datetime'; +import { getQuarterBeginEnd } from '../helpers/datetime'; import './CheckinsReportPage.css'; /** @@ -46,7 +46,7 @@ const CheckinsReportPage = () => { const [closed, setClosed] = useState(false); const [reportDate, setReportDate] = useState(new Date()); - const { startOfQuarter, endOfQuarter } = getQuarterDuration(reportDate); + const { startOfQuarter, endOfQuarter } = getQuarterBeginEnd(reportDate); // Set the report date to today less one month on first load useEffect(() => { From b72db0544127109332dd87984a4902dd84affe94 Mon Sep 17 00:00:00 2001 From: Josh Habdas Date: Tue, 7 May 2024 14:33:22 -0400 Subject: [PATCH 09/10] chore: feedback return comparator from sort function --- web-ui/src/pages/CheckinsReportPage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-ui/src/pages/CheckinsReportPage.jsx b/web-ui/src/pages/CheckinsReportPage.jsx index a88a5f4da4..57b6ce8dcf 100644 --- a/web-ui/src/pages/CheckinsReportPage.jsx +++ b/web-ui/src/pages/CheckinsReportPage.jsx @@ -31,7 +31,7 @@ import './CheckinsReportPage.css'; * @returns {number} - Comparison result for sorting */ function sortByLastName(a, b) { - if (!a.name || !b.name) return; + 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); From e97c4191f722127a2103171489cb28174748cdf3 Mon Sep 17 00:00:00 2001 From: Josh Habdas Date: Tue, 7 May 2024 16:02:30 -0400 Subject: [PATCH 10/10] chore: fix type definitions --- web-ui/src/components/reports-section/CheckinReport.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-ui/src/components/reports-section/CheckinReport.jsx b/web-ui/src/components/reports-section/CheckinReport.jsx index 2dc1d206c6..54766b175a 100644 --- a/web-ui/src/components/reports-section/CheckinReport.jsx +++ b/web-ui/src/components/reports-section/CheckinReport.jsx @@ -44,10 +44,10 @@ import './CheckinReport.css'; * @typedef {("Not Yet Scheduled" | "Scheduled" | "Completed")} SchedulingStatus */ -/** @type {CheckinStatus} */ +/** @type {CheckinStatus[]} */ const steps = ['Not Started', 'In Progress', 'Done']; -/** @type {SchedulingStatus} */ +/** @type {SchedulingStatus[]} */ const schedulingSteps = ['Not Yet Scheduled', 'Scheduled', 'Completed']; function HorizontalLinearStepper({ step = 0 }) {