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

feat: [DHIS2-14334] edit enrollment date #3350

Merged
merged 44 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1804300
build: upgrade @dhis2/ui version to get calendar
superskip Jun 19, 2023
febf74b
feat: basic editable enrollment date component
superskip Jun 9, 2023
51c1ee3
feat: run rules after updating enrollment date
superskip Jun 9, 2023
3a7907b
feat: enrollment date edit button enable condition
superskip Jun 9, 2023
9907855
feat: auto generated event warning message
superskip Jun 9, 2023
732ee9f
feat: use CalendarInput component from @dhis2/ui
superskip Jun 19, 2023
2130e8f
fix: fix UI layout
superskip Jun 22, 2023
e3addc1
fix: always use input format YYYY-MM-DD in CalendarInput
superskip Jun 26, 2023
d275c0f
chore: add translation string
superskip Jun 26, 2023
da78f8b
chore: linter errors
superskip Jun 26, 2023
6c1d7c8
Merge branch 'master' into DHIS2-14334
superskip Jun 26, 2023
8afb762
fix: cypress tests
superskip Jun 26, 2023
7abb823
Merge branch 'master' into DHIS2-14334
superskip Jun 27, 2023
983988d
fix: missing comma
superskip Jun 27, 2023
3827ee9
refactor: remove unused class name
superskip Jun 27, 2023
7102bf3
fix: change color
superskip Jun 27, 2023
da5de9b
Merge branch 'master' into DHIS2-14334
superskip Jun 27, 2023
1743436
fix: update enrollment date in top bar
superskip Jun 30, 2023
a17138b
feat: simple error handling
superskip Aug 9, 2023
4da0ea7
fix: adjust width of input field
superskip Aug 9, 2023
a2b4f0d
refactor: rename `mayEditDate` -> `readOnlyMode`
superskip Aug 9, 2023
05e8d2b
feat: editable incident date
superskip Aug 10, 2023
dcaeaac
fix: update incident date in redux store
superskip Aug 10, 2023
ddbab8c
Merge branch 'master' into DHIS2-14334
superskip Aug 10, 2023
65b7f4d
fix: prevent UTC conversion on the selected date
superskip Aug 10, 2023
259e5a2
fix: failing cypress tests
superskip Aug 10, 2023
5f5eabf
fix: rename TEI "Launchpad McQuack" -> "Breaking TheGlass"
superskip Aug 10, 2023
40f279f
Merge branch 'TECH-1623' into DHIS2-14334
superskip Aug 10, 2023
d7f5aa3
fix: data-test only works for html elements
superskip Aug 11, 2023
0a078f3
Merge branch 'master' into DHIS2-14334
superskip Aug 11, 2023
71c4cc4
fix: remove double dependency on rules engine
superskip Aug 11, 2023
c983799
Merge branch 'master' into DHIS2-14334
superskip Aug 30, 2023
449577b
refactor: rename prop `error` -> `initError`
superskip Aug 30, 2023
62a2578
refactor: change to optimistic gui
superskip Aug 31, 2023
82d3e94
refactor: refetch enrollment from api rather than maintaining it locally
superskip Aug 31, 2023
6125de4
refactor: rename EnrollmentDate -> Date
superskip Aug 31, 2023
5ca6205
feat: use maxWidth
superskip Aug 31, 2023
3628742
fix: error in the date equality check
superskip Aug 31, 2023
ba03879
style: further renaming of `enrollmentDate` -> `date`
superskip Aug 31, 2023
3d81bc3
fix: display selected date immediately after saving
superskip Aug 31, 2023
a02ea61
refactor: move enrollment update logic into `useEnrollment`
superskip Sep 1, 2023
8f6cadc
fix: condition for displaying auto-generated events warning
superskip Sep 5, 2023
6d89fd0
fix: condition for displaying auto-generated events warning
superskip Sep 5, 2023
e3ad11d
fix: use same date format as server
superskip Sep 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ Then('the enrollment widget should be opened', () => {
});

