Skip to content

Commit

Permalink
Merge pull request #334 from adhocteam/main
Browse files Browse the repository at this point in the history
Design added to login page, Add report "readOnly" mode and Form navigation update
  • Loading branch information
rahearn authored Mar 2, 2021
2 parents 79eaf80 + 90a7211 commit 506d06a
Show file tree
Hide file tree
Showing 31 changed files with 611 additions and 231 deletions.
21 changes: 21 additions & 0 deletions docs/openapi/paths/activity-reports/reset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
put:
tags:
- activity-reports
summary: Reset activity report to draft
description: >
Activity reports are not editable when submitted for approval. This
endpoint allows a user to reset a report back to draft mode so that
the report can be edited.
parameters:
- in: path
name: activityReportId
required: true
schema:
type: number
responses:
200:
description: The report that now has a status of "draft"
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 @@ -26,5 +26,7 @@
$ref: './activity-reports/submit.yaml'
'/activity-reports/{activityReportId}/review':
$ref: './activity-reports/review.yaml'
'/activity-reports/{activityReportId}/reset':
$ref: './activity-reports/reset.yaml'
'/files':
$ref: './files.yaml'
12 changes: 12 additions & 0 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ body {
background-color: #f8f8f8;
}

.smart-hub-bg-blue-primary {
background-color: #0166ab;
}

.smart-hub-border-blue-primary {
border-color: #0166ab;
}

.smart-hub-maxw-placard {
max-width: 34.25rem;
}

.height-12 {
height: 6.5em;
}
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/__tests__/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import fetchMock from 'fetch-mock';
import App from '../App';

describe('App', () => {
const loginText = 'Log In with HSES';

afterEach(() => fetchMock.restore());
const userUrl = join('api', 'user');
const logoutUrl = join('api', 'logout');
Expand All @@ -27,7 +29,7 @@ describe('App', () => {
it('can log the user out when "logout" is pressed', async () => {
const logout = await screen.findByText('Logout');
fireEvent.click(logout);
expect(await screen.findByText('HSES Login')).toBeVisible();
expect(await screen.findByText(loginText)).toBeVisible();
expect(await screen.findByText('Logout Successful')).toBeVisible();
});
});
Expand All @@ -39,7 +41,7 @@ describe('App', () => {
});

it('displays the login button', async () => {
expect(await screen.findByText('HSES Login')).toBeVisible();
expect(await screen.findByText(loginText)).toBeVisible();
});
});

Expand All @@ -50,7 +52,7 @@ describe('App', () => {
});

