From 90c4e502edb12a7d496d81a5a551c83542ccd4d0 Mon Sep 17 00:00:00 2001 From: Josh Salisbury Date: Mon, 1 Feb 2021 18:41:14 -0600 Subject: [PATCH 1/4] Managers can add notes and set status of reports * Review page is now different if the user looking at the page is the approving manager * Manager's review page includes the user's additional note and allows the manager to leave a manager note * Manager note added to DB * Report state is shown in the side nav's "review" section once it leaves "draft" mode * Policy, service and route added to update status of a report --- frontend/src/App.js | 2 +- .../components/Navigator/__tests__/index.js | 5 +- .../Navigator/components/SideNav.css | 7 +- .../Navigator/components/SideNav.js | 8 +- .../src/components/Navigator/constants.js | 2 + frontend/src/components/Navigator/index.js | 16 +- frontend/src/fetchers/activityReports.js | 6 + .../ActivityReport/Pages/ReviewSubmit.css | 8 + .../ActivityReport/Pages/ReviewSubmit.js | 166 ++++++++++++++---- .../Pages/__tests__/ReviewSubmit.js | 34 +++- .../src/pages/ActivityReport/Pages/index.js | 36 ++-- .../pages/ActivityReport/__tests__/index.js | 1 + frontend/src/pages/ActivityReport/index.js | 26 ++- src/middleware/authMiddleware.test.js | 7 + ...8-add-manager-notes-to-activity-reports.js | 16 ++ src/models/activityReport.js | 4 + src/policies/activityReport.js | 4 + src/policies/activityReport.test.js | 14 ++ src/routes/activityReports/handlers.js | 33 +++- src/routes/activityReports/handlers.test.js | 43 ++++- src/routes/activityReports/index.js | 9 +- src/services/activityReports.js | 11 ++ 22 files changed, 376 insertions(+), 82 deletions(-) create mode 100644 frontend/src/pages/ActivityReport/Pages/ReviewSubmit.css create mode 100644 src/migrations/20210201174748-add-manager-notes-to-activity-reports.js diff --git a/frontend/src/App.js b/frontend/src/App.js index c992efaef2..88b9b04c9f 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -76,7 +76,7 @@ function App() { ( - + )} /> {admin diff --git a/frontend/src/components/Navigator/__tests__/index.js b/frontend/src/components/Navigator/__tests__/index.js index fde326316e..a11f34e5f7 100644 --- a/frontend/src/components/Navigator/__tests__/index.js +++ b/frontend/src/components/Navigator/__tests__/index.js @@ -42,7 +42,7 @@ const pages = [ label: 'review page', path: 'review', review: true, - render: (hookForm, allComplete, formData, submitted, onSubmit) => ( + render: (hookForm, allComplete, formData, onSubmit) => (
@@ -59,6 +59,9 @@ describe('Navigator', () => { {}} + approvingManager={false} defaultValues={{ first: '', second: '' }} pages={pages} currentPage={currentPage} diff --git a/frontend/src/components/Navigator/components/SideNav.css b/frontend/src/components/Navigator/components/SideNav.css index 685fc85f03..d917223677 100644 --- a/frontend/src/components/Navigator/components/SideNav.css +++ b/frontend/src/components/Navigator/components/SideNav.css @@ -49,8 +49,13 @@ color: #f8f8f8; } +.smart-hub--tag-needs-action { + background-color: #f9e0e4; + color: #d42240; +} + .smart-hub--tag { - width: 84px; + width: 94px; text-align: center; font-weight: normal; display: inline-block; diff --git a/frontend/src/components/Navigator/components/SideNav.js b/frontend/src/components/Navigator/components/SideNav.js index f1e172ff23..7de4990ba3 100644 --- a/frontend/src/components/Navigator/components/SideNav.js +++ b/frontend/src/components/Navigator/components/SideNav.js @@ -13,7 +13,7 @@ import moment from 'moment'; import Container from '../../Container'; import './SideNav.css'; import { - NOT_STARTED, IN_PROGRESS, COMPLETE, SUBMITTED, + NOT_STARTED, IN_PROGRESS, COMPLETE, SUBMITTED, APPROVED, NEEDS_ACTION, } from '../constants'; const tagClass = (state) => { @@ -26,6 +26,10 @@ const tagClass = (state) => { return 'smart-hub--tag-complete'; case SUBMITTED: return 'smart-hub--tag-submitted'; + case APPROVED: + return 'smart-hub--tag-submitted'; + case NEEDS_ACTION: + return 'smart-hub--tag-needs-action'; default: return ''; } @@ -46,7 +50,7 @@ function SideNav({ > {page.label} - {page.state + {page.state !== 'draft' && ( {page.state} diff --git a/frontend/src/components/Navigator/constants.js b/frontend/src/components/Navigator/constants.js index 35cbee5d85..b644ef86cb 100644 --- a/frontend/src/components/Navigator/constants.js +++ b/frontend/src/components/Navigator/constants.js @@ -2,3 +2,5 @@ export const NOT_STARTED = 'Not started'; export const IN_PROGRESS = 'In progress'; export const COMPLETE = 'Complete'; export const SUBMITTED = 'Submitted'; +export const APPROVED = 'Approved'; +export const NEEDS_ACTION = 'Needs Action'; diff --git a/frontend/src/components/Navigator/index.js b/frontend/src/components/Navigator/index.js index 1b7f0c2650..a50fb62a24 100644 --- a/frontend/src/components/Navigator/index.js +++ b/frontend/src/components/Navigator/index.js @@ -15,7 +15,7 @@ import moment from 'moment'; import Container from '../Container'; import { - IN_PROGRESS, COMPLETE, SUBMITTED, + IN_PROGRESS, COMPLETE, } from './constants'; import SideNav from './components/SideNav'; import NavigatorHeader from './components/NavigatorHeader'; @@ -24,11 +24,13 @@ function Navigator({ initialData, pages, onFormSubmit, - submitted, + onReview, currentPage, additionalData, onSave, autoSaveInterval, + approvingManager, + status, }) { const [formData, updateFormData] = useState(initialData); const [errorMessage, updateErrorMessage] = useState(); @@ -36,7 +38,6 @@ function Navigator({ const { pageState } = formData; const page = pages.find((p) => p.path === currentPage); - const submittedNavState = submitted ? SUBMITTED : null; const allComplete = _.every(pageState, (state) => state === COMPLETE); const hookForm = useForm({ @@ -101,7 +102,7 @@ function Navigator({ const navigatorPages = pages.map((p) => { const current = p.position === page.position; const stateOfPage = current ? IN_PROGRESS : pageState[p.position]; - const state = p.review ? submittedNavState : stateOfPage; + const state = p.review ? status : stateOfPage; return { label: p.label, onNavigation: () => onSaveForm(false, p.position), @@ -128,9 +129,10 @@ function Navigator({ hookForm, allComplete, formData, - submitted, onFormSubmit, additionalData, + onReview, + approvingManager, )} {!page.review && ( @@ -156,8 +158,10 @@ function Navigator({ Navigator.propTypes = { initialData: PropTypes.shape({}), onFormSubmit: PropTypes.func.isRequired, - submitted: PropTypes.bool.isRequired, onSave: PropTypes.func.isRequired, + status: PropTypes.string.isRequired, + onReview: PropTypes.func.isRequired, + approvingManager: PropTypes.bool.isRequired, pages: PropTypes.arrayOf( PropTypes.shape({ review: PropTypes.bool.isRequired, diff --git a/frontend/src/fetchers/activityReports.js b/frontend/src/fetchers/activityReports.js index 5e4e92f4b3..476d4d734e 100644 --- a/frontend/src/fetchers/activityReports.js +++ b/frontend/src/fetchers/activityReports.js @@ -39,3 +39,9 @@ export const getCollaborators = async (region) => { const collaborators = await get(url); return collaborators.json(); }; + +export const reviewReport = async (reportId, data) => { + const url = join(activityReportUrl, reportId.toString(10), 'review'); + const report = await put(url, data); + return report.json(); +}; diff --git a/frontend/src/pages/ActivityReport/Pages/ReviewSubmit.css b/frontend/src/pages/ActivityReport/Pages/ReviewSubmit.css new file mode 100644 index 0000000000..82dbcd2e28 --- /dev/null +++ b/frontend/src/pages/ActivityReport/Pages/ReviewSubmit.css @@ -0,0 +1,8 @@ +.smart-hub--creator-notes { + padding: 1rem; + background-color: #f8f8f8; +} + +#status { + max-width: 270px; +} diff --git a/frontend/src/pages/ActivityReport/Pages/ReviewSubmit.js b/frontend/src/pages/ActivityReport/Pages/ReviewSubmit.js index 6670b9f252..4b5e12f3d7 100644 --- a/frontend/src/pages/ActivityReport/Pages/ReviewSubmit.js +++ b/frontend/src/pages/ActivityReport/Pages/ReviewSubmit.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { Dropdown, Form, Label, Fieldset, Textarea, Alert, Button, Accordion, @@ -6,16 +6,52 @@ import { import { Helmet } from 'react-helmet'; import Container from '../../../components/Container'; +import './ReviewSubmit.css'; + +const possibleStatus = [ + 'Approved', + 'Needs Action', +]; const ReviewSubmit = ({ - hookForm, allComplete, onSubmit, submitted, reviewItems, approvers, + hookForm, + allComplete, + onSubmit, + onReview, + reviewItems, + approvers, + approvingManager, + initialData, }) => { const { handleSubmit, register, formState } = hookForm; + const { additionalNotes } = initialData; const { isValid } = formState; const valid = allComplete && isValid; - const onFormSubmit = (data) => { - onSubmit(data); + const [submitted, updateSubmitted] = useState(false); + const [reviewed, updateReviewed] = useState(false); + const [error, updateError] = useState(); + + const onFormSubmit = async (data) => { + try { + await onSubmit(data); + updateSubmitted(true); + updateError(); + } catch (e) { + updateSubmitted(false); + updateError('Unable to submit report'); + } + }; + + const onFormReview = async (data) => { + try { + await onReview(data); + updateReviewed(true); + updateError(); + } catch (e) { + updateReviewed(false); + updateError('Unable to review report'); + } }; const setValue = (e) => { @@ -32,44 +68,92 @@ const ReviewSubmit = ({ -

Submit Report

- {submitted - && ( - - Success -
- This report was successfully submitted for approval -
- )} - {!allComplete - && ( + {error && ( - Incomplete report + Error
- This report cannot be submitted until all sections are complete + {error}
)} -
-
- -