Then('the user sees the enrollment date', () => {
cy.get('[data-test="widget-enrollment"]').within(() => {
cy.get('[data-test="widget-enrollment-enrollment-date"]').within(() => {
cy.get('[data-test="widget-enrollment-icon-calendar"]').should('exist');
cy.get('[data-test="widget-enrollment-enrollment-date"]')
.contains(`Date of enrollment ${getCurrentYear()}-08-01`)
cy.get('[data-test="widget-enrollment-date"]')
.contains(`Date of enrollment: ${getCurrentYear()}-08-01`)
.should('exist');
});
});

Then('the user sees the incident date', () => {
cy.get('[data-test="widget-enrollment"]').within(() => {
cy.get('[data-test="widget-enrollment-incident-date"]')
.contains(`Date of birth ${getCurrentYear()}-08-01`)
cy.get('[data-test="widget-enrollment-incident-date"]').within(() => {
cy.get('[data-test="widget-enrollment-date"]')
.contains(`Date of birth: ${getCurrentYear()}-08-01`)
.should('exist');
});
});
Expand Down
3 changes: 3 additions & 0 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,9 @@ msgstr "Remove mark for follow-up"
msgid "Mark for follow-up"
msgstr "Mark for follow-up"

msgid "Existing dates for auto-generated events will not be updated."
msgstr "Existing dates for auto-generated events will not be updated."

msgid "Enrollment date"
msgstr "Enrollment date"

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
},
"resolutions": {
"@babel/preset-react": "7.16.7",
"@js-temporal/polyfill": "0.4.3",
"core-js": "2.5.7"
},
"browserslist": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const enrollmentPageActionTypes = {

DELETE_ENROLLMENT: 'EnrollmentPage.DeleteEnrollment',
UPDATE_TEI_DISPLAY_NAME: 'EnrollmentPage.UpdateTeiDisplayName',
UPDATE_ENROLLMENT_DATE: 'EnrollmentPage.UpdateEnrollmentDate',
};

export const fetchEnrollmentPageInformation = () =>
Expand Down Expand Up @@ -73,3 +74,6 @@ export const updateTeiDisplayName = (teiDisplayName: string) =>
actionCreator(enrollmentPageActionTypes.UPDATE_TEI_DISPLAY_NAME)({
teiDisplayName,
});

export const updateEnrollmentDate = ({ enrollmentId, enrollmentDate }: { enrollmentId: string, enrollmentDate: string }) =>
actionCreator(enrollmentPageActionTypes.UPDATE_ENROLLMENT_DATE)({ enrollmentId, enrollmentDate });
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export const EnrollmentPageDefaultPlain = ({
classes,
onEventClick,
onUpdateTeiAttributeValues,
onUpdateEnrollmentDate,
onUpdateIncidentDate,
onEnrollmentError,
}: PlainProps) => (
<>
Expand Down Expand Up @@ -108,6 +110,8 @@ export const EnrollmentPageDefaultPlain = ({
programId={program.id}
onDelete={onDelete}
onAddNew={onAddNew}
onUpdateEnrollmentDate={onUpdateEnrollmentDate}
onUpdateIncidentDate={onUpdateIncidentDate}
onError={onEnrollmentError}
/>}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ import { useHistory } from 'react-router-dom';
import {
useCommonEnrollmentDomainData,
updateEnrollmentAttributeValues,
updateEnrollmentDate,
updateIncidentDate,
showEnrollmentError,
} from '../../common/EnrollmentOverviewDomain';
import {
updateEnrollmentDate as updateTopBarEnrollmentDate,
deleteEnrollment,
updateTeiDisplayName,
} from '../EnrollmentPage.actions';
import { useTrackerProgram } from '../../../../hooks/useTrackerProgram';
import { useRulesEngineOrgUnit } from '../../../../hooks/useRulesEngineOrgUnit';
import { EnrollmentPageDefaultComponent } from './EnrollmentPageDefault.component';
Expand All @@ -20,7 +27,6 @@ import {
useRuleEffects,
} from './hooks';
import { buildUrlQueryString, useLocationQuery } from '../../../../utils/routing';
import { deleteEnrollment, updateTeiDisplayName } from '../EnrollmentPage.actions';
import { useFilteredWidgetData } from './hooks/useFilteredWidgetData';

export const EnrollmentPageDefault = () => {
Expand Down Expand Up @@ -74,13 +80,23 @@ export const EnrollmentPageDefault = () => {
const onEventClick = (eventId: string) => {
history.push(`/enrollmentEventEdit?${buildUrlQueryString({ orgUnitId, eventId })}`);
};

const onUpdateTeiAttributeValues = useCallback((updatedAttributeValues, teiDisplayName) => {
dispatch(updateEnrollmentAttributeValues(updatedAttributeValues
.map(({ attribute, value }) => ({ id: attribute, value })),
));
dispatch(updateTeiDisplayName(teiDisplayName));
}, [dispatch]);

const onUpdateEnrollmentDate = useCallback((enrollmentDate) => {
dispatch(updateEnrollmentDate(enrollmentDate));
dispatch(updateTopBarEnrollmentDate({ enrollmentId, enrollmentDate }));
}, [dispatch, enrollmentId]);

const onUpdateIncidentDate = useCallback((incidentDate) => {
dispatch(updateIncidentDate(incidentDate));
}, [dispatch]);

const onAddNew = () => {
history.push(`/new?${buildUrlQueryString({ orgUnitId, programId, teiId })}`);
};
Expand All @@ -107,6 +123,8 @@ export const EnrollmentPageDefault = () => {
hideWidgets={hideWidgets}
onEventClick={onEventClick}
onUpdateTeiAttributeValues={onUpdateTeiAttributeValues}
onUpdateEnrollmentDate={onUpdateEnrollmentDate}
onUpdateIncidentDate={onUpdateIncidentDate}
onEnrollmentError={onEnrollmentError}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export type Props = {|
onCreateNew: (stageId: string) => void,
onEventClick: (eventId: string) => void,
onUpdateTeiAttributeValues: (attributes: Array<{ [key: string]: string }>, teiDisplayName: string) => void,
onUpdateEnrollmentDate: (enrollmentDate: string) => void,
onUpdateIncidentDate: (incidentDate: string) => void,
onEnrollmentError: (message: string) => void,
|};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const EnrollmentAddEventPagePain = ({
teiId={teiId}
enrollmentId={enrollmentId}
programId={programId}
readOnlyMode
onDelete={onDelete}
onAddNew={onAddNew}
onError={onEnrollmentError}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const EnrollmentEditEventPagePain = ({
teiId={teiId}
enrollmentId={enrollmentId}
programId={programId}
readOnlyMode
onDelete={onDelete}
onAddNew={onAddNew}
onError={onEnrollmentError}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { actionCreator } from '../../../../actions/actions.utils';

export const enrollmentSiteActionTypes = {
COMMON_ENROLLMENT_SITE_DATA_SET: 'EnrollmentSite.SetCommonData',
UPDATE_ENROLLMENT_DATE: 'Enrollment.UpdateEnrollmentDate',
UPDATE_INCIDENT_DATE: 'Enrollment.UpdateIncidentDate',
UPDATE_ENROLLMENT_EVENTS: 'Enrollment.UpdateEnrollmentEvents',
UPDATE_ENROLLMENT_EVENTS_WITHOUT_ID: 'Enrollment.UpdateEnrollmentEventsWithoutId',
UPDATE_ENROLLMENT_ATTRIBUTE_VALUES: 'Enrollment.UpdateEnrollmentAttributeValues',
Expand All @@ -18,6 +20,16 @@ export const enrollmentSiteActionTypes = {
export const setCommonEnrollmentSiteData = (enrollment: ApiEnrollment, attributeValues: ApiAttributeValues) =>
actionCreator(enrollmentSiteActionTypes.COMMON_ENROLLMENT_SITE_DATA_SET)({ enrollment, attributeValues });

export const updateEnrollmentDate = (enrollmentDate: string) =>
actionCreator(enrollmentSiteActionTypes.UPDATE_ENROLLMENT_DATE)({
enrollmentDate,
});

export const updateIncidentDate = (incidentDate: string) =>
actionCreator(enrollmentSiteActionTypes.UPDATE_INCIDENT_DATE)({
incidentDate,
});

export const updateEnrollmentEvents = (eventId: string, eventData: Object) =>
actionCreator(enrollmentSiteActionTypes.UPDATE_ENROLLMENT_EVENTS)({
eventId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
export type { HideWidgets, WidgetEffects } from './enrollmentOverviewDomain.types';
export {
enrollmentSiteActionTypes,
updateEnrollmentDate,
updateIncidentDate,
updateEnrollmentEvents,
commitEnrollmentEvent,
rollbackEnrollmentEvent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useDataMutation } from '@dhis2/app-runtime';
import React from 'react';
import { ActionsComponent } from './Actions.component';
import type { Props } from './actions.types';
import { processErrorReports } from '../processErrorReports';

const enrollmentUpdate = {
resource: 'tracker?async=false&importStrategy=UPDATE',
Expand All @@ -18,13 +19,6 @@ const enrollmentDelete = {
enrollments: [enrollment],
}),
};
const processErrorReports = (error) => {
// $FlowFixMe[prop-missing]
const errorReports = error?.details?.validationReport?.errorReports;
return errorReports?.length > 0
? errorReports.reduce((acc, errorReport) => `${acc} ${errorReport.message}`, '')
: error.message;
};

export const Actions = ({
enrollment = {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// @flow
import React, { useState, useCallback } from 'react';
import moment from 'moment';
import {
Button,
CalendarInput,
IconCalendar16,
IconEdit16,
colors,
spacersNum,
} from '@dhis2/ui';
import i18n from '@dhis2/d2-i18n';
import { withStyles } from '@material-ui/core';
import { parseDate } from 'capture-core-utils/parsers';
import { convertValue as convertValueClientToView } from '../../../converters/clientToView';
import { dataElementTypes } from '../../../metaData';

type Props = {
date: string,
dateLabel: string,
editEnabled: boolean,
displayAutoGeneratedEventWarning: boolean,
onSave: (string) => void,
...CssClasses,
}

const styles = {
editButton: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
cursor: 'pointer',
border: 'none',
borderRadius: '3px',
background: 'transparent',
color: colors.grey600,
padding: 0,
marginLeft: '2px',
'&:focus': {
outline: 'none',
background: colors.grey200,
color: colors.grey800,
},
'&:hover': {
background: colors.grey200,
color: colors.grey800,
},
},
calendar: {
paddingTop: '6px',
},
inputField: {
maxWidth: '200px',
},
buttonStrip: {
display: 'flex',
gap: `${spacersNum.dp4}px`,
margin: `${spacersNum.dp4}px 0`,
},
note: {
fontSize: '12px',
color: colors.grey700,
},
};

const DateComponentPlain = ({
date,
dateLabel,
editEnabled,
displayAutoGeneratedEventWarning,
onSave,
classes,
}: Props) => {
const [editMode, setEditMode] = useState(false);
const [selectedDate, setSelectedDate] = useState();
const dateChangeHandler = useCallback(({ calendarDateString }) => {
setSelectedDate(calendarDateString);
}, [setSelectedDate]);
const displayDate = String(convertValueClientToView(date, dataElementTypes.DATE));

const onOpenEdit = () => {
// CalendarInput component only supports the YYYY-MM-DD format
setSelectedDate(moment(date).format('YYYY-MM-DD'));
setEditMode(true);
};
const saveHandler = () => {
// CalendarInput component only supports the YYYY-MM-DD format
if (selectedDate) {
const dateObject = parseDate(selectedDate, 'YYYY-MM-DD').momentDate;
if (dateObject && dateObject.toISOString() !== date) {
onSave(dateObject.toISOString(true));
}
}
setEditMode(false);
};

return editMode ? (
<div data-test="widget-enrollment-date">
<div className={classes.inputField}>
<CalendarInput
calendar="gregory"
dense
className={classes.calendar}
label={dateLabel}
date={selectedDate}
onDateSelect={dateChangeHandler}
/>
</div>
<div className={classes.buttonStrip}>
<Button
primary
small
onClick={saveHandler}
>
{i18n.t('Save')}
</Button>
<Button
secondary
small
onClick={() => setEditMode(false)}
>
{i18n.t('Cancel')}
</Button>
</div>
{displayAutoGeneratedEventWarning && (
<div className={classes.note}>
{i18n.t('Existing dates for auto-generated events will not be updated.')}
</div>
)}
</div>
) : (
<div className={classes.row} data-test="widget-enrollment-date">
<span data-test="widget-enrollment-icon-calendar">
<IconCalendar16 color={colors.grey600} />
</span>
{dateLabel}{': '}
{displayDate}
{editEnabled &&
<button
className={classes.editButton}
data-test="widget-enrollment-icon-edit-date"
onClick={onOpenEdit}
>
<IconEdit16 />
</button>
}
</div>
);
};

export const Date = withStyles(styles)(DateComponentPlain);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
export { Date } from './Date.component';
Loading