Skip to content

Commit

Permalink
Merge pull request #264 from adhocteam/main
Browse files Browse the repository at this point in the history
Submit a report for approval
  • Loading branch information
rahearn authored Jan 22, 2021
2 parents 2d34634 + d72bb39 commit 4948bd3
Show file tree
Hide file tree
Showing 18 changed files with 236 additions and 156 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ parameters:
default: "main"
type: string
sandbox_git_branch: # change to feature branch to test deployment
default: "js-add-report-fields"
default: "js-135-submit-report"
type: string
jobs:
build_and_lint:
Expand Down
31 changes: 31 additions & 0 deletions docs/openapi/paths/activity-reports/submit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
post:
tags:
- activity-reports
summary: Submit an activity report for approval
requestBody:
description: The approving manager and any additional notes for the report
required: true
content:
application/json:
schema:
type: object
properties:
approvingManagerId:
type: string
description: The id of the manager assigned to approve the report
additionalNotes:
type: string
description: Any contextual information the author/collaborators want to provide the approving manager
parameters:
- in: path
name: activityReportId
required: true
schema:
type: number
responses:
200:
description: The submitted activity report
content:
application/json:
schema:
$ref: '../../index.yaml#/components/schemas/activityReport'
2 changes: 2 additions & 0 deletions docs/openapi/paths/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@
$ref: './activity-reports/activity-recipients.yaml'
'/activity-reports/{activityReportId}':
$ref: './activity-reports/activity-reports-id.yaml'
'/activity-reports/{activityReportId}/submit':
$ref: './activity-reports/submit.yaml'
27 changes: 14 additions & 13 deletions frontend/src/components/Navigator/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,27 +42,28 @@ const pages = [
label: 'review page',
path: 'review',
review: true,
render: (allComplete, formData, submitted, onSubmit) => (
render: (hookForm, allComplete, formData, submitted, onSubmit) => (
<div>
<button type="button" data-testid="review" onClick={onSubmit}>Continue</button>
</div>
),
},
];

const initialData = { pageState: { 1: NOT_STARTED, 2: NOT_STARTED } };

