Skip to content

Commit

Permalink
Merge pull request #2374 from HHS/mb/TTAHUB-2274-2274/tr-finalizations
Browse files Browse the repository at this point in the history
[TTAHUB-3373-3374] Post-launch tweaks to the Training Report
  • Loading branch information
thewatermethod authored Sep 19, 2024
2 parents cc4f4ab + ad718e4 commit 8202e84
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 17 deletions.
24 changes: 20 additions & 4 deletions frontend/src/components/ControlledDatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default function ControlledDatePicker({
isStartDate,
inputId,
endDate,
customValidationMessages,
required,
}) {
/**
Expand Down Expand Up @@ -54,20 +55,25 @@ export default function ControlledDatePicker({

const formattedValue = value ? moment(value, DATE_DISPLAY_FORMAT).format(DATEPICKER_VALUE_FORMAT) : '';

const {
beforeMessage,
afterMessage,
invalidMessage,
} = customValidationMessages;

// this is our custom validation function we pass to the hook form controller
function validate(v) {
const newValue = moment(v, DATE_DISPLAY_FORMAT);

if (!newValue.isValid()) {
return 'Enter valid date';
return invalidMessage || 'Enter valid date';
}

if (newValue.isBefore(min.moment)) {
return `Please enter a date after ${min.display}`;
return afterMessage || `Please enter a date after ${min.display}`;
}

if (newValue.isAfter(max.moment)) {
return `Please enter a date before ${max.display}`;
return beforeMessage || `Please enter a date before ${max.display}`;
}

return true;
Expand Down Expand Up @@ -135,6 +141,11 @@ ControlledDatePicker.propTypes = {
inputId: PropTypes.string.isRequired,
endDate: PropTypes.string,
required: PropTypes.bool,
customValidationMessages: PropTypes.shape({
beforeMessage: PropTypes.string,
afterMessage: PropTypes.string,
invalidMessage: PropTypes.string,
}),
};

ControlledDatePicker.defaultProps = {
Expand All @@ -145,4 +156,9 @@ ControlledDatePicker.defaultProps = {
setEndDate: () => {},
required: true,
value: '',
customValidationMessages: {
beforeMessage: '',
afterMessage: '',
invalidMessage: '',
},
};
57 changes: 54 additions & 3 deletions frontend/src/components/__tests__/ControlledDatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@
import '@testing-library/jest-dom';
import React from 'react';
import moment from 'moment';
import { render, screen, act } from '@testing-library/react';
import {
render, screen, act, fireEvent,
} from '@testing-library/react';
import { useForm } from 'react-hook-form';
import userEvent from '@testing-library/user-event';
import { Grid } from '@trussworks/react-uswds';
import { DATE_DISPLAY_FORMAT } from '../../Constants';

import ControlledDatePicker from '../ControlledDatePicker';

const defaultValidation = {
beforeMessage: '',
afterMessage: '',
invalidMessage: '',
};

describe('Controlled Date Picker', () => {
// eslint-disable-next-line react/prop-types
const TestDatePicker = ({ setEndDate }) => {
const TestDatePicker = ({ setEndDate, customValidationMessages = defaultValidation }) => {
const {
control, errors, handleSubmit, watch,
} = useForm({
Expand All @@ -34,9 +41,11 @@ describe('Controlled Date Picker', () => {
Start date
</label>
<ControlledDatePicker
customValidationMessages={customValidationMessages}
control={control}
name="startDate"
value={startDate}
minDate="09/01/2020"
setEndDate={setEndDate}
maxDate={endDate}
isStartDate
Expand All @@ -56,6 +65,7 @@ describe('Controlled Date Picker', () => {
minDate={startDate}
key="endDateKey"
inputId="endDate"
customValidationMessages={customValidationMessages}
/>
</Grid>
</Grid>
Expand Down Expand Up @@ -89,6 +99,47 @@ describe('Controlled Date Picker', () => {
expect(setEndDate).toHaveBeenCalled();
});

it('displays custom validation messages', async () => {
const setEndDate = jest.fn();
render(<TestDatePicker
setEndDate={setEndDate}
customValidationMessages={{
beforeMessage: 'Before message',
afterMessage: 'After message',
invalidMessage: 'Invalid message',
}}
/>);

const ed = await screen.findByRole('textbox', { name: /end date/i });
const sd = await screen.findByRole('textbox', { name: /start date/i });

act(() => {
userEvent.type(ed, '12/31/2020');
userEvent.type(sd, '01/03/2021');
});
expect(await screen.findByText('Invalid message')).toBeVisible();
act(() => {
userEvent.clear(sd);
userEvent.clear(ed);
});
userEvent.type(ed, '12/31/2020');
userEvent.type(sd, '08/31/2020');

fireEvent.blur(sd);

expect(await screen.findByText('After message')).toBeVisible();

act(() => {
userEvent.clear(sd);
userEvent.clear(ed);
});
userEvent.type(ed, '12/31/2020');
userEvent.type(sd, '01/01/2021');

fireEvent.blur(ed);
expect(await screen.findByText('Before message')).toBeVisible();
});

it('can set future start date and adjust end date', async () => {
const setEndDate = jest.fn();
render(<TestDatePicker setEndDate={setEndDate} />);
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/pages/SessionForm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ export default function SessionForm({ match }) {
}
try {
const session = await getSessionBySessionId(sessionId);
// eslint-disable-next-line max-len
const isPocFromSession = (session.event.pocIds || []).includes(user.id) && !isAdminUser;
resetFormData(hookForm.reset, session, isPocFromSession, isAdminUser);
reportId.current = session.id;
Expand Down Expand Up @@ -440,6 +441,8 @@ export default function SessionForm({ match }) {
}
};

const { event } = formData;

return (
<div className="smart-hub-training-report--session">
{ error
Expand Down Expand Up @@ -504,6 +507,7 @@ export default function SessionForm({ match }) {
status: formData.status,
pages: applicationPages,
isAdminUser,
event,
}}
formData={formData}
pages={applicationPages}
Expand Down
20 changes: 17 additions & 3 deletions frontend/src/pages/SessionForm/pages/sessionSummary.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const DEFAULT_RESOURCE = {
value: '',
};

const SessionSummary = ({ datePickerKey }) => {
const SessionSummary = ({ datePickerKey, event }) => {
const { setIsAppLoading, setAppLoadingText } = useContext(AppLoadingContext);

const {
Expand All @@ -70,6 +70,8 @@ const SessionSummary = ({ datePickerKey }) => {

const { id } = data;

const { startDate: eventStartDate } = (event || { data: { startDate: null } }).data;

const startDate = watch('startDate');
const endDate = watch('endDate');
const courses = watch('courses');
Expand Down Expand Up @@ -274,6 +276,10 @@ const SessionSummary = ({ datePickerKey }) => {
isStartDate
inputId="startDate"
endDate={endDate}
minDate={eventStartDate}
customValidationMessages={{
afterMessage: 'Date selected can\'t be before event start date.',
}}
/>
</FormItem>

Expand Down Expand Up @@ -628,6 +634,15 @@ const SessionSummary = ({ datePickerKey }) => {

SessionSummary.propTypes = {
datePickerKey: PropTypes.string.isRequired,
event: PropTypes.shape({
data: {
endDate: PropTypes.string,
},
}),
};

SessionSummary.defaultProps = {
event: null,
};

const fields = [...Object.keys(sessionSummaryFields), 'endDate', 'startDate'];
Expand Down Expand Up @@ -667,7 +682,7 @@ export default {
Alert,
) => (
<div className="padding-x-1">
<SessionSummary datePickerKey={datePickerKey} />
<SessionSummary datePickerKey={datePickerKey} event={additionalData.event} />
<Alert />
<div className="display-flex">
{
Expand All @@ -684,7 +699,6 @@ export default {
&& additionalData.status !== TRAINING_REPORT_STATUSES.COMPLETE && (
<Button id={`${path}-save-draft`} className="usa-button--outline" type="button" disabled={isAppLoading} onClick={onSaveDraft}>Save draft</Button>
)

}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/TrainingReports/components/EventCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function EventCard({
const isComplete = data.status === TRAINING_REPORT_STATUSES.COMPLETE;
const isNotCompleteOrSuspended = !isComplete && !isSuspended;

const canEditEvent = ((isOwnerOrCollaborator && !eventSubmitted && isNotCompleteOrSuspended)
const canEditEvent = ((isOwner && !eventSubmitted && isNotCompleteOrSuspended)
|| (hasAdminRights && isNotCompleteOrSuspended));
const canCreateSession = isNotCompleteOrSuspended && isOwnerOrCollaborator;
const canDeleteEvent = hasAdminRights && (data.status === TRAINING_REPORT_STATUSES.NOT_STARTED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ describe('EventCards', () => {
button.click(button);
});

it('collaborators cannot create training', () => {
it('collaborators cannot edit training', () => {
const collaboratorEvents = [{
id: 1,
ownerId: 3,
Expand Down Expand Up @@ -314,7 +314,7 @@ describe('EventCards', () => {
const button = screen.getByRole('button', { name: /actions for event TR-R01-1234/i });
button.click(button);
expect(screen.queryByText(/create session/i)).toBeInTheDocument();
expect(screen.queryByText(/edit event/i)).toBeInTheDocument();
expect(screen.queryByText(/edit event/i)).not.toBeInTheDocument();
expect(screen.queryByText(/view event/i)).toBeInTheDocument();
button.click(button);
});
Expand Down
2 changes: 1 addition & 1 deletion src/policies/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export default class EventReport {

// some handy & fun aliases
canEditEvent() {
return this.isAdmin() || this.isAuthor() || this.isCollaborator();
return this.isAdmin() || this.isAuthor();
}

canCreateSession() {
Expand Down
6 changes: 3 additions & 3 deletions src/policies/event.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,16 @@ describe('Event Report policies', () => {
expect(policy.canEditEvent()).toBe(true);
});

it('is true if the user is a collaborator', () => {
it('is false if the user is a collaborator', () => {
const eventRegion1 = createEvent({
ownerId: authorRegion1,
collaboratorIds: [authorRegion1Collaborator.id],
});
const policy = new EventReport(authorRegion1Collaborator, eventRegion1);
expect(policy.canEditEvent()).toBe(true);
expect(policy.canEditEvent()).toBe(false);
});

it('is true if the user is a poc', () => {
it('is false if the user is a poc', () => {
const eventRegion1 = createEvent({
ownerId: authorRegion1,
pocIds: [authorRegion1Collaborator.id],
Expand Down

0 comments on commit 8202e84

Please sign in to comment.