diff --git a/cypress/integration/WidgetsForEnrollmentPages/WidgetEnrollment/index.js b/cypress/integration/WidgetsForEnrollmentPages/WidgetEnrollment/index.js index 26146624d7..4584e1ba2a 100644 --- a/cypress/integration/WidgetsForEnrollmentPages/WidgetEnrollment/index.js +++ b/cypress/integration/WidgetsForEnrollmentPages/WidgetEnrollment/index.js @@ -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'); }); }); diff --git a/i18n/en.pot b/i18n/en.pot index 52dcc1b13e..9674ac525a 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -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" diff --git a/package.json b/package.json index 6f9a6db16c..b2ed536c77 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ }, "resolutions": { "@babel/preset-react": "7.16.7", + "@js-temporal/polyfill": "0.4.3", "core-js": "2.5.7" }, "browserslist": { diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.actions.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.actions.js index a1f8c3250a..5f29c4daac 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.actions.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.actions.js @@ -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 = () => @@ -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 }); diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.component.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.component.js index d2b5b6f091..dac5ca2c8a 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.component.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.component.js @@ -60,6 +60,8 @@ export const EnrollmentPageDefaultPlain = ({ classes, onEventClick, onUpdateTeiAttributeValues, + onUpdateEnrollmentDate, + onUpdateIncidentDate, onEnrollmentError, }: PlainProps) => ( <> @@ -108,6 +110,8 @@ export const EnrollmentPageDefaultPlain = ({ programId={program.id} onDelete={onDelete} onAddNew={onAddNew} + onUpdateEnrollmentDate={onUpdateEnrollmentDate} + onUpdateIncidentDate={onUpdateIncidentDate} onError={onEnrollmentError} />} diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.js index 976e03492a..12eb12de18 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.js @@ -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'; @@ -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 = () => { @@ -74,6 +80,7 @@ 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 })), @@ -81,6 +88,15 @@ export const EnrollmentPageDefault = () => { 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 })}`); }; @@ -107,6 +123,8 @@ export const EnrollmentPageDefault = () => { hideWidgets={hideWidgets} onEventClick={onEventClick} onUpdateTeiAttributeValues={onUpdateTeiAttributeValues} + onUpdateEnrollmentDate={onUpdateEnrollmentDate} + onUpdateIncidentDate={onUpdateIncidentDate} onEnrollmentError={onEnrollmentError} /> ); diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.types.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.types.js index 3822b286a0..b8e3dbd4dd 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.types.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.types.js @@ -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, |}; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.component.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.component.js index e741014b20..f7abac3990 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.component.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.component.js @@ -126,6 +126,7 @@ const EnrollmentAddEventPagePain = ({ teiId={teiId} enrollmentId={enrollmentId} programId={programId} + readOnlyMode onDelete={onDelete} onAddNew={onAddNew} onError={onEnrollmentError} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js index b5efd930b9..1add434b21 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js @@ -138,6 +138,7 @@ const EnrollmentEditEventPagePain = ({ teiId={teiId} enrollmentId={enrollmentId} programId={programId} + readOnlyMode onDelete={onDelete} onAddNew={onAddNew} onError={onEnrollmentError} diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.js index 6cdbe894ba..beb3832f89 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.js +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.js @@ -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', @@ -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, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.js index e7a9b66f5b..f79612916d 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.js +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.js @@ -2,6 +2,8 @@ export type { HideWidgets, WidgetEffects } from './enrollmentOverviewDomain.types'; export { enrollmentSiteActionTypes, + updateEnrollmentDate, + updateIncidentDate, updateEnrollmentEvents, commitEnrollmentEvent, rollbackEnrollmentEvent, diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.js index e83fdfda17..2ce98fcb38 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.js @@ -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', @@ -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 = {}, diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Date/Date.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/Date/Date.component.js new file mode 100644 index 0000000000..f3c7f36617 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Date/Date.component.js @@ -0,0 +1,151 @@ +// @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 { 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 newDate = moment.utc(selectedDate, 'YYYY-MM-DD').format('YYYY-MM-DDTHH:mm:ss.SSS'); + if (newDate !== date) { + onSave(newDate); + } + } + setEditMode(false); + }; + + return editMode ? ( +