it('displays the login button for now. in the future this should show the "request permissions" UI', async () => {
expect(await screen.findByText('HSES Login')).toBeVisible();
expect(await screen.findByText(loginText)).toBeVisible();
});
});
});
16 changes: 5 additions & 11 deletions frontend/src/components/Navigator/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const pages = [
label: 'first page',
review: false,
render: () => (
<Input name="first" />
<Input name="first" required />
),
},
{
Expand All @@ -38,7 +38,7 @@ const pages = [
label: 'second page',
review: false,
render: () => (
<Input name="second" />
<Input name="second" required />
),
},
{
Expand Down Expand Up @@ -70,6 +70,7 @@ describe('Navigator', () => {
const renderNavigator = (currentPage = 'first', onSubmit = () => {}, onSave = () => {}, updatePage = () => {}, updateForm = () => {}) => {
render(
<Navigator
editable
reportId={1}
submitted={false}
formData={initialData}
Expand Down Expand Up @@ -108,26 +109,19 @@ describe('Navigator', () => {
await waitFor(() => expect(onSubmit).toHaveBeenCalled());
});

it('shows an error message if the form is not valid', async () => {
renderNavigator('third');
const button = await screen.findByRole('button', { name: 'Save & Continue' });
userEvent.click(button);
expect(await screen.findByTestId('alert')).toHaveTextContent('Please complete all required fields before submitting this report.');
});

it('onBack calls onUpdatePage', async () => {
const updatePage = jest.fn();
renderNavigator('third', () => {}, () => {}, updatePage);
const button = await screen.findByRole('button', { name: 'Back' });
userEvent.click(button);
expect(updatePage).toHaveBeenCalledWith(2);
await waitFor(() => expect(updatePage).toHaveBeenCalledWith(2));
});

it('calls onSave on navigation', async () => {
const updatePage = jest.fn();
const updateForm = jest.fn();
renderNavigator('second', () => {}, () => {}, updatePage, updateForm);
userEvent.click(screen.getByRole('button', { name: 'first page' }));
userEvent.click(await screen.findByRole('button', { name: 'first page' }));
await waitFor(() => expect(updateForm).toHaveBeenCalledWith({ ...initialData, second: null }));
await waitFor(() => expect(updatePage).toHaveBeenCalledWith(1));
});
Expand Down
64 changes: 40 additions & 24 deletions frontend/src/components/Navigator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
on the left hand side with each page of the form listed. Clicking on an item in the nav list will
display that item in the content section. The navigator keeps track of the "state" of each page.
*/
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { FormProvider, useForm } from 'react-hook-form';
import {
Expand All @@ -26,12 +26,14 @@ import SideNav from './components/SideNav';
import NavigatorHeader from './components/NavigatorHeader';

function Navigator({
editable,
formData,
updateFormData,
initialLastUpdated,
pages,
onFormSubmit,
onReview,
onResetToDraft,
currentPage,
additionalData,
onSave,
Expand All @@ -43,6 +45,7 @@ function Navigator({
}) {
const [errorMessage, updateErrorMessage] = useState();
const [lastSaveTime, updateLastSaveTime] = useState(initialLastUpdated);
const [showValidationErrors, updateShowValidationErrors] = useState(false);
const { pageState } = formData;
const page = pages.find((p) => p.path === currentPage);

Expand All @@ -53,60 +56,65 @@ function Navigator({

const {
formState,
handleSubmit,
getValues,
reset,
trigger,
} = hookForm;

const { isDirty, errors } = formState;

const { isDirty, errors, isValid } = formState;
const hasErrors = Object.keys(errors).length > 0;

const newNavigatorState = (completed) => {
useEffect(() => {
if (showValidationErrors && !page.review) {
trigger();
}
}, [page.review, trigger, showValidationErrors]);

const newNavigatorState = () => {
if (page.review) {
return pageState;
}

const newPageState = { ...pageState };
if (completed) {
if (isValid) {
newPageState[page.position] = COMPLETE;
} else {
newPageState[page.position] = isDirty ? IN_PROGRESS : pageState[page.position];
}
return newPageState;
};

const onSaveForm = async (completed) => {
const onSaveForm = async () => {
if (!editable) {
return;
}
const { status, ...values } = getValues();
const data = { ...formData, ...values, pageState: newNavigatorState(completed) };
const data = { ...formData, ...values, pageState: newNavigatorState() };

updateFormData(data);
try {
const result = await onSave(data);
if (result) {
updateLastSaveTime(moment());
updateErrorMessage();
}
await onSave(data);
updateLastSaveTime(moment());
updateErrorMessage();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
updateErrorMessage('Unable to save activity report');
}
};

const onUpdatePage = (index, completed) => {
const onUpdatePage = async (index) => {
const newIndex = index === page.position ? null : index;
const { status, ...values } = getValues();
const data = { ...formData, ...values, pageState: newNavigatorState(completed) };
updateFormData(data);
await onSaveForm();
updatePage(newIndex);
};

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

useInterval(() => {
onSaveForm(false);
onSaveForm();
}, autoSaveInterval);

// A new form page is being shown so we need to reset `react-hook-form` so validations are
Expand All @@ -117,7 +125,12 @@ function Navigator({

const navigatorPages = pages.map((p) => {
const current = p.position === page.position;
const stateOfPage = current ? IN_PROGRESS : pageState[p.position];

let stateOfPage = pageState[p.position];
if (stateOfPage !== COMPLETE) {
stateOfPage = current ? IN_PROGRESS : pageState[p.position];
}

const state = p.review ? formData.status : stateOfPage;
return {
label: p.label,
Expand Down Expand Up @@ -149,9 +162,11 @@ function Navigator({
additionalData,
onReview,
approvingManager,
onResetToDraft,
onSaveForm,
navigatorPages,
reportCreator,
updateShowValidationErrors,
)}
{!page.review
&& (
Expand All @@ -166,14 +181,13 @@ function Navigator({
</Alert>
)}
<Form
onSubmit={handleSubmit(onContinue)}
className="smart-hub--form-large"
>
{page.render(additionalData, formData, reportId)}
<div className="display-flex">
<Button disabled={page.position <= 1} outline type="button" onClick={() => { onUpdatePage(page.position - 1); }}>Back</Button>
<Button type="button" onClick={() => { onSaveForm(false); }}>Save draft</Button>
<Button className="margin-left-auto margin-right-0" type="submit">Save & Continue</Button>
<Button type="button" onClick={() => { onSaveForm(); }}>Save draft</Button>
<Button className="margin-left-auto margin-right-0" type="button" onClick={onContinue}>Save & Continue</Button>
</div>
</Form>
</Container>
Expand All @@ -186,6 +200,8 @@ function Navigator({
}

Navigator.propTypes = {
onResetToDraft: PropTypes.func.isRequired,
editable: PropTypes.bool.isRequired,
formData: PropTypes.shape({
status: PropTypes.string,
pageState: PropTypes.shape({}),
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/fetchers/activityReports.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,9 @@ export const reviewReport = async (reportId, data) => {
const report = await put(url, data);
return report.json();
};

export const resetToDraft = async (reportId) => {
const url = join(activityReportUrl, reportId.toString(DECIMAL_BASE), 'reset');
const response = await put(url);
return response.json();
};
Binary file added frontend/src/images/eclkc-blocks-logo-156.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/src/images/eclkc-blocks-logo-78.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { REPORT_STATUSES } from '../../../../../../Constants';

const RenderApprover = ({
// eslint-disable-next-line react/prop-types
onFormReview, reviewed, valid, formData,
onFormReview, reviewed, formData,
}) => {
const hookForm = useForm({
mode: 'onChange',
Expand All @@ -21,16 +21,16 @@ const RenderApprover = ({
<Approver
onFormReview={onFormReview}
reviewed={reviewed}
valid={valid}
formData={formData}
/>
</FormProvider>
);
};

const renderReview = (status, onFormReview, reviewed, valid, notes = '') => {
const renderReview = (status, onFormReview, reviewed, notes = '') => {
const formData = {
approvingManager: { name: 'name' },
author: { name: 'user' },
managerNotes: notes,
additionalNotes: notes,
approvingManagerId: '1',
Expand All @@ -41,7 +41,6 @@ const renderReview = (status, onFormReview, reviewed, valid, notes = '') => {
status={status}
onFormReview={onFormReview}
reviewed={reviewed}
valid={valid}
formData={formData}
/>,
);
Expand All @@ -50,32 +49,33 @@ const renderReview = (status, onFormReview, reviewed, valid, notes = '') => {
describe('Approver review page', () => {
describe('when the report is submitted', () => {
it('displays the submit review component', async () => {
renderReview(REPORT_STATUSES.SUBMITTED, () => {}, false, true);
renderReview(REPORT_STATUSES.SUBMITTED, () => {}, false);
expect(await screen.findByText('Review and approve report')).toBeVisible();
});

it('allows the approver to submit a review', async () => {
const mockSubmit = jest.fn();
renderReview(REPORT_STATUSES.SUBMITTED, mockSubmit, true, true);
renderReview(REPORT_STATUSES.SUBMITTED, mockSubmit, true);
const dropdown = await screen.findByTestId('dropdown');
userEvent.selectOptions(dropdown, 'approved');
const button = await screen.findByRole('button');
userEvent.click(button);
const alert = await screen.findByTestId('alert');
expect(alert.textContent).toContain('Success');
const alerts = await screen.findAllByTestId('alert');
const success = alerts.find((alert) => alert.textContent.includes('Success'));
expect(success).toBeVisible();
expect(mockSubmit).toHaveBeenCalled();
});

it('handles empty notes', async () => {
renderReview(REPORT_STATUSES.SUBMITTED, () => {}, true, true);
renderReview(REPORT_STATUSES.SUBMITTED, () => {}, true);
const notes = await screen.findByLabelText('additionalNotes');
expect(notes.textContent).toContain('No creator notes');
});
});

describe('when the report is approved', () => {
it('displays the approved component', async () => {
renderReview(REPORT_STATUSES.APPROVED, () => {}, false, true);
renderReview(REPORT_STATUSES.APPROVED, () => {}, false);
expect(await screen.findByText('Report approved')).toBeVisible();
});
});
Expand Down
Loading

0 comments on commit 506d06a

Please sign in to comment.