From 760274b40741bdf5740b7005688c89aa70605f6d Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 2 Apr 2024 11:17:18 -0400 Subject: [PATCH 01/14] Update CSS --- frontend/src/components/GoalCards/GoalCard.scss | 2 +- frontend/src/components/Tooltip.scss | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/GoalCards/GoalCard.scss b/frontend/src/components/GoalCards/GoalCard.scss index e54d7041ae..5b9f30d336 100644 --- a/frontend/src/components/GoalCards/GoalCard.scss +++ b/frontend/src/components/GoalCards/GoalCard.scss @@ -86,6 +86,6 @@ $max-width-date-column: 50%; .ttahub-goal-card__entered-by-tooltip > .usa-tooltip__body, .ttahub-goal-card__entered-by-tooltip.smart-hub-tooltip:hover .usa-tooltip__body, -.smart-hub-tooltip.show-tooltip .usa-tooltip__body { +.ttahub-goal-card__entered-by-tooltip .smart-hub-tooltip.show-tooltip .usa-tooltip__body { white-space: nowrap; } diff --git a/frontend/src/components/Tooltip.scss b/frontend/src/components/Tooltip.scss index 4f286f2682..0940f21b22 100644 --- a/frontend/src/components/Tooltip.scss +++ b/frontend/src/components/Tooltip.scss @@ -48,8 +48,7 @@ display: inline-block; line-height: 1; position: absolute; - transform: translateX(-50%); - left: 50%; + transform: translateX(-25%); white-space: normal; &.usa-tooltip__body--top { From ddd925c0c1f970829383a0774fa7cfdda9f40670 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 2 Apr 2024 11:17:36 -0400 Subject: [PATCH 02/14] Use deep compare effect to regen data, since filters is an object --- frontend/src/widgets/withWidgetData.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/widgets/withWidgetData.js b/frontend/src/widgets/withWidgetData.js index 22e07dd695..ed6eadbf3b 100644 --- a/frontend/src/widgets/withWidgetData.js +++ b/frontend/src/widgets/withWidgetData.js @@ -1,6 +1,7 @@ /* eslint-disable react/jsx-props-no-spreading */ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; +import useDeepCompareEffect from 'use-deep-compare-effect'; import fetchWidget from '../fetchers/Widgets'; import { filtersToQueryString } from '../utils'; @@ -17,7 +18,7 @@ const withWidgetData = (Widget, widgetId) => { const { filters } = props; - useEffect(() => { + useDeepCompareEffect(() => { const fetch = async () => { try { updateLoading(true); From ae252bbc70ffeaa034d0b973b8fd40f654cdadbe Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 2 Apr 2024 11:18:11 -0400 Subject: [PATCH 03/14] More react duct tape to prevent re-renders --- frontend/src/hooks/useDashboardFilterKey.js | 13 +++++++++++++ .../pages/RegionalDashboard/components/Dashboard.js | 9 ++++++--- frontend/src/pages/RegionalDashboard/index.js | 9 +++------ 3 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 frontend/src/hooks/useDashboardFilterKey.js diff --git a/frontend/src/hooks/useDashboardFilterKey.js b/frontend/src/hooks/useDashboardFilterKey.js new file mode 100644 index 0000000000..f74124259f --- /dev/null +++ b/frontend/src/hooks/useDashboardFilterKey.js @@ -0,0 +1,13 @@ +import { useMemo } from 'react'; + +const FILTER_KEY = (dashboardName, reportType) => `${dashboardName}-filters-${reportType}`; + +export default function useDashboardFilterKey( + dashboardName, + reportType = '', +) { + const filterKey = useMemo(() => FILTER_KEY( + dashboardName, reportType, + ), [dashboardName, reportType]); + return filterKey; +} diff --git a/frontend/src/pages/RegionalDashboard/components/Dashboard.js b/frontend/src/pages/RegionalDashboard/components/Dashboard.js index bb8ad6e0af..23c39ccd7a 100644 --- a/frontend/src/pages/RegionalDashboard/components/Dashboard.js +++ b/frontend/src/pages/RegionalDashboard/components/Dashboard.js @@ -1,17 +1,20 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import { GridContainer } from '@trussworks/react-uswds'; import ActivityReportDashboard from './ActivityReportDashboard'; import TrainingReportDashboard from './TrainingReportDashboard'; import AllReports from './AllReports'; +import { expandFilters } from '../../../utils'; export default function Dashboard({ reportType, - filtersToApply, + filters, resetPagination, setResetPagination, filterKey, }) { + const filtersToApply = useMemo(() => expandFilters(filters), [filters]); + let DashboardComponent = ActivityReportDashboard; switch (reportType) { case 'training-reports': @@ -37,7 +40,7 @@ export default function Dashboard({ } Dashboard.propTypes = { - filtersToApply: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + filters: PropTypes.arrayOf(PropTypes.shape({})).isRequired, resetPagination: PropTypes.bool.isRequired, setResetPagination: PropTypes.func.isRequired, filterKey: PropTypes.string.isRequired, diff --git a/frontend/src/pages/RegionalDashboard/index.js b/frontend/src/pages/RegionalDashboard/index.js index ddf2c4ee8b..c4257bf813 100644 --- a/frontend/src/pages/RegionalDashboard/index.js +++ b/frontend/src/pages/RegionalDashboard/index.js @@ -7,7 +7,6 @@ import ReactRouterPropTypes from 'react-router-prop-types'; import { Grid } from '@trussworks/react-uswds'; import FilterPanel from '../../components/filter/FilterPanel'; import { hasApproveActivityReport } from '../../permissions'; -import { expandFilters } from '../../utils'; import UserContext from '../../UserContext'; import { DASHBOARD_FILTER_CONFIG } from './constants'; import RegionPermissionModal from '../../components/RegionPermissionModal'; @@ -18,8 +17,7 @@ import useFilters from '../../hooks/useFilters'; import './index.css'; import TabsNav from '../../components/TabsNav'; import Dashboard from './components/Dashboard'; - -const FILTER_KEY = (reportType) => `regional-dashboard-filters-${reportType || 'activityReport'}`; +import useDashboardFilterKey from '../../hooks/useDashboardFilterKey'; const pageConfig = (userHasOnlyOneRegion, defaultRegion) => { const prefix = `${userHasOnlyOneRegion ? `Region ${defaultRegion}` : 'Regional'}`; @@ -63,7 +61,7 @@ export default function RegionalDashboard({ match }) { const [resetPagination, setResetPagination] = useState(false); const { reportType } = match.params; - const filterKey = FILTER_KEY(reportType); + const filterKey = useDashboardFilterKey('regional-dashboard', reportType || 'activityReports'); const { // from useUserDefaultRegionFilters @@ -83,7 +81,6 @@ export default function RegionalDashboard({ match }) { ); const userHasOnlyOneRegion = useMemo(() => regions.length === 1, [regions]); - const filtersToApply = expandFilters(filters); const { h1Text, @@ -136,7 +133,7 @@ export default function RegionalDashboard({ match }) { From f77df3910e9f7ac675d4084f5b5c766a74fddc76 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 2 Apr 2024 11:18:41 -0400 Subject: [PATCH 04/14] Handle new filter key --- .../components/TrainingReportDashboard.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frontend/src/pages/RegionalDashboard/components/TrainingReportDashboard.js b/frontend/src/pages/RegionalDashboard/components/TrainingReportDashboard.js index 8589ed0046..f3917cfcdb 100644 --- a/frontend/src/pages/RegionalDashboard/components/TrainingReportDashboard.js +++ b/frontend/src/pages/RegionalDashboard/components/TrainingReportDashboard.js @@ -1,6 +1,7 @@ import React from 'react'; import { Helmet } from 'react-helmet'; import { Grid, GridContainer } from '@trussworks/react-uswds'; +import Overview from '../../../widgets/TrainingReportDashboardOverview'; export default function TrainingReportDashboard() { return ( @@ -9,6 +10,18 @@ export default function TrainingReportDashboard() { Regional Dashboard - Training Reports + From 7b7810a09d43378a5236f6c98d1aa51059620609 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 2 Apr 2024 11:19:11 -0400 Subject: [PATCH 05/14] Add overview to TR dashboard --- frontend/src/widgets/DashboardOverview.js | 15 ++++- .../TrainingReportDashboardOverview.js | 57 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 frontend/src/widgets/TrainingReportDashboardOverview.js diff --git a/frontend/src/widgets/DashboardOverview.js b/frontend/src/widgets/DashboardOverview.js index f1920d74e3..0b0cdd9680 100644 --- a/frontend/src/widgets/DashboardOverview.js +++ b/frontend/src/widgets/DashboardOverview.js @@ -7,7 +7,6 @@ import { } from '@fortawesome/free-solid-svg-icons'; import withWidgetData from './withWidgetData'; import './DashboardOverview.css'; - import Loader from '../components/Loader'; import Tooltip from '../components/Tooltip'; import colors from '../colors'; @@ -78,6 +77,20 @@ const DASHBOARD_FIELDS = { /> ), }, + 'Training reports': { + render: (data, showTooltip) => ( + + ), + }, 'Grants served': { render: (data, showTooltip) => ( + ); +} + +TrainingReportDashboardOverview.propTypes = { + data: PropTypes.shape({ + numReports: PropTypes.string, + totalRecipients: PropTypes.string, + recipientPercentage: PropTypes.string, + numGrants: PropTypes.string, + numRecipients: PropTypes.string, + sumDuration: PropTypes.string, + numParticipants: PropTypes.string, + }), + filters: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string, + topic: PropTypes.string, + condition: PropTypes.string, + query: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + })).isRequired, + loading: PropTypes.bool.isRequired, + showTooltips: PropTypes.bool.isRequired, + fields: PropTypes.arrayOf(PropTypes.string).isRequired, +}; + +TrainingReportDashboardOverview.defaultProps = { + data: { + numReports: '0', + totalRecipients: '0', + recipientPercentage: '0%', + numGrants: '0', + numRecipients: '0', + sumDuration: '0', + numParticipants: '0', + }, +}; + +export default withWidgetData(TrainingReportDashboardOverview, 'trOverview'); From 05280323a7063f2cfe1085c717a7f1396d5f6cb1 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 2 Apr 2024 11:24:08 -0400 Subject: [PATCH 06/14] Lower level config --- .../components/TrainingReportDashboard.js | 7 ------- .../src/widgets/TrainingReportDashboardOverview.js | 12 ++++++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/RegionalDashboard/components/TrainingReportDashboard.js b/frontend/src/pages/RegionalDashboard/components/TrainingReportDashboard.js index f3917cfcdb..e6cc485d7a 100644 --- a/frontend/src/pages/RegionalDashboard/components/TrainingReportDashboard.js +++ b/frontend/src/pages/RegionalDashboard/components/TrainingReportDashboard.js @@ -12,13 +12,6 @@ export default function TrainingReportDashboard() { diff --git a/frontend/src/widgets/TrainingReportDashboardOverview.js b/frontend/src/widgets/TrainingReportDashboardOverview.js index 29a9b4cba8..2ec9eb700f 100644 --- a/frontend/src/widgets/TrainingReportDashboardOverview.js +++ b/frontend/src/widgets/TrainingReportDashboardOverview.js @@ -3,9 +3,8 @@ import PropTypes from 'prop-types'; import withWidgetData from './withWidgetData'; import { DashboardOverviewWidget } from './DashboardOverview'; -function TrainingReportDashboardOverview({ +export function TrainingReportDashboardOverview({ filters, - fields, showTooltips, loading, data, @@ -14,7 +13,13 @@ function TrainingReportDashboardOverview({ @@ -39,7 +44,6 @@ TrainingReportDashboardOverview.propTypes = { })).isRequired, loading: PropTypes.bool.isRequired, showTooltips: PropTypes.bool.isRequired, - fields: PropTypes.arrayOf(PropTypes.string).isRequired, }; TrainingReportDashboardOverview.defaultProps = { From f6bd87833b54e6af7a89ffaf611123c81a91fb61 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 2 Apr 2024 11:27:57 -0400 Subject: [PATCH 07/14] Add test --- .../TrainingReportDashboardOverview.js | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 frontend/src/widgets/__tests__/TrainingReportDashboardOverview.js diff --git a/frontend/src/widgets/__tests__/TrainingReportDashboardOverview.js b/frontend/src/widgets/__tests__/TrainingReportDashboardOverview.js new file mode 100644 index 0000000000..f94cd0e250 --- /dev/null +++ b/frontend/src/widgets/__tests__/TrainingReportDashboardOverview.js @@ -0,0 +1,52 @@ +import '@testing-library/jest-dom'; +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { TrainingReportDashboardOverview } from '../TrainingReportDashboardOverview'; + +describe('TrainingReportDashboardOverview', () => { + const defaultProps = { + filters: [], + showTooltips: true, + loading: false, + }; + + // eslint-disable-next-line react/jsx-props-no-spreading + const renderTest = (props) => render(); + + it('renders without explicit data', async () => { + renderTest(defaultProps); + + expect(screen.getAllByText('0')).toHaveLength(4); + expect(screen.getAllByText('0%')).toHaveLength(1); + expect(screen.getByText('Recipients have at least one active grant click to visually reveal this information')).toBeInTheDocument(); + expect(screen.getByText('Grants served')).toBeInTheDocument(); + expect(screen.getByText('Training reports')).toBeInTheDocument(); + expect(screen.getByText('Participants')).toBeInTheDocument(); + expect(screen.getByText('Hours of TTA')).toBeInTheDocument(); + }); + + it('renders with data', async () => { + const data = { + numReports: '1', + totalRecipients: '2', + recipientPercentage: '3%', + numGrants: '4', + numRecipients: '5', + sumDuration: '6', + numParticipants: '7', + }; + renderTest({ ...defaultProps, data }); + + expect(screen.getByText('1')).toBeInTheDocument(); + expect(screen.getByText('3%')).toBeInTheDocument(); + expect(screen.getByText('4')).toBeInTheDocument(); + expect(screen.getByText('6')).toBeInTheDocument(); + expect(screen.getByText('7')).toBeInTheDocument(); + + expect(screen.getByText('Recipients have at least one active grant click to visually reveal this information')).toBeInTheDocument(); + expect(screen.getByText('Grants served')).toBeInTheDocument(); + expect(screen.getByText('Training reports')).toBeInTheDocument(); + expect(screen.getByText('Participants')).toBeInTheDocument(); + expect(screen.getByText('Hours of TTA')).toBeInTheDocument(); + }); +}); From 6a2651d11e0632577af30e3f21537744345a45ba Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 4 Apr 2024 11:04:49 -0400 Subject: [PATCH 08/14] Update dataset based on customer feedback --- src/widgets/trOverview.test.js | 18 ++++++++---------- src/widgets/trOverview.ts | 10 +++++++++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/widgets/trOverview.test.js b/src/widgets/trOverview.test.js index 3c2a4f9e43..5c419e34be 100644 --- a/src/widgets/trOverview.test.js +++ b/src/widgets/trOverview.test.js @@ -86,6 +86,7 @@ describe('TR overview widget', () => { numberOfParticipantsVirtually: 0, numberOfParticipantsInPerson: 0, numberOfParticipants: 25, + status: TRAINING_REPORT_STATUSES.COMPLETE, }, }); @@ -99,6 +100,7 @@ describe('TR overview widget', () => { numberOfParticipantsVirtually: 0, numberOfParticipantsInPerson: 0, numberOfParticipants: 25, + status: TRAINING_REPORT_STATUSES.COMPLETE, }, }); @@ -119,6 +121,7 @@ describe('TR overview widget', () => { numberOfParticipantsVirtually: 12, numberOfParticipantsInPerson: 13, numberOfParticipants: 0, + status: TRAINING_REPORT_STATUSES.COMPLETE, }, }); @@ -132,10 +135,11 @@ describe('TR overview widget', () => { numberOfParticipantsVirtually: 0, numberOfParticipantsInPerson: 0, numberOfParticipants: 25, + status: TRAINING_REPORT_STATUSES.COMPLETE, }, }); - // training report 3 (not completed) + // training report 3 (sessions not completed) trainingReport3 = await createTrainingReport({ collaboratorIds: [userCollaborator.id], pocIds: [userPoc.id], @@ -152,6 +156,7 @@ describe('TR overview widget', () => { numberOfParticipantsVirtually: 0, numberOfParticipantsInPerson: 0, numberOfParticipants: 25, + status: TRAINING_REPORT_STATUSES.IN_PROGRESS, }, }); @@ -165,24 +170,17 @@ describe('TR overview widget', () => { numberOfParticipantsVirtually: 0, numberOfParticipantsInPerson: 0, numberOfParticipants: 25, + status: TRAINING_REPORT_STATUSES.IN_PROGRESS, }, }); - // update TR 1 to complete + // update TR 1 to complete, the others will be "in progress" as they have sessions await trainingReport1.update({ data: { ...trainingReport1.data, status: TRAINING_REPORT_STATUSES.COMPLETE, }, }); - - // update TR 2 to complete - await trainingReport2.update({ - data: { - ...trainingReport2.data, - status: TRAINING_REPORT_STATUSES.COMPLETE, - }, - }); }); afterAll(async () => { diff --git a/src/widgets/trOverview.ts b/src/widgets/trOverview.ts index 3a7488aeb5..8b7171237b 100644 --- a/src/widgets/trOverview.ts +++ b/src/widgets/trOverview.ts @@ -87,7 +87,12 @@ export default async function trOverview( where: { [Op.and]: [ { - 'data.status': TRAINING_REPORT_STATUSES.COMPLETE, + 'data.status': { + [Op.in]: [ + TRAINING_REPORT_STATUSES.IN_PROGRESS, + TRAINING_REPORT_STATUSES.COMPLETE, + ], + }, }, ...scopes.trainingReport, ], @@ -96,6 +101,9 @@ export default async function trOverview( model: SessionReport, as: 'sessionReports', attributes: ['data', 'eventId'], + where: { + 'data.status': TRAINING_REPORT_STATUSES.COMPLETE, + }, required: true, }, }) as ITrainingReportForOverview[]; From 78740cabe035e37d08bb94ea3283746fa3deea47 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 4 Apr 2024 14:46:15 -0400 Subject: [PATCH 09/14] Fix margins on goal nudge --- frontend/src/components/GoalForm/GoalNudge.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/GoalForm/GoalNudge.js b/frontend/src/components/GoalForm/GoalNudge.js index edd191ad1e..02b838d59b 100644 --- a/frontend/src/components/GoalForm/GoalNudge.js +++ b/frontend/src/components/GoalForm/GoalNudge.js @@ -102,7 +102,7 @@ export default function GoalNudge({ const checkboxZed = similar.length && !useOhsInitiativeGoal && !dismissSimilar ? 'z-bottom' : ''; return ( -
+
-
+
{ (goalTemplates && goalTemplates.length > 0) && ( Date: Fri, 5 Apr 2024 09:47:50 -0400 Subject: [PATCH 10/14] Accessibility improvements for the goal nudge --- frontend/src/components/GoalForm/GoalNudge.js | 11 ++++++++++- .../components/GoalForm/GoalNudgeInitiativePicker.js | 6 ++++++ frontend/src/components/GoalForm/index.js | 4 +++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/GoalForm/GoalNudge.js b/frontend/src/components/GoalForm/GoalNudge.js index edd191ad1e..b71d34d9ad 100644 --- a/frontend/src/components/GoalForm/GoalNudge.js +++ b/frontend/src/components/GoalForm/GoalNudge.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import { Checkbox, @@ -38,12 +38,20 @@ export default function GoalNudge({ const [dismissSimilar, setDismissSimilar] = useState(false); const [goalTemplates, setGoalTemplates] = useState(null); + const initiativeRef = useRef(); + useEffect(() => { if (dismissSimilar) { setSimilarGoals([]); } }, [dismissSimilar]); + useEffect(() => { + if (useOhsInitiativeGoal && initiativeRef.current) { + initiativeRef.current.focus(); + } + }, [useOhsInitiativeGoal]); + // using DeepCompareEffect to avoid unnecessary fetches // as we have an object (selectedGrants) in the dependency array useDeepCompareEffect(() => { @@ -121,6 +129,7 @@ export default function GoalNudge({ validateGoalName={validateGoalName} goalTemplates={goalTemplates || []} onSelectNudgedGoal={onSelectNudgedGoal} + initiativeRef={initiativeRef} />
{ (goalTemplates && goalTemplates.length > 0) && ( diff --git a/frontend/src/components/GoalForm/GoalNudgeInitiativePicker.js b/frontend/src/components/GoalForm/GoalNudgeInitiativePicker.js index fd8fafd7c2..4593c6a6a9 100644 --- a/frontend/src/components/GoalForm/GoalNudgeInitiativePicker.js +++ b/frontend/src/components/GoalForm/GoalNudgeInitiativePicker.js @@ -17,6 +17,7 @@ export default function GoalNudgeInitiativePicker({ validateGoalName, goalTemplates, onSelectNudgedGoal, + initiativeRef, }) { const [selection, setSelection] = useState(''); @@ -40,6 +41,8 @@ export default function GoalNudgeInitiativePicker({