Skip to content

Commit 3d0e394

Browse files
authored
feat: [DHIS2-18746][DHIS2-18903] Show org. unit in edit event form (#3949)
* feat: add org unit field to form * fix: change order for org unit and schedule date * feat: top bar org unit in read only mode * fix: code clean up and topbar fix * feat: auto select org unit * feat: convert org unit in server data * feat: rules * fix: top bar event program * fix: missing semi colon * fix: style change * fix: review comment * fix: add path to org unit type * fix: breaking test * fix: type for org unit rules engine * fix: org unit for geometry
1 parent 28996f6 commit 3d0e394

13 files changed

+115
-35
lines changed

src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.js

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const TopBar = ({
6565
onResetOrgUnitId={() => resetOrgUnitId()}
6666
isUserInteractionInProgress={isUserInteractionInProgress}
6767
onStartAgain={() => reset()}
68+
isReadOnly={mode === dataEntryKeys.EDIT}
6869
>
6970
<SingleLockedSelect
7071
displayOnly

src/core_modules/capture-core/components/Pages/ViewEvent/TopBar.container.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,22 @@ import {
1111
resetCategoryOption,
1212
resetAllCategoryOptions,
1313
} from './ViewEventPage.actions';
14-
1514
import { TopBarActions } from '../../TopBarActions';
1615

1716
type TopBarProps = {
1817
isUserInteractionInProgress: boolean,
1918
programId?: string,
2019
orgUnitId?: string,
2120
selectedCategories?: any,
21+
isReadOnly: boolean,
2222
formIsOpen: boolean,
2323
};
2424

2525
export const TopBar = ({
2626
programId,
2727
orgUnitId,
2828
selectedCategories,
29+
isReadOnly,
2930
isUserInteractionInProgress,
3031
formIsOpen,
3132
}: TopBarProps) => {
@@ -63,6 +64,7 @@ export const TopBar = ({
6364
isUserInteractionInProgress={isUserInteractionInProgress}
6465
formIsOpen={formIsOpen}
6566
onStartAgain={() => reset()}
67+
isReadOnly={isReadOnly}
6668
>
6769
<TopBarActions
6870
selectedProgramId={programId}

src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventPage.component.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import { inMemoryFileStore } from '../../DataEntry/file/inMemoryFileStore';
1111
type Props = {
1212
isUserInteractionInProgress: boolean,
1313
showAddRelationship: boolean,
14+
isReadOnly: boolean,
1415
};
1516

16-
export const ViewEventPageComponent = ({ isUserInteractionInProgress, showAddRelationship }: Props) => {
17+
export const ViewEventPageComponent = ({ isUserInteractionInProgress, showAddRelationship, isReadOnly }: Props) => {
1718
const { serverVersion: { minor } } = useConfig();
1819
useEffect(() => inMemoryFileStore.clear, []);
1920

@@ -29,6 +30,7 @@ export const ViewEventPageComponent = ({ isUserInteractionInProgress, showAddRel
2930
programId={programId}
3031
orgUnitId={orgUnitId}
3132
selectedCategories={selectedCategories}
33+
isReadOnly={isReadOnly}
3234
isUserInteractionInProgress={isUserInteractionInProgress}
3335
formIsOpen
3436
/>

src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventPage.container.js

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { withLoadingIndicator, withErrorMessageHandler } from '../../../HOC';
77

88
const mapStateToProps = (state: ReduxState) => {
99
const eventDetailsSection = state.viewEventPage.eventDetailsSection || {};
10+
const isReadOnly = eventDetailsSection.showEditEvent;
1011
const isUserInteractionInProgress =
1112
(state.currentSelections.complete && eventDetailsSection.showEditEvent)
1213
?
@@ -17,6 +18,7 @@ const mapStateToProps = (state: ReduxState) => {
1718
error: state.activePage.viewEventLoadError && state.activePage.viewEventLoadError.error,
1819
ready: !state.activePage.lockedSelectorLoads,
1920
isUserInteractionInProgress,
21+
isReadOnly,
2022
showAddRelationship: state.viewEventPage.showAddRelationship,
2123
};
2224
};

src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/editEventDataEntry.actions.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import {
99
updateRulesEffects,
1010
} from '../../../rules';
1111
import { RenderFoundation, Program } from '../../../metaData';
12-
import { getEventDateValidatorContainers } from './fieldValidators/eventDate.validatorContainersGetter';
13-
import { getCategoryOptionsValidatorContainers } from './fieldValidators/categoryOptions.validatorContainersGetter';
12+
import {
13+
getEventDateValidatorContainers,
14+
getOrgUnitValidatorContainers,
15+
getCategoryOptionsValidatorContainers,
16+
} from './fieldValidators';
1417
import {
1518
getConvertGeometryIn,
1619
convertGeometryOut,
@@ -110,6 +113,11 @@ export const openEventForEditInDataEntry = ({
110113
type: 'DATE',
111114
validatorContainers: getEventDateValidatorContainers(),
112115
},
116+
{
117+
id: 'orgUnit',
118+
type: 'ORGANISATION_UNIT',
119+
validatorContainers: getOrgUnitValidatorContainers(),
120+
},
113121
{
114122
clientId: 'geometry',
115123
dataEntryId: 'geometry',

src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/epics/editEventDataEntry.epics.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { from } from 'rxjs';
44
import { ofType } from 'redux-observable';
55
import { map, switchMap } from 'rxjs/operators';
66
import { batchActions } from 'redux-batched-actions';
7-
import type { OrgUnit } from '@dhis2/rules-engine-javascript';
87
import { rulesExecutedPostUpdateField } from '../../../DataEntry/actions/dataEntry.actions';
98
import {
109
batchActionTypes as editEventDataEntryBatchActionTypes,
@@ -26,13 +25,13 @@ import { getDataEntryKey } from '../../../DataEntry/common/getDataEntryKey';
2625
import { prepareEnrollmentEventsForRulesEngine } from '../../../../events/prepareEnrollmentEvents';
2726
import { getEnrollmentForRulesEngine, getAttributeValuesForRulesEngine } from '../../helpers';
2827
import type { QuerySingleResource } from '../../../../utils/api';
28+
import { getCoreOrgUnitFn, orgUnitFetched } from '../../../../metadataRetrieval/coreOrgUnit';
2929

3030
const runRulesForEditSingleEvent = async ({
3131
store,
3232
dataEntryId,
3333
itemId,
3434
uid,
35-
orgUnit,
3635
fieldData,
3736
programId,
3837
querySingleResource,
@@ -42,7 +41,6 @@ const runRulesForEditSingleEvent = async ({
4241
itemId: string,
4342
uid: string,
4443
programId: string,
45-
orgUnit: OrgUnit,
4644
fieldData?: ?FieldData,
4745
querySingleResource: QuerySingleResource
4846
}) => {
@@ -66,6 +64,10 @@ const runRulesForEditSingleEvent = async ({
6664
// $FlowFixMe
6765
const currentEvent = { ...currentEventValues, ...currentEventMainData, eventId };
6866

67+
const { coreOrgUnit, cached } =
68+
// $FlowFixMe
69+
await getCoreOrgUnitFn(querySingleResource)(currentEvent.orgUnit?.id, store.value.organisationUnits);
70+
6971
let effects;
7072
if (program instanceof TrackerProgram) {
7173
const { enrollment, attributeValues } = state.enrollmentDomain;
@@ -82,7 +84,7 @@ const runRulesForEditSingleEvent = async ({
8284
effects = getApplicableRuleEffectsForTrackerProgram({
8385
program,
8486
stage,
85-
orgUnit,
87+
orgUnit: coreOrgUnit,
8688
currentEvent: { ...currentEvent, createdAt: apiCurrentEventOriginal.createdAt },
8789
otherEvents: prepareEnrollmentEventsForRulesEngine(apiOtherEvents),
8890
enrollmentData: getEnrollmentForRulesEngine(enrollment),
@@ -91,7 +93,7 @@ const runRulesForEditSingleEvent = async ({
9193
} else {
9294
effects = getApplicableRuleEffectsForEventProgram({
9395
program,
94-
orgUnit,
96+
orgUnit: coreOrgUnit,
9597
currentEvent,
9698
});
9799
}
@@ -105,6 +107,7 @@ const runRulesForEditSingleEvent = async ({
105107
return batchActions([
106108
updateRulesEffects(effectsWithValidations, formId),
107109
rulesExecutedPostUpdateField(dataEntryId, itemId, uid),
110+
...(coreOrgUnit && !cached ? [orgUnitFetched(coreOrgUnit)] : []),
108111
],
109112
editEventDataEntryBatchActionTypes.RULES_EFFECTS_ACTIONS_BATCH);
110113
};
@@ -114,20 +117,19 @@ export const runRulesOnUpdateDataEntryFieldForEditSingleEventEpic = (
114117
store: ReduxStore,
115118
{ querySingleResource }: ApiUtils,
116119
) =>
117-
// $FlowSuppress
120+
// $FlowSuppress
118121
action$.pipe(
119122
ofType(editEventDataEntryBatchActionTypes.UPDATE_DATA_ENTRY_FIELD_EDIT_SINGLE_EVENT_ACTION_BATCH),
120123
map(actionBatch =>
121124
actionBatch.payload.find(action => action.type === editEventDataEntryActionTypes.START_RUN_RULES_ON_UPDATE),
122125
),
123126
switchMap((action) => {
124-
const { dataEntryId, itemId, uid, orgUnit, programId } = action.payload;
127+
const { dataEntryId, itemId, uid, programId } = action.payload;
125128
const runRulesForEditSingleEventPromise = runRulesForEditSingleEvent({
126129
store,
127130
dataEntryId,
128131
itemId,
129132
uid,
130-
orgUnit,
131133
programId,
132134
querySingleResource,
133135
});
@@ -139,7 +141,7 @@ export const runRulesOnUpdateFieldForEditSingleEventEpic = (
139141
store: ReduxStore,
140142
{ querySingleResource }: ApiUtils,
141143
) =>
142-
// $FlowSuppress
144+
// $FlowSuppress
143145
action$.pipe(
144146
ofType(editEventDataEntryBatchActionTypes.UPDATE_FIELD_EDIT_SINGLE_EVENT_ACTION_BATCH),
145147
map(actionBatch =>
@@ -153,7 +155,6 @@ export const runRulesOnUpdateFieldForEditSingleEventEpic = (
153155
dataEntryId,
154156
itemId,
155157
uid,
156-
orgUnit,
157158
programId,
158159
} = action.payload;
159160
const fieldData: FieldData = {
@@ -166,7 +167,6 @@ export const runRulesOnUpdateFieldForEditSingleEventEpic = (
166167
dataEntryId,
167168
itemId,
168169
uid,
169-
orgUnit,
170170
fieldData,
171171
programId,
172172
querySingleResource,

src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js

+50-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
withDefaultFieldContainer,
3434
withDefaultShouldUpdateInterface,
3535
VirtualizedSelectField,
36+
SingleOrgUnitSelectField,
3637
} from '../../FormFields/New';
3738
import { statusTypes, translatedStatusTypes } from '../../../events/statusTypes';
3839
import labelTypeClasses from '../DataEntry/dataEntryFieldLabels.module.css';
@@ -48,6 +49,7 @@ import {
4849
withDataEntryFields,
4950
} from '../../DataEntryDhis2Helpers/';
5051
import type { UserFormField } from '../../FormFields/UserField';
52+
import { getOrgUnitValidatorContainers } from '../DataEntry/fieldValidators';
5153

5254
const tabMode = Object.freeze({
5355
REPORT: 'REPORT',
@@ -197,6 +199,47 @@ const buildScheduleDateSettingsFn = () => {
197199
return scheduleDateSettings;
198200
};
199201

202+
const buildOrgUnitSettingsFn = () => {
203+
const orgUnitComponent =
204+
withCalculateMessages(overrideMessagePropNames)(
205+
withFocusSaver()(
206+
withDefaultFieldContainer()(
207+
withDefaultShouldUpdateInterface()(
208+
withLabel({
209+
onGetUseVerticalOrientation: (props: Object) => props.formHorizontal,
210+
onGetCustomFieldLabeClass: (props: Object) =>
211+
`${props.fieldOptions.fieldLabelMediaBasedClass} ${labelTypeClasses.dateLabel}`,
212+
})(
213+
withDisplayMessages()(
214+
withInternalChangeHandler()(
215+
withFilterProps(defaultFilterProps)(SingleOrgUnitSelectField),
216+
),
217+
),
218+
),
219+
),
220+
),
221+
),
222+
);
223+
224+
const orgUnitSettings = {
225+
getComponent: () => orgUnitComponent,
226+
getComponentProps: (props: Object) => createComponentProps(props, {
227+
width: props && props.formHorizontal ? 150 : 350,
228+
label: i18n.t('Organisation unit'),
229+
required: true,
230+
}),
231+
getPropName: () => 'orgUnit',
232+
getValidatorContainers: () => getOrgUnitValidatorContainers(),
233+
getMeta: () => ({
234+
placement: placements.TOP,
235+
section: dataEntrySectionNames.BASICINFO,
236+
}),
237+
};
238+
239+
return orgUnitSettings;
240+
};
241+
242+
200243
const pointComponent = withCalculateMessages(overrideMessagePropNames)(
201244
withFocusSaver()(
202245
withDefaultFieldContainer()(
@@ -252,15 +295,15 @@ const buildGeometrySettingsFn = () => ({
252295
label: i18n.t('Area'),
253296
dialogLabel: i18n.t('Area'),
254297
required: false,
255-
orgUnitId: props.orgUnit?.id,
298+
orgUnitId: props.orgUnitIdFieldValue,
256299
});
257300
}
258301
return createComponentProps(props, {
259302
width: props && props.formHorizontal ? 150 : '100%',
260303
label: i18n.t('Coordinate'),
261304
dialogLabel: i18n.t('Coordinate'),
262305
required: false,
263-
orgUnitId: props.orgUnit?.id,
306+
orgUnitId: props.orgUnitIdFieldValue,
264307
});
265308
},
266309
getPropName: () => 'geometry',
@@ -359,7 +402,8 @@ const saveHandlerConfig = {
359402
const AOCFieldBuilderHOC = withAOCFieldBuilder({})(withDataEntryFields(getCategoryOptionsSettingsFn())(DataEntry));
360403
const CleanUpHOC = withCleanUp()(AOCFieldBuilderHOC);
361404
const GeometryField = withDataEntryFieldIfApplicable(buildGeometrySettingsFn())(CleanUpHOC);
362-
const ScheduleDateField = withDataEntryField(buildScheduleDateSettingsFn())(GeometryField);
405+
const OrgUnitField = withDataEntryField(buildOrgUnitSettingsFn())(GeometryField);
406+
const ScheduleDateField = withDataEntryField(buildScheduleDateSettingsFn())(OrgUnitField);
363407
const ReportDateField = withDataEntryField(buildReportDateSettingsFn())(ScheduleDateField);
364408
const SaveableDataEntry = withSaveHandler(saveHandlerConfig)(withMainButton()(ReportDateField));
365409
const CancelableDataEntry = withCancelButton(getCancelOptions)(SaveableDataEntry);
@@ -398,6 +442,7 @@ type Props = {
398442
enrollmentId?: string,
399443
isCompleted?: boolean,
400444
assignee?: UserFormField | null,
445+
orgUnitFieldValue: ?OrgUnit,
401446
};
402447

403448

@@ -493,6 +538,7 @@ class EditEventDataEntryPlain extends Component<Props, State> {
493538
dataEntryId,
494539
orgUnit,
495540
programId,
541+
orgUnitFieldValue,
496542
onUpdateDataEntryField,
497543
onUpdateField,
498544
onStartAsyncUpdateField,
@@ -512,6 +558,7 @@ class EditEventDataEntryPlain extends Component<Props, State> {
512558
onSaveAndCompleteEnrollment={onSaveAndCompleteEnrollment(orgUnit)}
513559
fieldOptions={this.fieldOptions}
514560
dataEntrySections={this.dataEntrySections}
561+
orgUnitIdFieldValue={orgUnitFieldValue?.id}
515562
orgUnit={orgUnit}
516563
orgUnitId={orgUnit?.id}
517564
programId={programId}

src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container.js

+2
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ const mapStateToProps = (state: ReduxState, props) => {
3232
const itemId = state.dataEntries[props.dataEntryId] && state.dataEntries[props.dataEntryId].itemId;
3333

3434
const dataEntryKey = `${props.dataEntryId}-${itemId}`;
35+
const orgUnitFieldValue = state.dataEntriesFieldsValue[dataEntryKey]?.orgUnit;
3536
const isCompleted = state.dataEntriesFieldsValue[dataEntryKey]?.complete === 'true';
3637

3738
return {
3839
ready: !state.activePage.isDataEntryLoading && !eventDetailsSection.loading,
3940
itemId,
4041
isCompleted,
42+
orgUnitFieldValue,
4143
enrolledAt: state.enrollmentDomain?.enrollment?.enrolledAt,
4244
occurredAt: state.enrollmentDomain?.enrollment?.occurredAt,
4345
eventData: state.enrollmentDomain?.enrollment?.events,

src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.epics.js

+2
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export const saveEditedEventEpic = (action$: InputObservable, store: ReduxStore,
121121
const serverData = {
122122
events: [{
123123
...mainDataServerValues,
124+
orgUnit: mainDataServerValues.orgUnit.id,
124125
attributeOptionCombo: undefined,
125126
dataValues: formFoundation
126127
.getElements()
@@ -267,6 +268,7 @@ export const saveEventAndCompleteEnrollmentEpic = (action$: InputObservable, sto
267268

268269
const editEvent = {
269270
...mainDataServerValues,
271+
orgUnit: mainDataServerValues.orgUnit.id,
270272
attributeOptionCombo: undefined,
271273
dataValues: formFoundation
272274
.getElements()

src/core_modules/capture-core/components/WidgetEventEdit/ViewEventDataEntry/ViewEventDataEntry.component.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ const buildOrgUnitSettingsFn = () => {
150150
label: i18n.t('Organisation unit'),
151151
valueConverter: value => dataElement.convertValue(value, valueConvertFn),
152152
}),
153-
getPropName: () => 'orgUnitId',
153+
getPropName: () => 'orgUnit',
154154
getMeta: () => ({
155155
placement: placements.TOP,
156156
section: dataEntrySectionNames.BASICINFO,

0 commit comments

Comments
 (0)