describe('Navigator', () => {
// eslint-disable-next-line arrow-body-style
const renderNavigator = (currentPage = 'first', onSubmit = () => {}, updatePage = () => {}) => {
const renderNavigator = (currentPage = 'first', onSubmit = () => {}, onSave = () => {}) => {
render(
<Navigator
submitted={false}
initialData={{ pageState: { 1: NOT_STARTED, 2: NOT_STARTED } }}
initialData={initialData}
defaultValues={{ first: '', second: '' }}
pages={pages}
updatePage={updatePage}
currentPage={currentPage}
onFormSubmit={onSubmit}
onSave={() => {}}
onSave={onSave}
/>,
);
};
Expand All @@ -75,11 +76,11 @@ describe('Navigator', () => {
await waitFor(() => expect(within(first).getByText('In progress')).toBeVisible());
});

it('onContinue calls update page with correct page position', async () => {
const updatePage = jest.fn();
renderNavigator('second', () => {}, updatePage);
it('onContinue calls onSave with correct page position', async () => {
const onSave = jest.fn();
renderNavigator('second', () => {}, onSave);
userEvent.click(screen.getByRole('button', { name: 'Continue' }));
await waitFor(() => expect(updatePage).toHaveBeenCalledWith(3));
await waitFor(() => expect(onSave).toHaveBeenCalledWith({ pageState: { ...initialData.pageState, 2: 'Complete' }, second: '' }, 3));
});

it('submits data when "continuing" from the review page', async () => {
Expand All @@ -89,10 +90,10 @@ describe('Navigator', () => {
await waitFor(() => expect(onSubmit).toHaveBeenCalled());
});

it('calls updatePage on navigation', async () => {
const updatePage = jest.fn();
renderNavigator('second', () => {}, updatePage);
it('calls onSave on navigation', async () => {
const onSave = jest.fn();
renderNavigator('second', () => {}, onSave);
userEvent.click(screen.getByRole('button', { name: 'first page' }));
await waitFor(() => expect(updatePage).toHaveBeenCalledWith(1));
await waitFor(() => expect(onSave).toHaveBeenCalledWith({ ...initialData, second: '' }, 1));
});
});
32 changes: 0 additions & 32 deletions frontend/src/components/Navigator/components/IndicatorHeader.js

This file was deleted.

20 changes: 20 additions & 0 deletions frontend/src/components/Navigator/components/NavigatorHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
USWDS step indicator header, grabbed from https://designsystem.digital.gov/components/step-indicator/
The uswds react library we use doesn't have this component yet.
*/
import React from 'react';
import PropTypes from 'prop-types';

const NavigatorHeader = ({ label }) => (
<div className="usa-step-indicator__header">
<h2 className="usa-step-indicator__heading">
<span className="usa-step-indicator__heading-text">{label}</span>
</h2>
</div>
);

NavigatorHeader.propTypes = {
label: PropTypes.string.isRequired,
};

export default NavigatorHeader;
32 changes: 15 additions & 17 deletions frontend/src/components/Navigator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@ import {
IN_PROGRESS, COMPLETE, SUBMITTED,
} from './constants';
import SideNav from './components/SideNav';
import IndicatorHeader from './components/IndicatorHeader';
import NavigatorHeader from './components/NavigatorHeader';

function Navigator({
initialData,
pages,
onFormSubmit,
submitted,
currentPage,
updatePage,
additionalData,
onSave,
autoSaveInterval,
Expand Down Expand Up @@ -68,11 +67,12 @@ function Navigator({
return newPageState;
};

const onSaveForm = async (completed) => {
const onSaveForm = async (completed, index) => {
const data = { ...formData, ...getValues(), pageState: newNavigatorState(completed) };
const newIndex = index === page.position ? null : index;
try {
updateFormData(data);
const result = await onSave(data);
const result = await onSave(data, newIndex);
if (result) {
updateLastSaveTime(moment());
}
Expand All @@ -84,13 +84,8 @@ function Navigator({
}
};

const navigateToPage = (index, completed) => {
onSaveForm(completed);
updatePage(index);
};

const onContinue = () => {
navigateToPage(page.position + 1, true);
onSaveForm(true, page.position + 1);
};

useInterval(() => {
Expand All @@ -109,7 +104,7 @@ function Navigator({
const state = p.review ? submittedNavState : stateOfPage;
return {
label: p.label,
onNavigation: () => navigateToPage(p.position),
onNavigation: () => onSaveForm(false, p.position),
state,
current,
};
Expand All @@ -124,19 +119,23 @@ function Navigator({
pages={navigatorPages}
lastSaveTime={lastSaveTime}
errorMessage={errorMessage}
navigateToPage={navigateToPage}
/>
</Grid>
<Grid col={12} tablet={{ col: 6 }} desktop={{ col: 8 }}>
<div id="navigator-form">
{page.review
&& page.render(allComplete, formData, submitted, onFormSubmit, additionalData)}
&& page.render(
hookForm,
allComplete,
formData,
submitted,
onFormSubmit,
additionalData,
)}
{!page.review
&& (
<Container skipTopPadding>
<IndicatorHeader
currentStep={page.position}
totalSteps={pages.filter((p) => !p.review).length}
<NavigatorHeader
label={page.label}
/>
<Form
Expand Down Expand Up @@ -169,7 +168,6 @@ Navigator.propTypes = {
}),
).isRequired,
currentPage: PropTypes.string.isRequired,
updatePage: PropTypes.func.isRequired,
autoSaveInterval: PropTypes.number,
additionalData: PropTypes.shape({}),
};
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/fetchers/activityReports.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ export const fetchApprovers = async () => {
};

export const submitReport = async (reportId, data) => {
const url = join(activityReportUrl, reportId, 'submit');
await post(url, {
report: data,
});
const url = join(activityReportUrl, reportId.toString(10), 'submit');
const report = await post(url, data);
return report.json();
};

export const saveReport = async (reportId, data) => {
Expand Down
66 changes: 29 additions & 37 deletions frontend/src/pages/ActivityReport/Pages/ReviewSubmit.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
Form, Label, Fieldset, Textarea, Alert, Button, Accordion,
Dropdown, Form, Label, Fieldset, Textarea, Alert, Button, Accordion,
} from '@trussworks/react-uswds';
import { useForm } from 'react-hook-form';
import { Helmet } from 'react-helmet';

import { fetchApprovers } from '../../../fetchers/activityReports';
import MultiSelect from '../../../components/MultiSelect';
import Container from '../../../components/Container';

const defaultValues = {
approvingManagers: null,
additionalNotes: null,
};

const ReviewSubmit = ({
initialData, allComplete, onSubmit, submitted, reviewItems,
hookForm, allComplete, onSubmit, submitted, reviewItems,
}) => {
const [loading, updateLoading] = useState(true);
const [possibleApprovers, updatePossibleApprovers] = useState([]);
const { handleSubmit, register, formState } = hookForm;
const { isValid } = formState;
const valid = allComplete && isValid;

useEffect(() => {
updateLoading(true);
Expand All @@ -31,22 +27,24 @@ const ReviewSubmit = ({
fetch();
}, []);

const {
handleSubmit, register, formState, control,
} = useForm({
mode: 'onChange',
defaultValues: { ...defaultValues, ...initialData },
});

const onFormSubmit = (data) => {
onSubmit(data);
};

const {
isValid,
} = formState;
if (loading) {
return (
<div>
loading...
</div>
);
}

const valid = allComplete && isValid;
const setValue = (e) => {
if (e === '') {
return null;
}
return parseInt(e, 10);
};

return (
<>
Expand Down Expand Up @@ -74,7 +72,7 @@ const ReviewSubmit = ({
)}
<Form className="smart-hub--form-large" onSubmit={handleSubmit(onFormSubmit)}>
<Fieldset className="smart-hub--report-legend smart-hub--form-section" legend="Additional Notes">
<Label htmlFor="additionalNotes">Additional notes for this activity (optional)</Label>
<Label htmlFor="additionalNotes">Creator notes</Label>
<Textarea inputRef={register} id="additionalNotes" name="additionalNotes" />
</Fieldset>
<Fieldset className="smart-hub--report-legend smart-hub--form-section" legend="Review and submit report">
Expand All @@ -83,29 +81,27 @@ const ReviewSubmit = ({
Please review all information in each section before submitting to your manager for
approval.
</p>
<MultiSelect
label="Manager - you may choose more than one."
name="approvingManagers"
options={possibleApprovers.map((user) => ({
label: user.name,
value: user.id,
}))}
control={control}
disabled={loading}
/>
<Label htmlFor="approvingManagerId">Approving manager</Label>
<Dropdown id="approvingManagerId" name="approvingManagerId" inputRef={register({ setValueAs: setValue, required: true })}>
<option name="default" value="" disabled hidden>Select a Manager...</option>
{possibleApprovers.map((approver) => (
<option key={approver.id} value={approver.id}>{approver.name}</option>
))}
</Dropdown>
</Fieldset>
<Button type="submit" disabled={!valid}>Submit report for approval</Button>
<Button type="submit" disabled={!valid}>Submit for approval</Button>
</Form>
</Container>
</>
);
};

ReviewSubmit.propTypes = {
initialData: PropTypes.shape({}),
allComplete: PropTypes.bool.isRequired,
onSubmit: PropTypes.func.isRequired,
submitted: PropTypes.bool.isRequired,
// eslint-disable-next-line react/forbid-prop-types
hookForm: PropTypes.object.isRequired,
reviewItems: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
Expand All @@ -115,8 +111,4 @@ ReviewSubmit.propTypes = {
).isRequired,
};

ReviewSubmit.defaultProps = {
initialData: {},
};

export default ReviewSubmit;
Loading

0 comments on commit 4948bd3

Please sign in to comment.