diff --git a/docker-compose.yml b/docker-compose.yml
index f366b61f31..e48af8837c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -27,7 +27,9 @@ services:
aws-cli:
image: amazon/aws-cli
env_file: .env
- command: ["--endpoint-url", "$S3_ENDPOINT", "s3api", "create-bucket", "--bucket", "$S3_BUCKET"]
+ command: ["--endpoint-url", "http://minio:9000", "s3api", "create-bucket", "--bucket", "$S3_BUCKET"]
+ depends_on:
+ - minio
clamav-rest:
image: ajilaag/clamav-rest
ports:
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 82f6a408b0..2ffa03179d 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -73,6 +73,9 @@ body {
}
@media print {
+ .smart-hub-header {
+ position: relative;
+ }
* {
color: #000 !important;
diff --git a/frontend/src/components/FileUploader.js b/frontend/src/components/FileUploader.js
index 9206935ce1..0ce8a61435 100644
--- a/frontend/src/components/FileUploader.js
+++ b/frontend/src/components/FileUploader.js
@@ -33,7 +33,7 @@ export const upload = async (file, reportId, attachmentType, setErrorMessage) =>
}
setErrorMessage(null);
return {
- id: res.id, originalFileName: file.name, fileSize: file.size, status: 'UPLOADED',
+ id: res.id, originalFileName: file.name, fileSize: file.size, status: 'UPLOADED', url: res.url,
};
};
diff --git a/frontend/src/components/Navigator/components/__tests__/SideNav.js b/frontend/src/components/Navigator/components/__tests__/SideNav.js
index ae84297d00..051beb07c1 100644
--- a/frontend/src/components/Navigator/components/__tests__/SideNav.js
+++ b/frontend/src/components/Navigator/components/__tests__/SideNav.js
@@ -13,7 +13,7 @@ import {
import { REPORT_STATUSES } from '../../../../Constants';
describe('SideNav', () => {
- const renderNav = (state, onNavigation = () => {}, current = false) => {
+ const renderNav = (state, onNavigation = () => {}, current = false, errorMessage = null) => {
const pages = [
{
label: 'test',
@@ -34,6 +34,7 @@ describe('SideNav', () => {
pages={pages}
skipTo="skip"
skipToMessage="message"
+ errorMessage={errorMessage}
/>,
);
};
@@ -60,6 +61,20 @@ describe('SideNav', () => {
expect(complete).toBeVisible();
});
+ it('approved', () => {
+ renderNav(REPORT_STATUSES.APPROVED);
+ const complete = screen.getByText('Approved');
+ expect(complete).toHaveClass('smart-hub--tag-submitted');
+ expect(complete).toBeVisible();
+ });
+
+ it('needs action', () => {
+ renderNav(REPORT_STATUSES.NEEDS_ACTION);
+ const complete = screen.getByText('Needs Action');
+ expect(complete).toHaveClass('smart-hub--tag-needs-action');
+ expect(complete).toBeVisible();
+ });
+
it('submitted', () => {
renderNav(REPORT_STATUSES.SUBMITTED);
const submitted = screen.getByText('Submitted');
@@ -68,6 +83,12 @@ describe('SideNav', () => {
});
});
+ it('displays error message', async () => {
+ renderNav(REPORT_STATUSES.SUBMITTED, () => {}, false, 'error');
+ const alert = await screen.findByTestId('alert');
+ expect(alert).toBeVisible();
+ });
+
it('clicking a nav item calls onNavigation', () => {
const onNav = jest.fn();
renderNav(NOT_STARTED, onNav);
diff --git a/frontend/src/components/Navigator/index.js b/frontend/src/components/Navigator/index.js
index e3b0b34a36..f2cf10b0dc 100644
--- a/frontend/src/components/Navigator/index.js
+++ b/frontend/src/components/Navigator/index.js
@@ -52,6 +52,7 @@ function Navigator({
const hookForm = useForm({
mode: 'onChange',
defaultValues: formData,
+ shouldUnregister: false,
});
const {
diff --git a/frontend/src/components/__tests__/FileUploader.js b/frontend/src/components/__tests__/FileUploader.js
index 481f47d26f..961714bb06 100644
--- a/frontend/src/components/__tests__/FileUploader.js
+++ b/frontend/src/components/__tests__/FileUploader.js
@@ -32,10 +32,10 @@ describe('upload tests', () => {
const mockFile = { name: 'MockFile', size: 2000 };
const mockSetErrorMessage = jest.fn();
it('can upload a file and return the correct information', async () => {
- const mockFileUpload = jest.spyOn(fileFetcher, 'uploadFile').mockImplementation(async () => ({ id: 1 }));
+ const mockFileUpload = jest.spyOn(fileFetcher, 'uploadFile').mockImplementation(async () => ({ id: 1, url: 'url' }));
const got = await upload(mockFile, 1, 'fakeAttachment', mockSetErrorMessage);
expect(got).toStrictEqual({
- id: 1, originalFileName: mockFile.name, fileSize: mockFile.size, status: 'UPLOADED',
+ id: 1, originalFileName: mockFile.name, fileSize: mockFile.size, status: 'UPLOADED', url: 'url',
});
expect(mockFileUpload).toHaveBeenCalled();
expect(mockSetErrorMessage).toHaveBeenCalledWith(null);
diff --git a/frontend/src/fetchers/__tests__/activityReports.js b/frontend/src/fetchers/__tests__/activityReports.js
new file mode 100644
index 0000000000..b9ae5bca27
--- /dev/null
+++ b/frontend/src/fetchers/__tests__/activityReports.js
@@ -0,0 +1,46 @@
+import join from 'url-join';
+import fetchMock from 'fetch-mock';
+
+import {
+ submitReport, saveReport, reviewReport, resetToDraft,
+} from '../activityReports';
+
+describe('activityReports fetcher', () => {
+ afterEach(() => fetchMock.restore());
+
+ describe('submitReport', () => {
+ it('returns the report', async () => {
+ const report = { id: 1 };
+ fetchMock.post(join('api', 'activity-reports', '1', 'submit'), report);
+ const savedReport = await submitReport(1, report);
+ expect(savedReport).toEqual(report);
+ });
+ });
+
+ describe('saveReport', () => {
+ it('returns the report', async () => {
+ const report = { id: 1 };
+ fetchMock.put(join('api', 'activity-reports', '1'), report);
+ const savedReport = await saveReport(1, report);
+ expect(savedReport).toEqual(report);
+ });
+ });
+
+ describe('resetToDraft', () => {
+ it('calls reset and returns the result', async () => {
+ const report = { id: 1 };
+ fetchMock.put(join('api', 'activity-reports', '1', 'reset'), report);
+ const savedReport = await resetToDraft(1);
+ expect(savedReport).toEqual(report);
+ });
+ });
+
+ describe('reviewReport', () => {
+ it('returns the report', async () => {
+ const report = { id: 1 };
+ fetchMock.put(join('api', 'activity-reports', '1', 'review'), report);
+ const savedReport = await reviewReport(1, report);
+ expect(savedReport).toEqual(report);
+ });
+ });
+});
diff --git a/frontend/src/pages/ActivityReport/Pages/Review/FileReviewItem.js b/frontend/src/pages/ActivityReport/Pages/Review/FileReviewItem.js
new file mode 100644
index 0000000000..1b1a591639
--- /dev/null
+++ b/frontend/src/pages/ActivityReport/Pages/Review/FileReviewItem.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Grid } from '@trussworks/react-uswds';
+
+const APPROVED = 'APPROVED';
+
+const FileReviewItem = ({ filename, url, status }) => {
+ const approved = status === APPROVED;
+ return (
+
+
+ {filename}
+
+
+
+ {approved
+ &&
Download}
+ {!approved
+ &&
Not Approved
}
+
+
+
+ );
+};
+
+FileReviewItem.propTypes = {
+ filename: PropTypes.string.isRequired,
+ status: PropTypes.string.isRequired,
+ url: PropTypes.string.isRequired,
+};
+
+export default FileReviewItem;
diff --git a/frontend/src/pages/ActivityReport/Pages/Review/ReviewItem.js b/frontend/src/pages/ActivityReport/Pages/Review/ReviewItem.js
new file mode 100644
index 0000000000..dc287d97f2
--- /dev/null
+++ b/frontend/src/pages/ActivityReport/Pages/Review/ReviewItem.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import _ from 'lodash';
+import { useFormContext } from 'react-hook-form';
+
+const ReviewItem = ({ label, name, path }) => {
+ const { watch } = useFormContext();
+ const value = watch(name);
+ let values = value;
+
+ if (!Array.isArray(value)) {
+ values = [value];
+ }
+
+ if (path) {
+ values = values.map((v) => _.get(v, path));
+ }
+
+ const emptySelector = value && value.length > 0 ? '' : 'smart-hub-review-item--empty';
+ const classes = ['margin-top-1', emptySelector].filter((x) => x !== '').join(' ');
+
+ return (
+
+
+ {label}
+
+
+ {values.map((v, index) => (
+
+ {Number.isNaN(v) ? '' : v}
+
+ ))}
+
+
+ );
+};
+
+ReviewItem.propTypes = {
+ label: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ path: PropTypes.string,
+};
+
+ReviewItem.defaultProps = {
+ path: '',
+};
+
+export default ReviewItem;
diff --git a/frontend/src/pages/ActivityReport/Pages/Review/ReviewPage.js b/frontend/src/pages/ActivityReport/Pages/Review/ReviewPage.js
new file mode 100644
index 0000000000..0be343f134
--- /dev/null
+++ b/frontend/src/pages/ActivityReport/Pages/Review/ReviewPage.js
@@ -0,0 +1,53 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { some } from 'lodash';
+import { useFormContext } from 'react-hook-form';
+
+import Section from './ReviewSection';
+import ReviewItem from './ReviewItem';
+
+const ReviewPage = ({ sections, path }) => {
+ const { getValues } = useFormContext();
+ return (
+ <>
+ {sections.map((section) => {
+ const names = section.items.map((item) => item.name);
+ const values = getValues(names);
+ const isEmpty = !some(values, (value) => value && value.length);
+ return (
+
+ {section.items.map((item) => (
+
+ ))}
+
+ );
+ })}
+ >
+ );
+};
+
+ReviewPage.propTypes = {
+ path: PropTypes.string.isRequired,
+ sections: PropTypes.arrayOf(PropTypes.shape({
+ title: PropTypes.string,
+ anchor: PropTypes.string,
+ items: PropTypes.arrayOf(PropTypes.shape({
+ label: PropTypes.string,
+ path: PropTypes.string,
+ name: PropTypes.string,
+ })),
+ })).isRequired,
+};
+
+export default ReviewPage;
diff --git a/frontend/src/pages/ActivityReport/Pages/Review/ReviewSection.js b/frontend/src/pages/ActivityReport/Pages/Review/ReviewSection.js
new file mode 100644
index 0000000000..bfb3412e5a
--- /dev/null
+++ b/frontend/src/pages/ActivityReport/Pages/Review/ReviewSection.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { HashLink } from 'react-router-hash-link';
+
+const Section = ({
+ title, children, basePath, anchor, hidePrint,
+}) => {
+ const classes = [
+ 'smart-hub-review-section',
+ 'margin-top-2 desktop:margin-top-0',
+ hidePrint ? 'smart-hub-review-section--empty no-print' : '',
+ 'margin-bottom-3',
+ ].filter((x) => x).join(' ');
+
+ return (
+
+
+
+ {title}
+
+
+
+ Edit
+
+
+
+ {children}
+
+ );
+};
+
+Section.propTypes = {
+ hidePrint: PropTypes.bool.isRequired,
+ title: PropTypes.string.isRequired,
+ anchor: PropTypes.string.isRequired,
+ children: PropTypes.node.isRequired,
+ basePath: PropTypes.string.isRequired,
+};
+
+export default Section;
diff --git a/frontend/src/pages/ActivityReport/Pages/Review/__tests__/FileReviewItem.js b/frontend/src/pages/ActivityReport/Pages/Review/__tests__/FileReviewItem.js
new file mode 100644
index 0000000000..69b6ca2736
--- /dev/null
+++ b/frontend/src/pages/ActivityReport/Pages/Review/__tests__/FileReviewItem.js
@@ -0,0 +1,35 @@
+/* eslint-disable react/jsx-props-no-spreading */
+import React from 'react';
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/react';
+
+import FileReviewItem from '../FileReviewItem';
+
+// eslint-disable-next-line react/prop-types
+const RenderFileReviewItem = ({ status = 'APPROVED' }) => (
+
+);
+
+describe('ReviewPage', () => {
+ it('displays the filename', async () => {
+ render();
+ const item = await screen.findByText('filename');
+ expect(item).toBeVisible();
+ });
+
+ it('displays the download link', async () => {
+ render();
+ const link = await screen.findByRole('link');
+ expect(link).toHaveAttribute('href', 'http://localhost:3000');
+ });
+
+ it('displays "not approved" if the file has not been approved', async () => {
+ render();
+ const status = await screen.findByText('Not Approved');
+ expect(status).toBeVisible();
+ });
+});
diff --git a/frontend/src/pages/ActivityReport/Pages/Review/__tests__/reviewItem.js b/frontend/src/pages/ActivityReport/Pages/Review/__tests__/ReviewPage.js
similarity index 77%
rename from frontend/src/pages/ActivityReport/Pages/Review/__tests__/reviewItem.js
rename to frontend/src/pages/ActivityReport/Pages/Review/__tests__/ReviewPage.js
index f7279c8c9f..cbc0182181 100644
--- a/frontend/src/pages/ActivityReport/Pages/Review/__tests__/reviewItem.js
+++ b/frontend/src/pages/ActivityReport/Pages/Review/__tests__/ReviewPage.js
@@ -1,8 +1,10 @@
+/* eslint-disable react/jsx-props-no-spreading */
import React from 'react';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
-import accordionItem from '../reviewItem';
+import { FormProvider, useForm } from 'react-hook-form';
+import ReviewPage from '../ReviewPage';
const sections = [
{
@@ -37,13 +39,28 @@ const values = {
object: { test: 'test' },
};
-describe('AccordionItem', () => {
+const RenderReviewPage = () => {
+ const hookForm = useForm({
+ mode: 'onChange',
+ defaultValues: values,
+ shouldUnregister: false,
+ });
+ return (
+
+
+
+
+
+ );
+};
+
+describe('ReviewPage', () => {
beforeEach(() => {
- const item = accordionItem('id', 'title', sections, values);
render(
-
- {item.content}
- ,
+ ,
);
});
diff --git a/frontend/src/pages/ActivityReport/Pages/Review/reviewItem.js b/frontend/src/pages/ActivityReport/Pages/Review/reviewItem.js
deleted file mode 100644
index 40ad70907a..0000000000
--- a/frontend/src/pages/ActivityReport/Pages/Review/reviewItem.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- Review items for activity report review section
-
- These items are for use with the USWDS accordion component. This module is split in the
- following way:
-
- 1. Review Item: this represents a page in the activity report, and contains multiple
- review sections.
- 2. Sections: this represents a "fieldset" in the report, or the major sections within a page
- of the activity report, they contain items.
- 3. Items: these are the individual form fields for the activity report
-*/
-import PropTypes from 'prop-types';
-import { HashLink } from 'react-router-hash-link';
-import _ from 'lodash';
-import React from 'react';
-
-const itemType = {
- label: PropTypes.string.isRequired,
- path: PropTypes.string,
- value: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number,
- PropTypes.shape({}),
- PropTypes.arrayOf(PropTypes.string),
- PropTypes.arrayOf(PropTypes.shape({})),
- ]),
-};
-
-const sectionType = {
- title: PropTypes.string.isRequired,
- anchor: PropTypes.string.isRequired,
- items: PropTypes.arrayOf(PropTypes.shape(itemType)).isRequired,
- basePath: PropTypes.string.isRequired,
-};
-
-const Section = ({
- title, items, basePath, anchor,
-}) => {
- const isEmpty = !items.some(({ value }) => value && value.length);
- const classes = [
- 'smart-hub-review-section',
- 'margin-top-2 desktop:margin-top-0',
- isEmpty ? 'smart-hub-review-section--empty no-print' : '',
- 'margin-bottom-3',
- ].filter((x) => x).join(' ');
-
- return (
-
-
-
- {title}
-
-
-
- Edit
-
-
-
- {items.map(({ label, value, path }) => (
-
- ))}
-
- );
-};
-
-Section.propTypes = sectionType;
-
-const Item = ({ label, value, path }) => {
- let values = value;
-
- if (!Array.isArray(value)) {
- values = [value];
- }
-
- if (path) {
- values = values.map((v) => _.get(v, path));
- }
-
- const emptySelector = value && value.length > 0 ? '' : 'smart-hub-review-item--empty';
- const classes = ['margin-top-1', emptySelector].filter((x) => x !== '').join(' ');
-
- return (
-
-
- {label}
-
-
- {values.map((v, index) => (
-
- {Number.isNaN(v) ? '' : v}
-
- ))}
-
-
- );
-};
-
-Item.propTypes = itemType;
-
-Item.defaultProps = {
- path: '',
- value: null,
-};
-
-const ReviewItem = ({ formData, sections, basePath }) => (
- <>
- {sections.map((section) => (
- {
- const { path, label } = item;
- const value = _.get(formData, item.name, '');
- return {
- label,
- value,
- path,
- };
- })}
- />
- ))}
- >
-);
-
-ReviewItem.propTypes = {
- formData: PropTypes.shape({}).isRequired,
- basePath: PropTypes.string.isRequired,
- sections: PropTypes.arrayOf(
- PropTypes.shape({
- title: PropTypes.string.isRequired,
- anchor: PropTypes.string.isRequired,
- items: PropTypes.arrayOf(PropTypes.shape(itemType)).isRequired,
- }),
- ).isRequired,
-};
-
-/*
- This is the format the USWDS accordion expects of accordion items
-*/
-export default (id, title, sections, formData) => ({
- id,
- title,
- content: ,
-});
diff --git a/frontend/src/pages/ActivityReport/Pages/__tests__/topicsResources.js b/frontend/src/pages/ActivityReport/Pages/__tests__/topicsResources.js
new file mode 100644
index 0000000000..8a965dc01b
--- /dev/null
+++ b/frontend/src/pages/ActivityReport/Pages/__tests__/topicsResources.js
@@ -0,0 +1,45 @@
+/* eslint-disable react/jsx-props-no-spreading */
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/react';
+import React from 'react';
+import { FormProvider, useForm } from 'react-hook-form';
+import { Router } from 'react-router-dom';
+import { createMemoryHistory } from 'history';
+
+import topics from '../topicsResources';
+
+// eslint-disable-next-line react/prop-types
+const RenderTopicsResourcesReview = ({ data }) => {
+ const history = createMemoryHistory();
+ const hookForm = useForm({
+ mode: 'onChange',
+ defaultValues: data,
+ });
+ // eslint-disable-next-line react/prop-types
+ return (
+
+
+ {topics.reviewSection()}
+
+
+ );
+};
+
+describe('Topics & resources', () => {
+ const data = {
+ otherResources: [{ originalFileName: 'other', url: { url: 'http://localhost/other' }, status: 'APPROVED' }],
+ attachments: [{ originalFileName: 'attachment', url: { url: 'http://localhost/attachment' }, status: 'APPROVED' }],
+ resourcesUsed: 'resources',
+ topics: 'topics',
+ };
+
+ describe('review page', () => {
+ it('displays attachments and other resources', async () => {
+ render();
+ expect(await screen.findByText('other')).toBeVisible();
+ expect(await screen.findByText('attachment')).toBeVisible();
+ expect(await screen.findByText('resources')).toBeVisible();
+ expect(await screen.findByText('topics')).toBeVisible();
+ });
+ });
+});
diff --git a/frontend/src/pages/ActivityReport/Pages/activitySummary.js b/frontend/src/pages/ActivityReport/Pages/activitySummary.js
index 91f2fb4dce..337d501624 100644
--- a/frontend/src/pages/ActivityReport/Pages/activitySummary.js
+++ b/frontend/src/pages/ActivityReport/Pages/activitySummary.js
@@ -8,6 +8,7 @@ import {
Fieldset, Radio, Grid, TextInput, Checkbox,
} from '@trussworks/react-uswds';
+import ReviewPage from './Review/ReviewPage';
import DatePicker from '../../../components/DatePicker';
import MultiSelect from '../../../components/MultiSelect';
import {
@@ -433,11 +434,15 @@ const sections = [
},
];
+const ReviewSection = () => (
+
+);
+
export default {
position: 1,
label: 'Activity summary',
path: 'activity-summary',
- sections,
+ reviewSection: () => ,
review: false,
render: (additionalData) => {
const { recipients, collaborators } = additionalData;
diff --git a/frontend/src/pages/ActivityReport/Pages/goalsObjectives.js b/frontend/src/pages/ActivityReport/Pages/goalsObjectives.js
index 0c948a064a..3e7e9d10da 100644
--- a/frontend/src/pages/ActivityReport/Pages/goalsObjectives.js
+++ b/frontend/src/pages/ActivityReport/Pages/goalsObjectives.js
@@ -6,6 +6,7 @@ import {
} from '@trussworks/react-uswds';
import useDeepCompareEffect from 'use-deep-compare-effect';
import { useFormContext } from 'react-hook-form';
+import ReviewPage from './Review/ReviewPage';
import GoalPicker from './components/GoalPicker';
import { getGoals } from '../../../fetchers/activityReports';
@@ -83,12 +84,16 @@ const sections = [
},
];
+const ReviewSection = () => (
+
+);
+
export default {
position: 3,
label: 'Goals and objectives',
path: 'goals-objectives',
review: false,
- sections,
+ reviewSection: () => ,
render: (additionalData, formData) => {
const recipients = formData.activityRecipients || [];
const { activityRecipientType } = formData;
diff --git a/frontend/src/pages/ActivityReport/Pages/index.js b/frontend/src/pages/ActivityReport/Pages/index.js
index dbec25836e..005ffd9e5c 100644
--- a/frontend/src/pages/ActivityReport/Pages/index.js
+++ b/frontend/src/pages/ActivityReport/Pages/index.js
@@ -4,7 +4,6 @@ import topicsResources from './topicsResources';
import nextSteps from './nextSteps';
import goalsObjectives from './goalsObjectives';
import ReviewSubmit from './Review';
-import reviewItem from './Review/reviewItem';
/*
Note these are not react nodes but objects used by the navigator to render out
@@ -48,7 +47,11 @@ const reviewPage = {
approvingManager={approvingManager}
onResetToDraft={onResetToDraft}
reviewItems={
- pages.map((p) => reviewItem(p.path, p.label, p.sections, formData))
+ pages.map((p) => ({
+ id: p.path,
+ title: p.label,
+ content: p.reviewSection(),
+ }))
}
formData={formData}
pages={allPages}
diff --git a/frontend/src/pages/ActivityReport/Pages/nextSteps.js b/frontend/src/pages/ActivityReport/Pages/nextSteps.js
index 4f05a4d7a5..8b3a74aaf5 100644
--- a/frontend/src/pages/ActivityReport/Pages/nextSteps.js
+++ b/frontend/src/pages/ActivityReport/Pages/nextSteps.js
@@ -1,5 +1,6 @@
import React from 'react';
import { Helmet } from 'react-helmet';
+import ReviewPage from './Review/ReviewPage';
const NextSteps = () => (
<>
@@ -16,12 +17,16 @@ NextSteps.propTypes = {};
const sections = [];
+const ReviewSection = () => (
+
+);
+
export default {
position: 4,
label: 'Next steps',
path: 'next-steps',
review: false,
- sections,
+ reviewSection: () => ,
render: () => (
),
diff --git a/frontend/src/pages/ActivityReport/Pages/topicsResources.js b/frontend/src/pages/ActivityReport/Pages/topicsResources.js
index 6f3ff4aa52..f49ceabb00 100644
--- a/frontend/src/pages/ActivityReport/Pages/topicsResources.js
+++ b/frontend/src/pages/ActivityReport/Pages/topicsResources.js
@@ -1,13 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
-
import { Controller, useFormContext } from 'react-hook-form';
import { Helmet } from 'react-helmet';
-
import {
Fieldset, Label, TextInput,
} from '@trussworks/react-uswds';
+import { isUndefined } from 'lodash';
+import Section from './Review/ReviewSection';
+import ReviewItem from './Review/ReviewItem';
+import FileReviewItem from './Review/FileReviewItem';
import MultiSelect from '../../../components/MultiSelect';
import FileUploader from '../../../components/FileUploader';
import FormItem from '../../../components/FormItem';
@@ -91,36 +93,77 @@ TopicsResources.propTypes = {
reportId: PropTypes.node.isRequired,
};
-const sections = [
- {
- title: 'Topics covered',
- anchor: 'topics-resources',
- items: [
- { label: 'Topics', name: 'topics' },
- ],
- },
- {
- title: 'Resources',
- anchor: 'resources',
- items: [
- { label: 'Resources used', name: 'resourcesUsed' },
- { label: 'Other resources', name: 'otherResources', path: 'originalFileName' },
- ],
- },
- {
- title: 'Attachments',
- anchor: 'attachments',
- items: [
- { label: 'Attachments', name: 'attachments', path: 'originalFileName' },
- ],
- },
-];
+const ReviewSection = () => {
+ const { watch } = useFormContext();
+ const {
+ otherResources,
+ resourcesUsed,
+ attachments,
+ topics: formTopics,
+ } = watch();
+
+ const hasOtherResources = otherResources && otherResources.length > 0;
+ const hasAttachments = attachments && attachments.length > 0;
+
+ return (
+ <>
+
+
+
+ {otherResources.map((file) => (
+
+ ))}
+
+
+ {attachments.map((file) => (
+
+ ))}
+
+ >
+ );
+};
export default {
position: 2,
label: 'Topics and resources',
path: 'topics-resources',
- sections,
+ reviewSection: () => ,
review: false,
render: (additionalData, formData, reportId) => (
{
describe('File Upload Handlers happy path', () => {
beforeEach(() => {
uploadFile.mockReset();
+ getPresignedURL.mockReset();
});
it('tests a file upload', async () => {
ActivityReportPolicy.mockImplementation(() => ({
canUpdate: () => true,
}));
+ uploadFile.mockResolvedValue({ key: 'key' });
await request(app)
.post('/api/files')
.field('reportId', report.dataValues.id)
diff --git a/src/services/activityReports.test.js b/src/services/activityReports.test.js
index edf8e47f8f..5f4dc31ccb 100644
--- a/src/services/activityReports.test.js
+++ b/src/services/activityReports.test.js
@@ -253,8 +253,8 @@ describe('Activity Reports DB service', () => {
it('retrieves all recipients when not specifying region', async () => {
const recipients = await possibleRecipients();
- // 11 From db being seeded + 1 that we create for this test suite = 12
- expect(recipients.grants.length).toBe(12);
+ const grantees = await Grantee.findAll();
+ expect(recipients.grants.length).toBe(grantees.length);
});
});
});