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

Draft alert and report bug fixes #390

Merged
merged 10 commits into from
Mar 16, 2021
47 changes: 47 additions & 0 deletions frontend/src/components/DismissingComponentWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';

function DismissingComponentWrapper({
shown, timeVisibleInSec, hideFromScreenReader, children, updateShown,
}) {
useEffect(() => {
let id;
if (shown) {
id = setTimeout(() => {
updateShown(false);
}, [timeVisibleInSec * 1000]);
} else {
clearTimeout(id);
}

return () => {
clearTimeout(id);
};
}, [shown]);

return (
<>
{shown && (
<div aria-hidden={hideFromScreenReader}>
{children}
</div>
)}
</>
);
}

DismissingComponentWrapper.propTypes = {
shown: PropTypes.bool.isRequired,
timeVisibleInSec: PropTypes.number,
hideFromScreenReader: PropTypes.bool,
updateShown: PropTypes.func.isRequired,
children: PropTypes.node,
};

DismissingComponentWrapper.defaultProps = {
timeVisibleInSec: 30,
hideFromScreenReader: true,
children: null,
};

export default DismissingComponentWrapper;
33 changes: 26 additions & 7 deletions frontend/src/components/Navigator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ import {
} from './constants';
import SideNav from './components/SideNav';
import NavigatorHeader from './components/NavigatorHeader';
import DismissingComponentWrapper from '../DismissingComponentWrapper';

