diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.js index e613228db2..238bf0f811 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.js @@ -65,6 +65,7 @@ export const TopBar = ({ onResetOrgUnitId={() => resetOrgUnitId()} isUserInteractionInProgress={isUserInteractionInProgress} onStartAgain={() => reset()} + isReadOnly={mode === dataEntryKeys.EDIT} > { @@ -63,6 +64,7 @@ export const TopBar = ({ isUserInteractionInProgress={isUserInteractionInProgress} formIsOpen={formIsOpen} onStartAgain={() => reset()} + isReadOnly={isReadOnly} > { +export const ViewEventPageComponent = ({ isUserInteractionInProgress, showAddRelationship, isReadOnly }: Props) => { const { serverVersion: { minor } } = useConfig(); useEffect(() => inMemoryFileStore.clear, []); @@ -29,6 +30,7 @@ export const ViewEventPageComponent = ({ isUserInteractionInProgress, showAddRel programId={programId} orgUnitId={orgUnitId} selectedCategories={selectedCategories} + isReadOnly={isReadOnly} isUserInteractionInProgress={isUserInteractionInProgress} formIsOpen /> diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventPage.container.js b/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventPage.container.js index e7ae0841d8..6b494e9485 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventPage.container.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventPage.container.js @@ -7,6 +7,7 @@ import { withLoadingIndicator, withErrorMessageHandler } from '../../../HOC'; const mapStateToProps = (state: ReduxState) => { const eventDetailsSection = state.viewEventPage.eventDetailsSection || {}; + const isReadOnly = eventDetailsSection.showEditEvent; const isUserInteractionInProgress = (state.currentSelections.complete && eventDetailsSection.showEditEvent) ? @@ -17,6 +18,7 @@ const mapStateToProps = (state: ReduxState) => { error: state.activePage.viewEventLoadError && state.activePage.viewEventLoadError.error, ready: !state.activePage.lockedSelectorLoads, isUserInteractionInProgress, + isReadOnly, showAddRelationship: state.viewEventPage.showAddRelationship, }; }; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js index 503a47524d..b4b6f38444 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js @@ -9,8 +9,11 @@ import { updateRulesEffects, } from '../../../rules'; import { RenderFoundation, Program } from '../../../metaData'; -import { getEventDateValidatorContainers } from './fieldValidators/eventDate.validatorContainersGetter'; -import { getCategoryOptionsValidatorContainers } from './fieldValidators/categoryOptions.validatorContainersGetter'; +import { + getEventDateValidatorContainers, + getOrgUnitValidatorContainers, + getCategoryOptionsValidatorContainers, +} from './fieldValidators'; import { getConvertGeometryIn, convertGeometryOut, @@ -110,6 +113,11 @@ export const openEventForEditInDataEntry = ({ type: 'DATE', validatorContainers: getEventDateValidatorContainers(), }, + { + id: 'orgUnit', + type: 'ORGANISATION_UNIT', + validatorContainers: getOrgUnitValidatorContainers(), + }, { clientId: 'geometry', dataEntryId: 'geometry', diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js index 6ff85dc625..8faa384940 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js @@ -4,7 +4,6 @@ import { from } from 'rxjs'; import { ofType } from 'redux-observable'; import { map, switchMap } from 'rxjs/operators'; import { batchActions } from 'redux-batched-actions'; -import type { OrgUnit } from '@dhis2/rules-engine-javascript'; import { rulesExecutedPostUpdateField } from '../../../DataEntry/actions/dataEntry.actions'; import { batchActionTypes as editEventDataEntryBatchActionTypes, @@ -26,13 +25,13 @@ import { getDataEntryKey } from '../../../DataEntry/common/getDataEntryKey'; import { prepareEnrollmentEventsForRulesEngine } from '../../../../events/prepareEnrollmentEvents'; import { getEnrollmentForRulesEngine, getAttributeValuesForRulesEngine } from '../../helpers'; import type { QuerySingleResource } from '../../../../utils/api'; +import { getCoreOrgUnitFn, orgUnitFetched } from '../../../../metadataRetrieval/coreOrgUnit'; const runRulesForEditSingleEvent = async ({ store, dataEntryId, itemId, uid, - orgUnit, fieldData, programId, querySingleResource, @@ -42,7 +41,6 @@ const runRulesForEditSingleEvent = async ({ itemId: string, uid: string, programId: string, - orgUnit: OrgUnit, fieldData?: ?FieldData, querySingleResource: QuerySingleResource }) => { @@ -66,6 +64,10 @@ const runRulesForEditSingleEvent = async ({ // $FlowFixMe const currentEvent = { ...currentEventValues, ...currentEventMainData, eventId }; + const { coreOrgUnit, cached } = + // $FlowFixMe + await getCoreOrgUnitFn(querySingleResource)(currentEvent.orgUnit?.id, store.value.organisationUnits); + let effects; if (program instanceof TrackerProgram) { const { enrollment, attributeValues } = state.enrollmentDomain; @@ -82,7 +84,7 @@ const runRulesForEditSingleEvent = async ({ effects = getApplicableRuleEffectsForTrackerProgram({ program, stage, - orgUnit, + orgUnit: coreOrgUnit, currentEvent: { ...currentEvent, createdAt: apiCurrentEventOriginal.createdAt }, otherEvents: prepareEnrollmentEventsForRulesEngine(apiOtherEvents), enrollmentData: getEnrollmentForRulesEngine(enrollment), @@ -91,7 +93,7 @@ const runRulesForEditSingleEvent = async ({ } else { effects = getApplicableRuleEffectsForEventProgram({ program, - orgUnit, + orgUnit: coreOrgUnit, currentEvent, }); } @@ -105,6 +107,7 @@ const runRulesForEditSingleEvent = async ({ return batchActions([ updateRulesEffects(effectsWithValidations, formId), rulesExecutedPostUpdateField(dataEntryId, itemId, uid), + ...(coreOrgUnit && !cached ? [orgUnitFetched(coreOrgUnit)] : []), ], editEventDataEntryBatchActionTypes.RULES_EFFECTS_ACTIONS_BATCH); }; @@ -114,20 +117,19 @@ export const runRulesOnUpdateDataEntryFieldForEditSingleEventEpic = ( store: ReduxStore, { querySingleResource }: ApiUtils, ) => -// $FlowSuppress + // $FlowSuppress action$.pipe( ofType(editEventDataEntryBatchActionTypes.UPDATE_DATA_ENTRY_FIELD_EDIT_SINGLE_EVENT_ACTION_BATCH), map(actionBatch => actionBatch.payload.find(action => action.type === editEventDataEntryActionTypes.START_RUN_RULES_ON_UPDATE), ), switchMap((action) => { - const { dataEntryId, itemId, uid, orgUnit, programId } = action.payload; + const { dataEntryId, itemId, uid, programId } = action.payload; const runRulesForEditSingleEventPromise = runRulesForEditSingleEvent({ store, dataEntryId, itemId, uid, - orgUnit, programId, querySingleResource, }); @@ -139,7 +141,7 @@ export const runRulesOnUpdateFieldForEditSingleEventEpic = ( store: ReduxStore, { querySingleResource }: ApiUtils, ) => -// $FlowSuppress + // $FlowSuppress action$.pipe( ofType(editEventDataEntryBatchActionTypes.UPDATE_FIELD_EDIT_SINGLE_EVENT_ACTION_BATCH), map(actionBatch => @@ -153,7 +155,6 @@ export const runRulesOnUpdateFieldForEditSingleEventEpic = ( dataEntryId, itemId, uid, - orgUnit, programId, } = action.payload; const fieldData: FieldData = { @@ -166,7 +167,6 @@ export const runRulesOnUpdateFieldForEditSingleEventEpic = ( dataEntryId, itemId, uid, - orgUnit, fieldData, programId, querySingleResource, diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js index 7c2103b71e..5fed499194 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js @@ -33,6 +33,7 @@ import { withDefaultFieldContainer, withDefaultShouldUpdateInterface, VirtualizedSelectField, + SingleOrgUnitSelectField, } from '../../FormFields/New'; import { statusTypes, translatedStatusTypes } from '../../../events/statusTypes'; import labelTypeClasses from '../DataEntry/dataEntryFieldLabels.module.css'; @@ -48,6 +49,7 @@ import { withDataEntryFields, } from '../../DataEntryDhis2Helpers/'; import type { UserFormField } from '../../FormFields/UserField'; +import { getOrgUnitValidatorContainers } from '../DataEntry/fieldValidators'; const tabMode = Object.freeze({ REPORT: 'REPORT', @@ -197,6 +199,47 @@ const buildScheduleDateSettingsFn = () => { return scheduleDateSettings; }; +const buildOrgUnitSettingsFn = () => { + const orgUnitComponent = + withCalculateMessages(overrideMessagePropNames)( + withFocusSaver()( + withDefaultFieldContainer()( + withDefaultShouldUpdateInterface()( + withLabel({ + onGetUseVerticalOrientation: (props: Object) => props.formHorizontal, + onGetCustomFieldLabeClass: (props: Object) => + `${props.fieldOptions.fieldLabelMediaBasedClass} ${labelTypeClasses.dateLabel}`, + })( + withDisplayMessages()( + withInternalChangeHandler()( + withFilterProps(defaultFilterProps)(SingleOrgUnitSelectField), + ), + ), + ), + ), + ), + ), + ); + + const orgUnitSettings = { + getComponent: () => orgUnitComponent, + getComponentProps: (props: Object) => createComponentProps(props, { + width: props && props.formHorizontal ? 150 : 350, + label: i18n.t('Organisation unit'), + required: true, + }), + getPropName: () => 'orgUnit', + getValidatorContainers: () => getOrgUnitValidatorContainers(), + getMeta: () => ({ + placement: placements.TOP, + section: dataEntrySectionNames.BASICINFO, + }), + }; + + return orgUnitSettings; +}; + + const pointComponent = withCalculateMessages(overrideMessagePropNames)( withFocusSaver()( withDefaultFieldContainer()( @@ -252,7 +295,7 @@ const buildGeometrySettingsFn = () => ({ label: i18n.t('Area'), dialogLabel: i18n.t('Area'), required: false, - orgUnitId: props.orgUnit?.id, + orgUnitId: props.orgUnitIdFieldValue, }); } return createComponentProps(props, { @@ -260,7 +303,7 @@ const buildGeometrySettingsFn = () => ({ label: i18n.t('Coordinate'), dialogLabel: i18n.t('Coordinate'), required: false, - orgUnitId: props.orgUnit?.id, + orgUnitId: props.orgUnitIdFieldValue, }); }, getPropName: () => 'geometry', @@ -359,7 +402,8 @@ const saveHandlerConfig = { const AOCFieldBuilderHOC = withAOCFieldBuilder({})(withDataEntryFields(getCategoryOptionsSettingsFn())(DataEntry)); const CleanUpHOC = withCleanUp()(AOCFieldBuilderHOC); const GeometryField = withDataEntryFieldIfApplicable(buildGeometrySettingsFn())(CleanUpHOC); -const ScheduleDateField = withDataEntryField(buildScheduleDateSettingsFn())(GeometryField); +const OrgUnitField = withDataEntryField(buildOrgUnitSettingsFn())(GeometryField); +const ScheduleDateField = withDataEntryField(buildScheduleDateSettingsFn())(OrgUnitField); const ReportDateField = withDataEntryField(buildReportDateSettingsFn())(ScheduleDateField); const SaveableDataEntry = withSaveHandler(saveHandlerConfig)(withMainButton()(ReportDateField)); const CancelableDataEntry = withCancelButton(getCancelOptions)(SaveableDataEntry); @@ -398,6 +442,7 @@ type Props = { enrollmentId?: string, isCompleted?: boolean, assignee?: UserFormField | null, + orgUnitFieldValue: ?OrgUnit, }; @@ -493,6 +538,7 @@ class EditEventDataEntryPlain extends Component { dataEntryId, orgUnit, programId, + orgUnitFieldValue, onUpdateDataEntryField, onUpdateField, onStartAsyncUpdateField, @@ -512,6 +558,7 @@ class EditEventDataEntryPlain extends Component { onSaveAndCompleteEnrollment={onSaveAndCompleteEnrollment(orgUnit)} fieldOptions={this.fieldOptions} dataEntrySections={this.dataEntrySections} + orgUnitIdFieldValue={orgUnitFieldValue?.id} orgUnit={orgUnit} orgUnitId={orgUnit?.id} programId={programId} diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container.js index 23f9599d56..fff2ffeb41 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container.js @@ -32,12 +32,14 @@ const mapStateToProps = (state: ReduxState, props) => { const itemId = state.dataEntries[props.dataEntryId] && state.dataEntries[props.dataEntryId].itemId; const dataEntryKey = `${props.dataEntryId}-${itemId}`; + const orgUnitFieldValue = state.dataEntriesFieldsValue[dataEntryKey]?.orgUnit; const isCompleted = state.dataEntriesFieldsValue[dataEntryKey]?.complete === 'true'; return { ready: !state.activePage.isDataEntryLoading && !eventDetailsSection.loading, itemId, isCompleted, + orgUnitFieldValue, enrolledAt: state.enrollmentDomain?.enrollment?.enrolledAt, occurredAt: state.enrollmentDomain?.enrollment?.occurredAt, eventData: state.enrollmentDomain?.enrollment?.events, diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js index dcd813f546..b3cbc9a4af 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js @@ -121,6 +121,7 @@ export const saveEditedEventEpic = (action$: InputObservable, store: ReduxStore, const serverData = { events: [{ ...mainDataServerValues, + orgUnit: mainDataServerValues.orgUnit.id, attributeOptionCombo: undefined, dataValues: formFoundation .getElements() @@ -267,6 +268,7 @@ export const saveEventAndCompleteEnrollmentEpic = (action$: InputObservable, sto const editEvent = { ...mainDataServerValues, + orgUnit: mainDataServerValues.orgUnit.id, attributeOptionCombo: undefined, dataValues: formFoundation .getElements() diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/ViewEventDataEntry/ViewEventDataEntry.component.js b/src/core_modules/capture-core/components/WidgetEventEdit/ViewEventDataEntry/ViewEventDataEntry.component.js index eed050d621..ffdff870ac 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/ViewEventDataEntry/ViewEventDataEntry.component.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/ViewEventDataEntry/ViewEventDataEntry.component.js @@ -150,7 +150,7 @@ const buildOrgUnitSettingsFn = () => { label: i18n.t('Organisation unit'), valueConverter: value => dataElement.convertValue(value, valueConvertFn), }), - getPropName: () => 'orgUnitId', + getPropName: () => 'orgUnit', getMeta: () => ({ placement: placements.TOP, section: dataEntrySectionNames.BASICINFO, diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/ViewEventDataEntry/viewEventDataEntry.actions.js b/src/core_modules/capture-core/components/WidgetEventEdit/ViewEventDataEntry/viewEventDataEntry.actions.js index 65b90ecf13..96a7fbd365 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/ViewEventDataEntry/viewEventDataEntry.actions.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/ViewEventDataEntry/viewEventDataEntry.actions.js @@ -51,7 +51,7 @@ export const loadViewEventDataEntry = serverMinorVersion, }: { eventContainer: ClientEventContainer, - orgUnit: OrgUnit, + orgUnit: { ...OrgUnit, path: string }, foundation: RenderFoundation, program: Program, dataEntryId: string, @@ -67,15 +67,15 @@ export const loadViewEventDataEntry = type: 'DATE', validatorContainers: getEventDateValidatorContainers(), }, - { - id: 'orgUnitId', - type: 'ORGANISATION_UNIT', - validatorContainers: getOrgUnitValidatorContainers(), - }, { id: 'scheduledAt', type: 'DATE', }, + { + id: 'orgUnit', + type: 'ORGANISATION_UNIT', + validatorContainers: getOrgUnitValidatorContainers(), + }, { clientId: 'geometry', dataEntryId: 'geometry', @@ -118,6 +118,11 @@ export const loadViewEventDataEntry = dataEntryPropsToInclude.push(...Object.keys(attributeCategoryOptions).map(id => ({ id, type: 'TEXT' }))); } + const clientValuesForDataEntry = { + ...eventContainer.event, + orgUnit: { id: orgUnit.id, name: orgUnit.name, path: orgUnit.path }, + }; + const extraProps = { eventId: eventContainer.event.eventId, }; @@ -126,7 +131,7 @@ export const loadViewEventDataEntry = loadEditDataEntryAsync( dataEntryId, dataEntryKey, - eventContainer.event, + clientValuesForDataEntry, eventContainer.values, dataEntryPropsToInclude, foundation, @@ -163,7 +168,6 @@ export const loadViewEventDataEntry = }); } const filteredEffects = filterApplicableRuleEffects(effects, effectActions.ASSIGN_VALUE); - return [ ...dataEntryActions, updateRulesEffects(filteredEffects, formId), diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.component.js b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.component.js index a86780b8c4..1cf0c14a9d 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.component.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.component.js @@ -2,6 +2,7 @@ import React, { type ComponentType, useEffect } from 'react'; import { spacersNum } from '@dhis2/ui'; import withStyles from '@material-ui/core/styles/withStyles'; +import { DividerHorizontal as Divider } from 'capture-ui'; import i18n from '@dhis2/d2-i18n'; import { isValidOrgUnit } from 'capture-core-utils/validators/form'; import { DataSection } from '../DataSection'; @@ -15,11 +16,17 @@ import { CategoryOptions } from './CategoryOptions/CategoryOptions.component'; import { Assignee } from './Assignee'; import { ScheduleOrgUnit } from './ScheduleOrgUnit/ScheduleOrgUnit.component'; -const styles = () => ({ +const styles = theme => ({ wrapper: { paddingLeft: spacersNum.dp16, minWidth: '300px', }, + evenNumbersRecords: { + backgroundColor: theme.palette.grey.lightest, + }, + divider: { + backgroundColor: theme.palette.dividerForm, + }, }); const WidgetEventSchedulePlain = ({ @@ -76,12 +83,6 @@ const WidgetEventSchedulePlain = ({ dataTest="schedule-section" sectionName={i18n.t('Schedule info')} > - + +
+ +
{programCategory &&