Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Prod] Reopen select closed FEI goals, add statistics widget to training report dashboard #2085

Merged
merged 17 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/components/GoalCards/GoalCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
15 changes: 12 additions & 3 deletions frontend/src/components/GoalForm/GoalNudge.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import {
Checkbox,
Expand Down Expand Up @@ -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(() => {
Expand Down Expand Up @@ -102,7 +110,7 @@ export default function GoalNudge({
const checkboxZed = similar.length && !useOhsInitiativeGoal && !dismissSimilar ? 'z-bottom' : '';

return (
<div className="ttahub-goal-nudge--container position-relative">
<div className="ttahub-goal-nudge--container position-relative margin-bottom-3">
<GoalNudgeText
error={error}
inputName={inputName}
Expand All @@ -121,8 +129,9 @@ export default function GoalNudge({
validateGoalName={validateGoalName}
goalTemplates={goalTemplates || []}
onSelectNudgedGoal={onSelectNudgedGoal}
initiativeRef={initiativeRef}
/>
<div className="desktop:display-flex flex-justify margin-top-2 smart-hub-maxw-form-field">
<div className="desktop:display-flex flex-justify margin-top-1 smart-hub-maxw-form-field">
{ (goalTemplates && goalTemplates.length > 0) && (
<Checkbox
id="use-ohs-initiative-goal"
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/GoalForm/GoalNudgeInitiativePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function GoalNudgeInitiativePicker({
validateGoalName,
goalTemplates,
onSelectNudgedGoal,
initiativeRef,
}) {
const [selection, setSelection] = useState('');

Expand All @@ -40,6 +41,8 @@ export default function GoalNudgeInitiativePicker({
<Req />
</Label>
<Select
aria-label="OHS initiative goal"
ref={initiativeRef}
inputId="goal-template"
name="goal-template"
className="usa-select"
Expand Down Expand Up @@ -68,4 +71,7 @@ GoalNudgeInitiativePicker.propTypes = {
})).isRequired,
useOhsInitiativeGoal: PropTypes.bool.isRequired,
onSelectNudgedGoal: PropTypes.func.isRequired,
initiativeRef: PropTypes.shape({
current: PropTypes.instanceOf(Element),
}).isRequired,
};
4 changes: 3 additions & 1 deletion frontend/src/components/GoalForm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -912,11 +912,12 @@ export default function GoalForm({
goalTemplateId: goal.id,
}));
const created = await createOrUpdateGoals(goals);
setAppLoadingText('loading');
forwardToGoalWithIds(created.map((g) => g.goalIds).flat());
};

try {
setAppLoadingText('Retrieving existing goal');
setAppLoadingText('loading existing goal');
setNudgedGoalSelection(goal);

if (goal.status === 'Suspended') {
Expand All @@ -926,6 +927,7 @@ export default function GoalForm({

if (goal.isCurated) {
// we need to do a little magic here to get the goal
setAppLoadingText('creating new ohs initiative goal');
await onSelectInitiativeGoal();
return;
}
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/components/Tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/hooks/useDashboardFilterKey.js
Original file line number Diff line number Diff line change
@@ -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;
}
9 changes: 6 additions & 3 deletions frontend/src/pages/RegionalDashboard/components/Dashboard.js
Original file line number Diff line number Diff line change
@@ -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':
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -9,6 +10,11 @@ export default function TrainingReportDashboard() {
<title>Regional Dashboard - Training Reports</title>
</Helmet>
<GridContainer className="margin-0 padding-0">
<Overview
filters={[]}
showTooltips
loading={false}
/>
<Grid row gap={2}>
<Grid desktop={{ col: 5 }} tabletLg={{ col: 12 }} />
<Grid desktop={{ col: 7 }} tabletLg={{ col: 12 }} />
Expand Down
9 changes: 3 additions & 6 deletions frontend/src/pages/RegionalDashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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'}`;
Expand Down Expand Up @@ -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
Expand All @@ -83,7 +81,6 @@ export default function RegionalDashboard({ match }) {
);

const userHasOnlyOneRegion = useMemo(() => regions.length === 1, [regions]);
const filtersToApply = expandFilters(filters);

const {
h1Text,
Expand Down Expand Up @@ -136,7 +133,7 @@ export default function RegionalDashboard({ match }) {
<Dashboard
reportType={reportType}
setResetPagination={setResetPagination}
filtersToApply={filtersToApply}
filters={filters}
filterKey={filterKey}
resetPagination={resetPagination}
/>
Expand Down
15 changes: 14 additions & 1 deletion frontend/src/widgets/DashboardOverview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -78,6 +77,20 @@ const DASHBOARD_FIELDS = {
/>
),
},
'Training reports': {
render: (data, showTooltip) => (
<Field
key="training-reports"
showTooltip={showTooltip}
tooltipText="Training reports with a completed session"
icon={faChartColumn}
iconColor={colors.success}
backgroundColor={colors.successLighter}
label={`across ${data.numReports} training reports`}
data={`${data.numSessions} sessions`}
/>
),
},
'Grants served': {
render: (data, showTooltip) => (
<Field
Expand Down
62 changes: 62 additions & 0 deletions frontend/src/widgets/TrainingReportDashboardOverview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import PropTypes from 'prop-types';
import withWidgetData from './withWidgetData';
import { DashboardOverviewWidget } from './DashboardOverview';

export function TrainingReportDashboardOverview({
filters,
showTooltips,
loading,
data,
}) {
return (
<DashboardOverviewWidget
data={data}
filters={filters}
fields={[
'Recipients served',
'Grants served',
'Training reports',
'Participants',
'Hours of TTA',
]}
showTooltips={showTooltips}
loading={loading}
/>
);
}

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,
};

TrainingReportDashboardOverview.defaultProps = {
data: {
numReports: '0',
totalRecipients: '0',
recipientPercentage: '0%',
numGrants: '0',
numRecipients: '0',
sumDuration: '0',
numParticipants: '0',
numSessions: '0',
},
};

export default withWidgetData(TrainingReportDashboardOverview, 'trOverview');
54 changes: 54 additions & 0 deletions frontend/src/widgets/__tests__/TrainingReportDashboardOverview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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(<TrainingReportDashboardOverview {...props} />);

it('renders without explicit data', async () => {
renderTest(defaultProps);

expect(screen.getAllByText('0')).toHaveLength(3);
expect(screen.getByText('0 sessions')).toBeInTheDocument();
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('across 0 training reports')).toBeInTheDocument();
expect(screen.getByText('Participants')).toBeInTheDocument();
expect(screen.getByText('Hours of TTA')).toBeInTheDocument();
});

it('renders with data', async () => {
const data = {
numReports: '2',
totalRecipients: '1',
recipientPercentage: '3%',
numGrants: '4',
numRecipients: '5',
sumDuration: '6',
numParticipants: '7',
numSessions: '2',
};
renderTest({ ...defaultProps, data });

expect(screen.getByText('2 sessions')).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('across 2 training reports')).toBeInTheDocument();
expect(screen.getByText('Participants')).toBeInTheDocument();
expect(screen.getByText('Hours of TTA')).toBeInTheDocument();
});
});
5 changes: 3 additions & 2 deletions frontend/src/widgets/withWidgetData.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -17,7 +18,7 @@ const withWidgetData = (Widget, widgetId) => {

const { filters } = props;

useEffect(() => {
useDeepCompareEffect(() => {
const fetch = async () => {
try {
updateLoading(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ module.exports = {
;

-- Remove AR link records: -------------
DROP TABLE IF EXISTS deleted_activityrecipients;
CREATE TEMP TABLE deleted_activityrecipients AS
WITH deletes AS (
DELETE FROM "ActivityRecipients"
Expand Down
Loading