function Navigator({
editable,
formData,
updateFormData,
initialLastUpdated,
pages,
onFormSubmit,
onReview,
Expand All @@ -42,10 +42,13 @@ function Navigator({
reportId,
updatePage,
reportCreator,
lastSaveTime,
updateLastSaveTime,
showValidationErrors,
updateShowValidationErrors,
}) {
const [errorMessage, updateErrorMessage] = useState();
const [lastSaveTime, updateLastSaveTime] = useState(initialLastUpdated);
const [showValidationErrors, updateShowValidationErrors] = useState(false);
const [showSavedDraft, updateShowSavedDraft] = useState(false);
const page = pages.find((p) => p.path === currentPage);

const hookForm = useForm({
Expand All @@ -72,8 +75,8 @@ function Navigator({

const currentPageState = pageState[page.position];
const isComplete = page.isPageComplete ? page.isPageComplete(getValues()) : isValid;

const newPageState = { ...pageState };

if (isComplete) {
newPageState[page.position] = COMPLETE;
} else if (currentPageState === COMPLETE) {
Expand Down Expand Up @@ -107,6 +110,7 @@ function Navigator({
await onSaveForm();
if (index !== page.position) {
updatePage(index);
updateShowSavedDraft(false);
}
};

Expand Down Expand Up @@ -198,10 +202,22 @@ function Navigator({
{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(); }}>Save draft</Button>
<Button type="button" onClick={async () => { await onSaveForm(); updateShowSavedDraft(true); }}>Save draft</Button>
<Button className="margin-left-auto margin-right-0" type="button" onClick={onContinue}>Save & Continue</Button>
</div>
</Form>
<DismissingComponentWrapper
shown={showSavedDraft}
updateShown={updateShowSavedDraft}
>
{lastSaveTime && (
<Alert className="margin-top-3 maxw-mobile-lg" noIcon slim type="success">
Draft saved on
{' '}
{lastSaveTime.format('MM/DD/YYYY [at] h:mm a z')}
</Alert>
)}
</DismissingComponentWrapper>
</Container>
)}
</div>
Expand All @@ -219,7 +235,10 @@ Navigator.propTypes = {
pageState: PropTypes.shape({}),
}).isRequired,
updateFormData: PropTypes.func.isRequired,
initialLastUpdated: PropTypes.instanceOf(moment),
lastSaveTime: PropTypes.instanceOf(moment),
updateLastSaveTime: PropTypes.func.isRequired,
showValidationErrors: PropTypes.bool.isRequired,
updateShowValidationErrors: PropTypes.func.isRequired,
onFormSubmit: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
onReview: PropTypes.func.isRequired,
Expand Down Expand Up @@ -247,7 +266,7 @@ Navigator.propTypes = {
Navigator.defaultProps = {
additionalData: {},
autoSaveInterval: 1000 * 60 * 2,
initialLastUpdated: null,
lastSaveTime: null,
reportCreator: {
name: null,
role: null,
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/pages/ActivityReport/Pages/activitySummary.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,20 @@ const ActivitySummary = ({
const participants = nonGranteeSelected ? nonGranteeParticipants : granteeParticipants;

useEffect(() => {
if (previousActivityRecipientType.current !== activityRecipientType) {
setValue('activityRecipients', []);
setValue('participants', []);
setValue('programTypes', []);
if (previousActivityRecipientType.current !== activityRecipientType
&& previousActivityRecipientType.current !== ''
&& previousActivityRecipientType.current !== null) {
setValue('activityRecipients', [], { shouldValidate: true });
setValue('participants', [], { shouldValidate: true });
setValue('programTypes', [], { shouldValidate: true });
// Goals and objectives (page 3) has required fields when the recipient
// type is grantee, so we need to make sure that page is set as "not started"
// when recipient type is changed and we need to clear out any previously
// selected goals
setValue('goals', []);
setValue('pageState', { ...pageState, 3: NOT_STARTED });
previousActivityRecipientType.current = activityRecipientType;
}
previousActivityRecipientType.current = activityRecipientType;
}, [activityRecipientType, setValue]);

const renderCheckbox = (name, value, label, requiredMessage) => (
Expand Down
11 changes: 8 additions & 3 deletions frontend/src/pages/ActivityReport/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ function ActivityReport({
const [initialAdditionalData, updateAdditionalData] = useState({});
const [approvingManager, updateApprovingManager] = useState(false);
const [editable, updateEditable] = useState(false);
const [initialLastUpdated, updateInitialLastUpdated] = useState();
const [lastSaveTime, updateLastSaveTime] = useState();
const [showValidationErrors, updateShowValidationErrors] = useState(false);
const reportId = useRef();

const showLastUpdatedTime = (location.state && location.state.showLastUpdatedTime) || false;
Expand Down Expand Up @@ -141,7 +142,7 @@ function ActivityReport({
updateEditable(canWriteReport);

if (showLastUpdatedTime) {
updateInitialLastUpdated(moment(report.updatedAt));
updateLastSaveTime(moment(report.updatedAt));
}

updateError();
Expand Down Expand Up @@ -256,10 +257,14 @@ function ActivityReport({
</Grid>
</Grid>
<Navigator
key={currentPage}
editable={editable}
updatePage={updatePage}
reportCreator={reportCreator}
initialLastUpdated={initialLastUpdated}
showValidationErrors={showValidationErrors}
updateShowValidationErrors={updateShowValidationErrors}
lastSaveTime={lastSaveTime}
updateLastSaveTime={updateLastSaveTime}
reportId={reportId.current}
currentPage={currentPage}
additionalData={initialAdditionalData}
Expand Down
19 changes: 19 additions & 0 deletions src/migrations/20210315234158-seed-non-grantee-recipients.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = {
up: (queryInterface) => queryInterface.bulkInsert('NonGrantees', [
{ name: 'CCDF / Child Care Administrator' },
{ name: 'Head Start Collaboration Office' },
{ name: 'QRIS System' },
{ name: 'Regional Head Start Association' },
{ name: 'Regional TTA/Other Specialists' },
{ name: 'State CCR&R' },
{ name: 'State Early Learning Standards' },
{ name: 'State Education System' },
{ name: 'State Health System' },
{ name: 'State Head Start Association' },
{ name: 'State Professional Development / Continuing Education' },
],
{
ignoreDuplicates: true,
}),
down: (queryInterface) => queryInterface.bulkDelete('Regions', null, {}),
};
45 changes: 0 additions & 45 deletions src/seeders/20210107170250-nonGrantees.js

This file was deleted.

6 changes: 5 additions & 1 deletion src/services/activityReports.js
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,14 @@ export async function setStatus(report, status) {
* @returns {*} Grants and Non grantees
*/
export async function possibleRecipients(regionId) {
const where = regionId ? { regionId } : undefined;
let where = { status: 'Active' };
if (regionId) {
where = { ...where, regionId };
}

const grants = await Grantee.findAll({
attributes: ['id', 'name'],
order: ['name'],
include: [{
where,
model: Grant,
Expand Down
5 changes: 2 additions & 3 deletions src/services/activityReports.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe('Activity Reports DB service', () => {
grantee = await Grantee.create({ id: RECIPIENT_ID, name: 'grantee', regionId: 17 });
await Region.create({ name: 'office 17', id: 17 });
await Grant.create({
id: RECIPIENT_ID, number: 1, granteeId: grantee.id, regionId: 17,
id: RECIPIENT_ID, number: 1, granteeId: grantee.id, regionId: 17, status: 'Active',
});
await NonGrantee.create({ id: RECIPIENT_ID, name: 'nonGrantee' });
});
Expand Down Expand Up @@ -454,8 +454,7 @@ describe('Activity Reports DB service', () => {

it('retrieves all recipients when not specifying region', async () => {
const recipients = await possibleRecipients();
const grantees = await Grantee.findAll();
expect(recipients.grants.length).toBe(grantees.length);
expect(recipients.grants.length).toBe(11);
});
});
});