From 199cb0e1930ba282dc8a730209541bc4259916e3 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:28:19 +0000 Subject: [PATCH 01/40] fix: update orgUnit type to allow null and enhance form value handling --- i18n/en.pot | 4 ++-- .../DataEntry/ProgramRules/getRulesActionsForTEI.ts | 2 +- .../WidgetProfile/DataEntry/hooks/useFormValues.ts | 8 +++++--- .../WidgetProfile/DataEntry/hooks/useLifecycle.ts | 13 +++++++++---- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index cb89718162..1f4f701420 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-17T06:08:25.172Z\n" -"PO-Revision-Date: 2026-08-17T06:08:25.172Z\n" +"POT-Creation-Date: 2026-08-18T11:28:21.619Z\n" +"PO-Revision-Date: 2026-08-18T11:28:21.620Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts index 5b32b6880f..e1ec8395c0 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts @@ -69,7 +69,7 @@ export const getRulesActionsForTEIAsync = async ({ }: { foundation: RenderFoundation; formId: string; - orgUnit: OrgUnit; + orgUnit: OrgUnit | null; enrollmentData?: EnrollmentData; teiValues?: TEIValues; trackedEntityAttributes?: TrackedEntityAttributes; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts index 39323c42c5..fac7436981 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts @@ -45,10 +45,12 @@ const buildFormValues = async ({ export const useFormValues = ({ formFoundation, orgUnit, + orgUnitId, clientAttributesWithSubvalues, }: { formFoundation: RenderFoundation; orgUnit: any; + orgUnitId?: string; clientAttributesWithSubvalues: Array; }) => { const [formValues, setFormValues] = useState({}); @@ -58,12 +60,12 @@ export const useFormValues = ({ useEffect(() => { if ( - orgUnit?.id && + (orgUnit?.id || !orgUnitId) && Object.entries(formFoundation).length > 0 && Object.entries(formValues).length === 0 && formValuesReadyRef.current === false ) { - const staticPatternValues = { orgUnitCode: orgUnit.code }; + const staticPatternValues = { orgUnitCode: orgUnit?.code ?? '' }; const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); formValuesReadyRef.current = true; buildFormValues({ @@ -75,7 +77,7 @@ export const useFormValues = ({ querySingleResource, }); } - }, [formFoundation, clientAttributesWithSubvalues, formValues, formValuesReadyRef, orgUnit, dataEngine]); + }, [formFoundation, clientAttributesWithSubvalues, formValues, formValuesReadyRef, orgUnit, orgUnitId, dataEngine]); return { formValues, clientValues }; }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts index d819659829..a8a6cac8db 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts @@ -60,7 +60,12 @@ export const useLifecycle = ({ const orgUnit: any = useCoreOrgUnit(orgUnitId).orgUnit; const rulesContainer: ProgramRulesContainer = useRulesContainer(programAPI); const formFoundation: RenderFoundation = useFormFoundation(programAPI, dataEntryFormConfig); - const { formValues, clientValues } = useFormValues({ formFoundation, clientAttributesWithSubvalues, orgUnit }); + const { formValues, clientValues } = useFormValues({ + formFoundation, + clientAttributesWithSubvalues, + orgUnit, + orgUnitId, + }); const { formGeometryValues, clientGeometryValues } = useGeometryValues({ geometry, featureType: programAPI.trackedEntityType.featureType, @@ -88,8 +93,7 @@ export const useLifecycle = ({ useEffect(() => { if ( awaitingInitialRulesExecution.current && - orgUnit && - Object.entries(orgUnit).length > 0 && + (!orgUnitId || (orgUnit && Object.entries(orgUnit).length > 0)) && Object.entries(formFoundation).length > 0 && Object.entries(clientValues).length > 0 && Object.entries(rulesContainer).length > 0 @@ -98,7 +102,7 @@ export const useLifecycle = ({ getRulesActionsForTEIAsync({ foundation: formFoundation, formId: `${dataEntryId}-${itemId}`, - orgUnit, + orgUnit: orgUnit ?? null, trackedEntityAttributes: programTrackedEntityAttributes, teiValues: { ...clientValues, ...clientGeometryValues }, optionSets, @@ -117,6 +121,7 @@ export const useLifecycle = ({ }, [ dispatch, orgUnit, + orgUnitId, formFoundation, programTrackedEntityAttributes, clientAttributesWithSubvalues, From 994858372c2d33474c039bce9fcbb6458c9bb718 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:19:57 +0000 Subject: [PATCH 02/40] fix: add teiOrgUnit to WidgetProfile --- i18n/en.pot | 4 ++-- .../components/WidgetProfile/WidgetProfile.component.tsx | 6 +++--- .../WidgetProfile/hooks/useTrackedEntityInstances.ts | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 1f4f701420..2c2293d060 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-18T11:28:21.619Z\n" -"PO-Revision-Date: 2026-08-18T11:28:21.620Z\n" +"POT-Creation-Date: 2026-08-18T12:19:59.271Z\n" +"PO-Revision-Date: 2026-08-18T12:19:59.271Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index fbfcd015a9..19ca026a9f 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -92,6 +92,7 @@ const WidgetProfilePlain = ({ error: trackedEntityInstancesError, trackedEntity, trackedEntityInstanceAttributes, + teiOrgUnit, geometry, } = useTrackedEntityInstances(teiId, programId, storedAttributeValues, storedGeometry); const { @@ -203,7 +204,6 @@ const WidgetProfilePlain = ({ const { trackedEntityProp, trackedEntityForToggle } = useMemo(() => { const resolvedId = (trackedEntity && trackedEntity.trackedEntity) || teiId; const trackedEntityTypeId = program?.trackedEntityType?.id; - const teiOrgUnit = (trackedEntity as any)?.orgUnit; return { trackedEntityProp: { trackedEntity: resolvedId }, trackedEntityForToggle: trackedEntity && trackedEntityTypeId && teiOrgUnit @@ -214,7 +214,7 @@ const WidgetProfilePlain = ({ } : null, }; - }, [trackedEntity, program, teiId]); + }, [trackedEntity, program, teiId, teiOrgUnit]); const widgetHeader = (
@@ -270,7 +270,7 @@ const WidgetProfilePlain = ({ onEnable={handleOnEnable} programAPI={program} dataEntryFormConfig={dataEntryFormConfig} - orgUnitId={orgUnitId} + orgUnitId={orgUnitId || teiOrgUnit || ''} clientAttributesWithSubvalues={clientAttributesWithSubvalues} userRoles={userRoles} trackedEntityInstanceId={teiId} diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useTrackedEntityInstances.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useTrackedEntityInstances.ts index 6de9d9a339..6bdd484fa1 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useTrackedEntityInstances.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useTrackedEntityInstances.ts @@ -6,6 +6,7 @@ type TrackedEntityInstance = { attributes?: Array<{ attribute: string; value: string }>; geometry?: Geometry; trackedEntityType?: string; + orgUnit?: string; [key: string]: any; }; @@ -68,10 +69,12 @@ export const useTrackedEntityInstances = ( }, [storedGeometry]); - return { error, + return { + error, loading, trackedEntity: !loading && data?.trackedEntityInstance, trackedEntityInstanceAttributes: !loading && trackedEntityInstanceAttributes, + teiOrgUnit: !loading ? data?.trackedEntityInstance?.orgUnit : undefined, geometry, }; }; From 603c0724332ac7f07577d98b1047dda3bf041ec8 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:26:47 +0000 Subject: [PATCH 03/40] fix: add rule effects handling in widget profile --- i18n/en.pot | 4 ++-- .../LayoutComponentConfig.ts | 2 ++ .../ProgramRules/getRulesActionsForTEI.ts | 15 ++++++++++++++- .../WidgetProfile/DataEntry/hooks/useLifecycle.ts | 1 + .../WidgetProfile/WidgetProfile.component.tsx | 12 ++++++++++-- .../WidgetProfile/widgetProfile.types.ts | 1 + 6 files changed, 30 insertions(+), 5 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index cb89718162..784a0054f5 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-17T06:08:25.172Z\n" -"PO-Revision-Date: 2026-08-17T06:08:25.172Z\n" +"POT-Creation-Date: 2026-08-18T12:26:48.820Z\n" +"PO-Revision-Date: 2026-08-18T12:26:48.820Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts index 3b64fb4f55..30ece58d84 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts @@ -147,6 +147,7 @@ export const ProfileWidget: WidgetConfig = { onUpdateTeiAttributeValues, onDeleteTrackedEntitySuccess, onStatusToggleSuccess, + ruleEffects, }: any): WidgetProfileProps => ({ teiId, programId: program.id, @@ -154,6 +155,7 @@ export const ProfileWidget: WidgetConfig = { onUpdateTeiAttributeValues, onDeleteSuccess: onDeleteTrackedEntitySuccess, onStatusToggleSuccess, + ruleEffects, }), }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts index 5b32b6880f..1555093bc0 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts @@ -1,3 +1,4 @@ +import { effectActions } from '@dhis2/rules-engine-javascript'; import type { Enrollment, TEIValues, @@ -66,6 +67,7 @@ export const getRulesActionsForTEIAsync = async ({ programName, querySingleResource, onGetValidationContext, + isInitialLoad = false, }: { foundation: RenderFoundation; formId: string; @@ -81,6 +83,7 @@ export const getRulesActionsForTEIAsync = async ({ programName: string; querySingleResource: QuerySingleResource; onGetValidationContext: () => Record; + isInitialLoad?: boolean; }) => { const effects: OutputEffects = ruleEngine().getProgramRuleEffects({ programRulesContainer: rulesContainer, @@ -94,7 +97,17 @@ export const getRulesActionsForTEIAsync = async ({ selectedUserRoles: userRoles, optionSets, }); - const effectsHierarchy = buildEffectsHierarchy(postProcessRulesEffects(effects, foundation)); + + const flatEffects = postProcessRulesEffects(effects, foundation); + + // On initial load the form is pre-populated with existing values, so the rules engine + // would mark hidden fields as hadValue=true and show the "was blanked out" notice — + // even though the user didn't take any action. Strip it here before building the hierarchy. + const effectsHierarchy = buildEffectsHierarchy( + isInitialLoad + ? flatEffects.map(e => (e.type === effectActions.HIDE_FIELD ? { ...e, hadValue: false } : e)) + : flatEffects, + ); const effectsWithValidations = await validateAssignEffects({ dataElements: foundation.getElements(), effects: effectsHierarchy, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts index d819659829..67e8d3d97f 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts @@ -110,6 +110,7 @@ export const useLifecycle = ({ programName: programAPI.displayName, querySingleResource, onGetValidationContext, + isInitialLoad: true, }).then((action) => { dispatch(action); }); diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index fbfcd015a9..dc260639e8 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -7,6 +7,7 @@ import log from 'loglevel'; import { FlatList } from 'capture-ui'; import { useQueryClient } from '@tanstack/react-query'; import { errorCreator } from 'capture-core-utils'; +import { effectActions } from '@dhis2/rules-engine-javascript'; import { Widget } from '../Widget'; import { LoadingMaskElementCenter } from '../LoadingMasks'; import { NoticeBox } from '../NoticeBox'; @@ -72,6 +73,7 @@ const WidgetProfilePlain = ({ programId, readOnlyMode = false, orgUnitId = '', + ruleEffects, onUpdateTeiAttributeValues, onDeleteSuccess, onStatusToggleSuccess, @@ -131,15 +133,21 @@ const WidgetProfilePlain = ({ const teiDisplayName = useTeiDisplayName(program, storedAttributeValues, clientAttributesWithSubvalues, teiId); const displayChangelog = program?.trackedEntityType?.changelogEnabled; + const hiddenFieldIds = useMemo(() => new Set( + (ruleEffects ?? []) + .filter(effect => effect.type === effectActions.HIDE_FIELD) + .map(effect => effect.id), + ), [ruleEffects]); + const displayInListAttributes = useMemo(() => clientAttributesWithSubvalues - .filter((item: any) => item.displayInList) + .filter((item: any) => item.displayInList && !hiddenFieldIds.has(item.attribute)) .map((clientAttribute: any) => { const { attribute, key, valueType } = clientAttribute; const value = convertClientToView(clientAttribute); return { attribute, key, value, valueType, reactKey: attribute, }; - }), [clientAttributesWithSubvalues]); + }), [clientAttributesWithSubvalues, hiddenFieldIds]); const onSaveExternal = useCallback(() => { queryClient.removeQueries([ReactQueryAppNamespace, 'changelog', CHANGELOG_ENTITY_TYPES.TRACKED_ENTITY, teiId]); diff --git a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts index 0888fab937..ceb62b046a 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts @@ -3,6 +3,7 @@ export type Props = { programId: string; orgUnitId: string; readOnlyMode?: boolean; + ruleEffects?: Array<{ type: string; id: string; [key: string]: any }>; onUpdateTeiAttributeValues?: (attributes: Array<{ [key: string]: string }>, teiDisplayName: string) => void; onDeleteSuccess?: () => void; onStatusToggleSuccess?: () => void; From dc3d6719b56c8c0c017901528d5f68a4de848339 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:39:42 +0000 Subject: [PATCH 04/40] fix: handle orgUnit as nullable and enforce orgUnitId requirement in form values --- i18n/en.pot | 4 ++-- .../components/WidgetProfile/DataEntry/dataEntry.actions.ts | 2 +- .../components/WidgetProfile/DataEntry/hooks/useFormValues.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 2c2293d060..10aeceab6f 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-18T12:19:59.271Z\n" -"PO-Revision-Date: 2026-08-18T12:19:59.271Z\n" +"POT-Creation-Date: 2026-08-18T12:39:44.433Z\n" +"PO-Revision-Date: 2026-08-18T12:39:44.433Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts index 41f3f941f0..159afcae53 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts @@ -104,7 +104,7 @@ export const getUpdateFieldActions = async ({ const rulesActions = await getRulesActionsForTEIAsync({ foundation: formFoundation, formId, - orgUnit, + orgUnit: orgUnit ?? null, enrollmentData: enrollment, teiValues: currentTEIValues, trackedEntityAttributes, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts index fac7436981..d11cf9e684 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts @@ -50,7 +50,7 @@ export const useFormValues = ({ }: { formFoundation: RenderFoundation; orgUnit: any; - orgUnitId?: string; + orgUnitId: string; clientAttributesWithSubvalues: Array; }) => { const [formValues, setFormValues] = useState({}); From 1df2b60d8b9d72eca1ecb6284029e04ec669359a Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:25:18 +0000 Subject: [PATCH 05/40] fix: replace orgUnitId with waitForOrgUnit in useFormValues and useLifecycle hooks --- i18n/en.pot | 4 ++-- .../WidgetProfile/DataEntry/dataEntry.actions.ts | 2 +- .../WidgetProfile/DataEntry/hooks/useFormValues.ts | 8 ++++---- .../WidgetProfile/DataEntry/hooks/useLifecycle.ts | 9 +++++---- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 10aeceab6f..969d5ab445 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-18T12:39:44.433Z\n" -"PO-Revision-Date: 2026-08-18T12:39:44.433Z\n" +"POT-Creation-Date: 2026-08-18T13:25:20.873Z\n" +"PO-Revision-Date: 2026-08-18T13:25:20.873Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts index 159afcae53..8fc7acb906 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts @@ -54,7 +54,7 @@ const dataEntryPropsToInclude: Array = [ ]; type Context = { - orgUnit: OrgUnit; + orgUnit: OrgUnit | undefined; programName: string; trackedEntityAttributes?: TrackedEntityAttributes; optionSets: OptionSets; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts index d11cf9e684..9ca8f8c582 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts @@ -45,12 +45,12 @@ const buildFormValues = async ({ export const useFormValues = ({ formFoundation, orgUnit, - orgUnitId, + waitForOrgUnit, clientAttributesWithSubvalues, }: { formFoundation: RenderFoundation; orgUnit: any; - orgUnitId: string; + waitForOrgUnit: boolean; clientAttributesWithSubvalues: Array; }) => { const [formValues, setFormValues] = useState({}); @@ -60,7 +60,7 @@ export const useFormValues = ({ useEffect(() => { if ( - (orgUnit?.id || !orgUnitId) && + (!waitForOrgUnit || orgUnit?.id) && Object.entries(formFoundation).length > 0 && Object.entries(formValues).length === 0 && formValuesReadyRef.current === false @@ -77,7 +77,7 @@ export const useFormValues = ({ querySingleResource, }); } - }, [formFoundation, clientAttributesWithSubvalues, formValues, formValuesReadyRef, orgUnit, orgUnitId, dataEngine]); + }, [formFoundation, clientAttributesWithSubvalues, formValues, formValuesReadyRef, orgUnit, waitForOrgUnit, dataEngine]); return { formValues, clientValues }; }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts index a8a6cac8db..4c5bbd4045 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts @@ -57,14 +57,15 @@ export const useLifecycle = ({ const enrollment: EnrollmentData = useSelector(({ enrollmentDomain }: any) => enrollmentDomain?.enrollment); const dataElements: DataElements = useDataElements(programAPI); const otherEvents = useEvents(enrollment, dataElements); - const orgUnit: any = useCoreOrgUnit(orgUnitId).orgUnit; + const { orgUnit, error: orgUnitError } = useCoreOrgUnit(orgUnitId); + const waitForOrgUnit = !!orgUnitId && !orgUnitError; const rulesContainer: ProgramRulesContainer = useRulesContainer(programAPI); const formFoundation: RenderFoundation = useFormFoundation(programAPI, dataEntryFormConfig); const { formValues, clientValues } = useFormValues({ formFoundation, clientAttributesWithSubvalues, orgUnit, - orgUnitId, + waitForOrgUnit, }); const { formGeometryValues, clientGeometryValues } = useGeometryValues({ geometry, @@ -93,7 +94,7 @@ export const useLifecycle = ({ useEffect(() => { if ( awaitingInitialRulesExecution.current && - (!orgUnitId || (orgUnit && Object.entries(orgUnit).length > 0)) && + (!waitForOrgUnit || (orgUnit && Object.entries(orgUnit).length > 0)) && Object.entries(formFoundation).length > 0 && Object.entries(clientValues).length > 0 && Object.entries(rulesContainer).length > 0 @@ -121,7 +122,7 @@ export const useLifecycle = ({ }, [ dispatch, orgUnit, - orgUnitId, + waitForOrgUnit, formFoundation, programTrackedEntityAttributes, clientAttributesWithSubvalues, From fcc8079817f33050f83539ce4de32351a90426f5 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:25:19 +0000 Subject: [PATCH 06/40] fix: integrate rule effects handling --- i18n/en.pot | 7 ++++-- .../EnrollmentAddEventPage.container.tsx | 2 +- ...nrollmentAddEventPageDefault.container.tsx | 12 +++++++++ .../EnrollmentAddEventPageDefault.types.ts | 5 +++- .../EnrollmentEditEventPage.component.tsx | 2 ++ .../EnrollmentEditEventPage.container.tsx | 11 ++++++++ .../EnrollmentEditEventPage.types.ts | 2 ++ .../ProgramRules/getRulesActionsForTEI.ts | 13 +--------- .../DataEntry/hooks/useLifecycle.ts | 1 - .../WidgetProfile/WidgetProfile.component.tsx | 25 +++++++++++++++++-- 10 files changed, 61 insertions(+), 19 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 784a0054f5..f32cbf8225 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-18T12:26:48.820Z\n" -"PO-Revision-Date: 2026-08-18T12:26:48.820Z\n" +"POT-Creation-Date: 2026-08-18T13:25:20.964Z\n" +"PO-Revision-Date: 2026-08-18T13:25:20.965Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." @@ -1703,6 +1703,9 @@ msgstr "No attributes configured for {{trackedEntityTypeName}}" msgid "No attributes configured" msgstr "No attributes configured" +msgid "All attributes are hidden by program rules" +msgstr "All attributes are hidden by program rules" + msgid "{{trackedEntityTypeName}} profile" msgstr "{{trackedEntityTypeName}} profile" diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx index a8fc2b681a..c8e2874f80 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx @@ -96,7 +96,7 @@ const EnrollmentAddEventPagePlain = ({ classes }: WithStyles) => | null | undefined} + attributeValues={attributeValues} commonDataError={Boolean(commonDataError)} trackedEntityInactive={Boolean(trackedEntityInactive)} /> diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx index 23d7df4412..ac8ca4cb7a 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx @@ -20,7 +20,10 @@ import { showEnrollmentError, updateEnrollmentAndEvents, updateOrAddEnrollmentEvents, + useRuleEffects, } from '../../common/EnrollmentOverviewDomain'; +import { useCoreOrgUnit } from '../../../../metadataRetrieval/coreOrgUnit'; +import { useTrackerProgram } from '../../../../hooks/useTrackerProgram'; import { dataEntryHasChanges as getDataEntryHasChanges } from '../../../DataEntry/common/dataEntryHasChanges'; import type { ContainerProps } from './EnrollmentAddEventPageDefault.types'; import { WidgetsForEnrollmentEventNew } from '../PageLayout/DefaultPageLayout.constants'; @@ -114,6 +117,14 @@ export const EnrollmentAddEventPageDefault = ({ const selectedProgramStage = [...program?.stages.values() ?? []].find((item: any) => item.id === stageId); const outputEffects = useWidgetDataFromStore(widgetReducerName); const hideWidgets = useHideWidgetByRuleLocations(program?.programRules.concat(selectedProgramStage?.programRules ?? [])); + const trackerProgram = useTrackerProgram(programId); + const { orgUnit } = useCoreOrgUnit(orgUnitId); + const ruleEffects = useRuleEffects({ + orgUnit, + program: trackerProgram, + apiEnrollment: enrollment ?? undefined, + apiAttributeValues: attributeValues ?? undefined, + }); const trackedEntityName = (program instanceof TrackerProgram) ? program.trackedEntityType?.name ?? '' : ''; const rulesExecutionDependencies = useMemo(() => ({ @@ -192,6 +203,7 @@ export const EnrollmentAddEventPageDefault = ({ onAddNew={handleAddNew} widgetEffects={outputEffects} hideWidgets={hideWidgets} + ruleEffects={ruleEffects} widgetReducerName={widgetReducerName} pageFailure={commonDataError} rulesExecutionDependencies={rulesExecutionDependencies} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts index 28c8458fce..f47b0ccf0b 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts @@ -1,5 +1,7 @@ +import type { OutputEffect } from '@dhis2/rules-engine-javascript'; import type { ApiEnrollmentEvent } from 'capture-core-utils/types/api-types'; import type { HideWidgets, WidgetEffects } from '../../common/EnrollmentOverviewDomain'; +import type { AttributeValue } from '../../common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; import type { ExternalSaveHandler } from '../../../WidgetEnrollmentEventNew'; import type { PageLayoutConfig, @@ -26,6 +28,7 @@ export type Props = { onEnrollmentSuccess: () => void; widgetEffects: WidgetEffects | null | undefined; hideWidgets: HideWidgets; + ruleEffects?: Array; rulesExecutionDependencies: Record; pageFailure: boolean; ready: boolean; @@ -48,7 +51,7 @@ export type ContainerProps = { occurredAt?: string; enrollment?: string; } | null | undefined; - attributeValues: Record | null | undefined; + attributeValues: Array | null | undefined; commonDataError: boolean; trackedEntityInactive: boolean; }; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx index b5a96db2d1..0900a4e384 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx @@ -27,6 +27,7 @@ export const EnrollmentEditEventPageComponent = ({ teiDisplayName, widgetEffects, hideWidgets, + ruleEffects, onDelete, onAddNew, onLinkedRecordClick, @@ -118,6 +119,7 @@ export const EnrollmentEditEventPageComponent = ({ pageStatus={pageStatus} widgetEffects={widgetEffects} hideWidgets={hideWidgets} + ruleEffects={ruleEffects} onDelete={onDelete} onAddNew={onAddNew} eventAccess={eventAccess} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx index 6339759652..218d5269ca 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx @@ -14,6 +14,7 @@ import { updateEnrollmentAndEvents, updateEnrollmentEvent, useCommonEnrollmentDomainData, + useRuleEffects, deleteEnrollmentEvent, deleteEnrollmentEventRelationship, updateOrAddEnrollmentEvents, @@ -22,6 +23,7 @@ import { } from '../common/EnrollmentOverviewDomain'; import { useTeiDisplayName } from '../common/EnrollmentOverviewDomain/useTeiDisplayName'; import { useTrackerProgram } from '../../../hooks/useTrackerProgram'; +import { useCoreOrgUnit } from '../../../metadataRetrieval/coreOrgUnit'; import { pageStatuses } from './EnrollmentEditEventPage.constants'; import { EnrollmentEditEventPageComponent } from './EnrollmentEditEventPage.component'; import { useWidgetDataFromStore } from '../EnrollmentAddEvent/hooks'; @@ -156,6 +158,14 @@ const EnrollmentEditEventPageWithContextPlain = ({ const hideWidgets = useHideWidgetByRuleLocations( program.programRules.concat(programStage?.programRules as ProgramRule[]), ); + const { orgUnit } = useCoreOrgUnit(orgUnitId); + const attributeValues = useSelector(({ enrollmentDomain }: any) => enrollmentDomain?.attributeValues); + const ruleEffects = useRuleEffects({ + orgUnit, + program, + apiEnrollment: enrollmentSite, + apiAttributeValues: attributeValues, + }); const onDeleteTrackedEntitySuccess = useCallback(() => { navigate(`/?${buildUrlQueryString({ orgUnitId, programId })}`); @@ -327,6 +337,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ onBackToViewEvent={onBackToViewEvent} widgetEffects={outputEffects} hideWidgets={hideWidgets} + ruleEffects={ruleEffects} teiId={teiId} enrollmentId={enrollmentId} eventId={eventId} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts index 27e7190010..2a47b6ea34 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts @@ -1,3 +1,4 @@ +import type { OutputEffect } from '@dhis2/rules-engine-javascript'; import type { ProgramStage } from '../../../metaData'; import { Program } from '../../../metaData'; import type { HideWidgets, WidgetEffects } from '../common/EnrollmentOverviewDomain'; @@ -12,6 +13,7 @@ export type PlainProps = { programStage?: ProgramStage | null; widgetEffects: WidgetEffects; hideWidgets: HideWidgets; + ruleEffects?: Array; teiId: string; enrollmentId: string; eventId: string; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts index 1555093bc0..81e6d887b1 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts @@ -1,4 +1,3 @@ -import { effectActions } from '@dhis2/rules-engine-javascript'; import type { Enrollment, TEIValues, @@ -67,7 +66,6 @@ export const getRulesActionsForTEIAsync = async ({ programName, querySingleResource, onGetValidationContext, - isInitialLoad = false, }: { foundation: RenderFoundation; formId: string; @@ -83,7 +81,6 @@ export const getRulesActionsForTEIAsync = async ({ programName: string; querySingleResource: QuerySingleResource; onGetValidationContext: () => Record; - isInitialLoad?: boolean; }) => { const effects: OutputEffects = ruleEngine().getProgramRuleEffects({ programRulesContainer: rulesContainer, @@ -99,15 +96,7 @@ export const getRulesActionsForTEIAsync = async ({ }); const flatEffects = postProcessRulesEffects(effects, foundation); - - // On initial load the form is pre-populated with existing values, so the rules engine - // would mark hidden fields as hadValue=true and show the "was blanked out" notice — - // even though the user didn't take any action. Strip it here before building the hierarchy. - const effectsHierarchy = buildEffectsHierarchy( - isInitialLoad - ? flatEffects.map(e => (e.type === effectActions.HIDE_FIELD ? { ...e, hadValue: false } : e)) - : flatEffects, - ); + const effectsHierarchy = buildEffectsHierarchy(flatEffects); const effectsWithValidations = await validateAssignEffects({ dataElements: foundation.getElements(), effects: effectsHierarchy, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts index 67e8d3d97f..d819659829 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts @@ -110,7 +110,6 @@ export const useLifecycle = ({ programName: programAPI.displayName, querySingleResource, onGetValidationContext, - isInitialLoad: true, }).then((action) => { dispatch(action); }); diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index dc260639e8..9a75bee418 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -123,7 +123,10 @@ const WidgetProfilePlain = ({ return null; }, [isEditable, readOnlyMode, hasNoAttributes]); - const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched); + // ruleEffects starts as undefined and resolves to an array once computed. + // Delay rendering the list until rules are ready so hidden attributes never flash visible. + const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched) + || ruleEffects === undefined; const error = computeError(programsError, trackedEntityInstancesError, userRolesError); const clientAttributesWithSubvalues = useClientAttributesWithSubvalues( teiId, @@ -149,6 +152,13 @@ const WidgetProfilePlain = ({ }; }), [clientAttributesWithSubvalues, hiddenFieldIds]); + const allDisplayInListHiddenByRules = useMemo(() => { + if (!hiddenFieldIds.size) return false; + const displayInListItems = clientAttributesWithSubvalues.filter((item: any) => item.displayInList); + return displayInListItems.length > 0 + && displayInListItems.every((item: any) => hiddenFieldIds.has(item.attribute)); + }, [clientAttributesWithSubvalues, hiddenFieldIds]); + const onSaveExternal = useCallback(() => { queryClient.removeQueries([ReactQueryAppNamespace, 'changelog', CHANGELOG_ENTITY_TYPES.TRACKED_ENTITY, teiId]); }, [queryClient, teiId]); @@ -194,6 +204,16 @@ const WidgetProfilePlain = ({ ); } + if (allDisplayInListHiddenByRules) { + return ( +
+

+ {i18n.t('All attributes are hidden by program rules')} +

+
+ ); + } + return (
@@ -206,7 +226,8 @@ const WidgetProfilePlain = ({ const handleOpen = useCallback(() => setOpenStatus(true), [setOpenStatus]); const handleClose = useCallback(() => setOpenStatus(false), [setOpenStatus]); - const isEmptyList = !loading && !error && !hasNoAttributes && displayInListAttributes.length === 0; + const isEmptyList = !loading && !error && !hasNoAttributes + && displayInListAttributes.length === 0 && !allDisplayInListHiddenByRules; const { trackedEntityProp, trackedEntityForToggle } = useMemo(() => { const resolvedId = (trackedEntity && trackedEntity.trackedEntity) || teiId; From 45c023f59a23213fabbcf8cc060fe834c6508aba Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:00:39 +0000 Subject: [PATCH 07/40] fix: prevent flashing of hidden attributes by delaying rendering until rule effects are ready --- i18n/en.pot | 4 ++-- .../components/WidgetProfile/WidgetProfile.component.tsx | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index f32cbf8225..f18e0dfa14 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-18T13:25:20.964Z\n" -"PO-Revision-Date: 2026-08-18T13:25:20.965Z\n" +"POT-Creation-Date: 2026-08-18T14:00:40.066Z\n" +"PO-Revision-Date: 2026-08-18T14:00:40.066Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 9a75bee418..52f4ff65b0 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -123,10 +123,7 @@ const WidgetProfilePlain = ({ return null; }, [isEditable, readOnlyMode, hasNoAttributes]); - // ruleEffects starts as undefined and resolves to an array once computed. - // Delay rendering the list until rules are ready so hidden attributes never flash visible. - const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched) - || ruleEffects === undefined; + const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched); const error = computeError(programsError, trackedEntityInstancesError, userRolesError); const clientAttributesWithSubvalues = useClientAttributesWithSubvalues( teiId, From 26f691d2bc447c7872c1ddf2b57fa586ba8411ae Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:27:29 +0000 Subject: [PATCH 08/40] Revert "Merge remote-tracking branch 'origin/hv/fix/DHIS2-21937_profile-widget-infinite-loading-without-org-unit' into hv/fix/DHIS2-18620_hidden-TEA-displayed-in-TEI-profile" This reverts commit 2054d1b1fa3e7505d30519ad5fde7d50c8ea9e76, reversing changes made to 45c023f59a23213fabbcf8cc060fe834c6508aba. --- i18n/en.pot | 4 ++-- .../ProgramRules/getRulesActionsForTEI.ts | 2 +- .../WidgetProfile/DataEntry/dataEntry.actions.ts | 4 ++-- .../DataEntry/hooks/useFormValues.ts | 8 +++----- .../DataEntry/hooks/useLifecycle.ts | 16 +++++----------- .../WidgetProfile/WidgetProfile.component.tsx | 6 +++--- .../hooks/useTrackedEntityInstances.ts | 5 +---- 7 files changed, 17 insertions(+), 28 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 3b038e47f3..f18e0dfa14 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-18T14:14:57.605Z\n" -"PO-Revision-Date: 2026-08-18T14:14:57.605Z\n" +"POT-Creation-Date: 2026-08-18T14:00:40.066Z\n" +"PO-Revision-Date: 2026-08-18T14:00:40.066Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts index 580251eb91..81e6d887b1 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts @@ -69,7 +69,7 @@ export const getRulesActionsForTEIAsync = async ({ }: { foundation: RenderFoundation; formId: string; - orgUnit: OrgUnit | null; + orgUnit: OrgUnit; enrollmentData?: EnrollmentData; teiValues?: TEIValues; trackedEntityAttributes?: TrackedEntityAttributes; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts index 8fc7acb906..41f3f941f0 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts @@ -54,7 +54,7 @@ const dataEntryPropsToInclude: Array = [ ]; type Context = { - orgUnit: OrgUnit | undefined; + orgUnit: OrgUnit; programName: string; trackedEntityAttributes?: TrackedEntityAttributes; optionSets: OptionSets; @@ -104,7 +104,7 @@ export const getUpdateFieldActions = async ({ const rulesActions = await getRulesActionsForTEIAsync({ foundation: formFoundation, formId, - orgUnit: orgUnit ?? null, + orgUnit, enrollmentData: enrollment, teiValues: currentTEIValues, trackedEntityAttributes, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts index 9ca8f8c582..39323c42c5 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useFormValues.ts @@ -45,12 +45,10 @@ const buildFormValues = async ({ export const useFormValues = ({ formFoundation, orgUnit, - waitForOrgUnit, clientAttributesWithSubvalues, }: { formFoundation: RenderFoundation; orgUnit: any; - waitForOrgUnit: boolean; clientAttributesWithSubvalues: Array; }) => { const [formValues, setFormValues] = useState({}); @@ -60,12 +58,12 @@ export const useFormValues = ({ useEffect(() => { if ( - (!waitForOrgUnit || orgUnit?.id) && + orgUnit?.id && Object.entries(formFoundation).length > 0 && Object.entries(formValues).length === 0 && formValuesReadyRef.current === false ) { - const staticPatternValues = { orgUnitCode: orgUnit?.code ?? '' }; + const staticPatternValues = { orgUnitCode: orgUnit.code }; const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); formValuesReadyRef.current = true; buildFormValues({ @@ -77,7 +75,7 @@ export const useFormValues = ({ querySingleResource, }); } - }, [formFoundation, clientAttributesWithSubvalues, formValues, formValuesReadyRef, orgUnit, waitForOrgUnit, dataEngine]); + }, [formFoundation, clientAttributesWithSubvalues, formValues, formValuesReadyRef, orgUnit, dataEngine]); return { formValues, clientValues }; }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts index 4c5bbd4045..d819659829 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts @@ -57,16 +57,10 @@ export const useLifecycle = ({ const enrollment: EnrollmentData = useSelector(({ enrollmentDomain }: any) => enrollmentDomain?.enrollment); const dataElements: DataElements = useDataElements(programAPI); const otherEvents = useEvents(enrollment, dataElements); - const { orgUnit, error: orgUnitError } = useCoreOrgUnit(orgUnitId); - const waitForOrgUnit = !!orgUnitId && !orgUnitError; + const orgUnit: any = useCoreOrgUnit(orgUnitId).orgUnit; const rulesContainer: ProgramRulesContainer = useRulesContainer(programAPI); const formFoundation: RenderFoundation = useFormFoundation(programAPI, dataEntryFormConfig); - const { formValues, clientValues } = useFormValues({ - formFoundation, - clientAttributesWithSubvalues, - orgUnit, - waitForOrgUnit, - }); + const { formValues, clientValues } = useFormValues({ formFoundation, clientAttributesWithSubvalues, orgUnit }); const { formGeometryValues, clientGeometryValues } = useGeometryValues({ geometry, featureType: programAPI.trackedEntityType.featureType, @@ -94,7 +88,8 @@ export const useLifecycle = ({ useEffect(() => { if ( awaitingInitialRulesExecution.current && - (!waitForOrgUnit || (orgUnit && Object.entries(orgUnit).length > 0)) && + orgUnit && + Object.entries(orgUnit).length > 0 && Object.entries(formFoundation).length > 0 && Object.entries(clientValues).length > 0 && Object.entries(rulesContainer).length > 0 @@ -103,7 +98,7 @@ export const useLifecycle = ({ getRulesActionsForTEIAsync({ foundation: formFoundation, formId: `${dataEntryId}-${itemId}`, - orgUnit: orgUnit ?? null, + orgUnit, trackedEntityAttributes: programTrackedEntityAttributes, teiValues: { ...clientValues, ...clientGeometryValues }, optionSets, @@ -122,7 +117,6 @@ export const useLifecycle = ({ }, [ dispatch, orgUnit, - waitForOrgUnit, formFoundation, programTrackedEntityAttributes, clientAttributesWithSubvalues, diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index d34da7c973..52f4ff65b0 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -94,7 +94,6 @@ const WidgetProfilePlain = ({ error: trackedEntityInstancesError, trackedEntity, trackedEntityInstanceAttributes, - teiOrgUnit, geometry, } = useTrackedEntityInstances(teiId, programId, storedAttributeValues, storedGeometry); const { @@ -230,6 +229,7 @@ const WidgetProfilePlain = ({ const { trackedEntityProp, trackedEntityForToggle } = useMemo(() => { const resolvedId = (trackedEntity && trackedEntity.trackedEntity) || teiId; const trackedEntityTypeId = program?.trackedEntityType?.id; + const teiOrgUnit = (trackedEntity as any)?.orgUnit; return { trackedEntityProp: { trackedEntity: resolvedId }, trackedEntityForToggle: trackedEntity && trackedEntityTypeId && teiOrgUnit @@ -240,7 +240,7 @@ const WidgetProfilePlain = ({ } : null, }; - }, [trackedEntity, program, teiId, teiOrgUnit]); + }, [trackedEntity, program, teiId]); const widgetHeader = (
@@ -296,7 +296,7 @@ const WidgetProfilePlain = ({ onEnable={handleOnEnable} programAPI={program} dataEntryFormConfig={dataEntryFormConfig} - orgUnitId={orgUnitId || teiOrgUnit || ''} + orgUnitId={orgUnitId} clientAttributesWithSubvalues={clientAttributesWithSubvalues} userRoles={userRoles} trackedEntityInstanceId={teiId} diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useTrackedEntityInstances.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useTrackedEntityInstances.ts index 6bdd484fa1..6de9d9a339 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useTrackedEntityInstances.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useTrackedEntityInstances.ts @@ -6,7 +6,6 @@ type TrackedEntityInstance = { attributes?: Array<{ attribute: string; value: string }>; geometry?: Geometry; trackedEntityType?: string; - orgUnit?: string; [key: string]: any; }; @@ -69,12 +68,10 @@ export const useTrackedEntityInstances = ( }, [storedGeometry]); - return { - error, + return { error, loading, trackedEntity: !loading && data?.trackedEntityInstance, trackedEntityInstanceAttributes: !loading && trackedEntityInstanceAttributes, - teiOrgUnit: !loading ? data?.trackedEntityInstance?.orgUnit : undefined, geometry, }; }; From 3de9aa2ae5790717c3a91d09b52036f496a2a5bd Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:28:21 +0000 Subject: [PATCH 09/40] fix: remove orgUnit dependency from rule effects condition --- i18n/en.pot | 4 ++-- .../EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index f18e0dfa14..9f7ae6427d 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-18T14:00:40.066Z\n" -"PO-Revision-Date: 2026-08-18T14:00:40.066Z\n" +"POT-Creation-Date: 2026-08-18T14:28:22.144Z\n" +"PO-Revision-Date: 2026-08-18T14:28:22.144Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts index b63f1603ab..fd6d34d80a 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts @@ -76,7 +76,7 @@ export const useRuleEffects = ({ orgUnit, program, apiEnrollment, apiAttributeVa const otherEvents = useEventsData(apiEnrollment, program); useEffect(() => { - if (orgUnit && attributeValues && enrollmentData && otherEvents) { + if (attributeValues && enrollmentData && otherEvents) { const effects = getApplicableRuleEffectsForTrackerProgram({ program, orgUnit, From 379e914965c190eca5ad257339c81eeb0f7ffd37 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:46:13 +0000 Subject: [PATCH 10/40] fix: refactor enrollment data handling and improve rule effects validation --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault.container.tsx | 15 ++++++++------- .../EnrollmentEditEventPage.container.tsx | 7 ++++--- .../EnrollmentEditEventPage.types.ts | 2 ++ .../useRuleEffects/useRuleEffects.ts | 2 +- .../ProgramRules/getRulesActionsForTEI.ts | 3 +-- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 9f7ae6427d..f621de46b7 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-18T14:28:22.144Z\n" -"PO-Revision-Date: 2026-08-18T14:28:22.144Z\n" +"POT-Creation-Date: 2026-08-18T14:46:15.075Z\n" +"PO-Revision-Date: 2026-08-18T14:46:15.075Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index bfa61fbf85..4f34de9c6a 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -53,7 +53,14 @@ export const EnrollmentPageDefault = () => { const { fromClientDate } = useTimeZoneConversion(); const { status: widgetEnrollmentStatus } = useSelector(({ widgetEnrollment }: any) => widgetEnrollment); const { enrollmentId, programId, teiId, orgUnitId } = useLocationQuery(); - const { orgUnit, error } = useCoreOrgUnit(orgUnitId); + const { + error: enrollmentsError, + enrollment, + attributeValues, + readOnly: trackedEntityInactive, + } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); + const effectiveOrgUnitId = orgUnitId ?? enrollment?.orgUnit; + const { orgUnit, error } = useCoreOrgUnit(effectiveOrgUnitId); const { onLinkedRecordClick } = useLinkedRecordClick(); const { pageLayout, @@ -65,12 +72,6 @@ export const EnrollmentPageDefault = () => { }); const program = useTrackerProgram(programId); - const { - error: enrollmentsError, - enrollment, - attributeValues, - readOnly: trackedEntityInactive, - } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const onStatusToggleSuccess = useCallback(() => { dispatch(setTrackedEntityInactiveStatus(!trackedEntityInactive)); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx index 218d5269ca..0e6f70073d 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx @@ -99,7 +99,7 @@ export const EnrollmentEditEventPage = () => { const { loading, event } = useEvent(eventId ?? ''); const { program: programId, programStage: stageId, trackedEntity: teiId, enrollment: enrollmentId } = event; const { orgUnitId, eventId: urlEventId, initMode } = useLocationQuery(); - const { enrollment: enrollmentSite, readOnly: trackedEntityInactive } = + const { enrollment: enrollmentSite, attributeValues, readOnly: trackedEntityInactive } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const storedEvent = enrollmentSite?.events?.find((item: Record) => item.event === eventId); @@ -120,6 +120,7 @@ export const EnrollmentEditEventPage = () => { orgUnitId={orgUnitId} initMode={initMode} enrollmentSite={enrollmentSite} + attributeValues={attributeValues} trackedEntityInactive={trackedEntityInactive} event={storedEvent} /> @@ -134,6 +135,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ orgUnitId, initMode, enrollmentSite, + attributeValues, trackedEntityInactive, event, }: Props) => { @@ -159,12 +161,11 @@ const EnrollmentEditEventPageWithContextPlain = ({ program.programRules.concat(programStage?.programRules as ProgramRule[]), ); const { orgUnit } = useCoreOrgUnit(orgUnitId); - const attributeValues = useSelector(({ enrollmentDomain }: any) => enrollmentDomain?.attributeValues); const ruleEffects = useRuleEffects({ orgUnit, program, apiEnrollment: enrollmentSite, - apiAttributeValues: attributeValues, + apiAttributeValues: attributeValues ?? undefined, }); const onDeleteTrackedEntitySuccess = useCallback(() => { diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts index 2a47b6ea34..5ddd7e7833 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts @@ -2,6 +2,7 @@ import type { OutputEffect } from '@dhis2/rules-engine-javascript'; import type { ProgramStage } from '../../../metaData'; import { Program } from '../../../metaData'; import type { HideWidgets, WidgetEffects } from '../common/EnrollmentOverviewDomain'; +import type { AttributeValue } from '../common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; import type { UserFormField } from '../../FormFields/UserField'; import type { LinkedRecordClick } from '../../WidgetsRelationship/WidgetTrackedEntityRelationship'; import type { @@ -72,6 +73,7 @@ export type Props = { orgUnitId: string; event: any; enrollmentSite: any; + attributeValues: Array | null | undefined; trackedEntityInactive?: boolean; initMode?: string; }; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts index fd6d34d80a..b63f1603ab 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts @@ -76,7 +76,7 @@ export const useRuleEffects = ({ orgUnit, program, apiEnrollment, apiAttributeVa const otherEvents = useEventsData(apiEnrollment, program); useEffect(() => { - if (attributeValues && enrollmentData && otherEvents) { + if (orgUnit && attributeValues && enrollmentData && otherEvents) { const effects = getApplicableRuleEffectsForTrackerProgram({ program, orgUnit, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts index 81e6d887b1..a31171a849 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts @@ -95,8 +95,7 @@ export const getRulesActionsForTEIAsync = async ({ optionSets, }); - const flatEffects = postProcessRulesEffects(effects, foundation); - const effectsHierarchy = buildEffectsHierarchy(flatEffects); + const effectsHierarchy = buildEffectsHierarchy(postProcessRulesEffects(effects, foundation)); const effectsWithValidations = await validateAssignEffects({ dataElements: foundation.getElements(), effects: effectsHierarchy, From 3f73b20a5d1a5bf6481e1e14c6275aba28dd03a0 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:32:05 +0000 Subject: [PATCH 11/40] fix: update translation for hidden attributes message and improve type handling in useRuleEffects --- i18n/en.pot | 8 ++++---- .../EnrollmentAddEventPageDefault.container.tsx | 3 +-- .../useRuleEffects/useRuleEffects.ts | 10 +++++----- .../useRuleEffects/useRuleEffects.types.ts | 2 +- .../WidgetProfile/WidgetProfile.component.tsx | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index f621de46b7..035e7dd4fa 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-18T14:46:15.075Z\n" -"PO-Revision-Date: 2026-08-18T14:46:15.075Z\n" +"POT-Creation-Date: 2026-08-19T10:32:06.534Z\n" +"PO-Revision-Date: 2026-08-19T10:32:06.534Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." @@ -1703,8 +1703,8 @@ msgstr "No attributes configured for {{trackedEntityTypeName}}" msgid "No attributes configured" msgstr "No attributes configured" -msgid "All attributes are hidden by program rules" -msgstr "All attributes are hidden by program rules" +msgid "Attributes are hidden by program rules" +msgstr "Attributes are hidden by program rules" msgid "{{trackedEntityTypeName}} profile" msgstr "{{trackedEntityTypeName}} profile" diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx index ac8ca4cb7a..db203abd3c 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx @@ -23,7 +23,6 @@ import { useRuleEffects, } from '../../common/EnrollmentOverviewDomain'; import { useCoreOrgUnit } from '../../../../metadataRetrieval/coreOrgUnit'; -import { useTrackerProgram } from '../../../../hooks/useTrackerProgram'; import { dataEntryHasChanges as getDataEntryHasChanges } from '../../../DataEntry/common/dataEntryHasChanges'; import type { ContainerProps } from './EnrollmentAddEventPageDefault.types'; import { WidgetsForEnrollmentEventNew } from '../PageLayout/DefaultPageLayout.constants'; @@ -117,7 +116,7 @@ export const EnrollmentAddEventPageDefault = ({ const selectedProgramStage = [...program?.stages.values() ?? []].find((item: any) => item.id === stageId); const outputEffects = useWidgetDataFromStore(widgetReducerName); const hideWidgets = useHideWidgetByRuleLocations(program?.programRules.concat(selectedProgramStage?.programRules ?? [])); - const trackerProgram = useTrackerProgram(programId); + const trackerProgram = program instanceof TrackerProgram ? program : undefined; const { orgUnit } = useCoreOrgUnit(orgUnitId); const ruleEffects = useRuleEffects({ orgUnit, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts index b63f1603ab..789f8b519f 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts @@ -22,8 +22,8 @@ const getClientFormattedDataValuesAsObject = (dataValues: any, elementsById: any return acc; }, {}); -const useEventsData = (enrollment: any, program: TrackerProgram) => { - const elementsById = useMemo(() => getDataElementsInProgram(program), [program]); +const useEventsData = (enrollment: any, program?: TrackerProgram) => { + const elementsById = useMemo(() => (program ? getDataElementsInProgram(program) : {}), [program]); return useMemo(() => enrollment && enrollment.events.map((event: any) => ({ eventId: event.event, @@ -59,10 +59,10 @@ const useEnrollmentData = (enrollment: any) => useMemo(() => { export const useRuleEffects = ({ orgUnit, program, apiEnrollment, apiAttributeValues }: UseRuleEffectsInput) => { const [ruleEffects, setRuleEffects] = useState(undefined); const attributesObject = useMemo(() => - program.attributes.reduce((acc: any, attribute: any) => { + (program?.attributes ?? []).reduce((acc: any, attribute: any) => { acc[attribute.id] = attribute; return acc; - }, {}), [program.attributes]); + }, {}), [program?.attributes]); const attributeValues = useMemo(() => apiAttributeValues && apiAttributeValues @@ -76,7 +76,7 @@ export const useRuleEffects = ({ orgUnit, program, apiEnrollment, apiAttributeVa const otherEvents = useEventsData(apiEnrollment, program); useEffect(() => { - if (orgUnit && attributeValues && enrollmentData && otherEvents) { + if (program && orgUnit && attributeValues && enrollmentData && otherEvents) { const effects = getApplicableRuleEffectsForTrackerProgram({ program, orgUnit, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts index 236e9804f8..650ce4d807 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts @@ -6,7 +6,7 @@ import type { export type UseRuleEffectsInput = { orgUnit?: any; - program: TrackerProgram; + program?: TrackerProgram; apiEnrollment?: EnrollmentData; apiAttributeValues?: Array; }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 52f4ff65b0..51f8daf505 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -205,7 +205,7 @@ const WidgetProfilePlain = ({ return (

- {i18n.t('All attributes are hidden by program rules')} + {i18n.t('Attributes are hidden by program rules')}

); From bcbb388ee621bf07572e76673bd616f68edd7383 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:58:14 +0000 Subject: [PATCH 12/40] fix: update loading state computation to handle undefined rule effects --- i18n/en.pot | 4 ++-- .../components/WidgetProfile/WidgetProfile.component.tsx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 035e7dd4fa..d8e776aac1 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-19T10:32:06.534Z\n" -"PO-Revision-Date: 2026-08-19T10:32:06.534Z\n" +"POT-Creation-Date: 2026-08-19T11:58:15.831Z\n" +"PO-Revision-Date: 2026-08-19T11:58:15.831Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 51f8daf505..746204f782 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -123,7 +123,8 @@ const WidgetProfilePlain = ({ return null; }, [isEditable, readOnlyMode, hasNoAttributes]); - const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched); + const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched) + || ruleEffects === undefined; const error = computeError(programsError, trackedEntityInstancesError, userRolesError); const clientAttributesWithSubvalues = useClientAttributesWithSubvalues( teiId, From cb4807ab8058fc97826d79714e6c546f191b455b Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:26:15 +0000 Subject: [PATCH 13/40] fix: update loading state computation to include ruleEffectsPending --- i18n/en.pot | 4 ++-- .../components/WidgetProfile/WidgetProfile.component.tsx | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index d8e776aac1..e008414b20 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-19T11:58:15.831Z\n" -"PO-Revision-Date: 2026-08-19T11:58:15.831Z\n" +"POT-Creation-Date: 2026-08-19T12:26:16.045Z\n" +"PO-Revision-Date: 2026-08-19T12:26:16.045Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 746204f782..81887ade4f 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -60,7 +60,8 @@ const computeLoadingState = ( trackedEntityInstancesLoading: boolean, userRolesLoading: boolean, configIsFetched: boolean, -) => programsLoading || trackedEntityInstancesLoading || userRolesLoading || !configIsFetched; + ruleEffectsPending: boolean, +) => programsLoading || trackedEntityInstancesLoading || userRolesLoading || !configIsFetched || ruleEffectsPending; const computeError = ( programsError: any, @@ -123,8 +124,9 @@ const WidgetProfilePlain = ({ return null; }, [isEditable, readOnlyMode, hasNoAttributes]); - const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched) - || ruleEffects === undefined; + const loading = computeLoadingState( + programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched, ruleEffects === undefined, + ); const error = computeError(programsError, trackedEntityInstancesError, userRolesError); const clientAttributesWithSubvalues = useClientAttributesWithSubvalues( teiId, From c3b69471d3b22a01c75ad4ccf279181911c5a277 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:45:44 +0000 Subject: [PATCH 14/40] fix: update orgUnitId prop to use effectiveOrgUnitId in EnrollmentPageDefault --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault/EnrollmentPageDefault.container.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index e008414b20..3999432a7b 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-19T12:26:16.045Z\n" -"PO-Revision-Date: 2026-08-19T12:26:16.045Z\n" +"POT-Creation-Date: 2026-08-19T12:45:45.760Z\n" +"PO-Revision-Date: 2026-08-19T12:45:45.760Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index 4f34de9c6a..2969835fbf 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -205,7 +205,7 @@ export const EnrollmentPageDefault = () => { currentPage={EnrollmentPageKeys.OVERVIEW} availableWidgets={WidgetsForEnrollmentPageDefault} teiId={teiId} - orgUnitId={orgUnitId} + orgUnitId={effectiveOrgUnitId} program={program} stages={stages} events={enrollment?.events} From 46a43ee42bd70fb81d1f1ef2f9663d5bf19b3070 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:49:44 +0000 Subject: [PATCH 15/40] fix: update orgUnitId usage and improve loading state computation in Enrollment components --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault.container.tsx | 5 ++--- .../EnrollmentAddEventPageDefault.container.tsx | 8 ++++++-- .../EnrollmentAddEventPageDefault.types.ts | 1 + .../components/WidgetProfile/WidgetProfile.component.tsx | 5 ++--- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 3999432a7b..0f81bec781 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-19T12:45:45.760Z\n" -"PO-Revision-Date: 2026-08-19T12:45:45.760Z\n" +"POT-Creation-Date: 2026-08-20T14:49:45.572Z\n" +"PO-Revision-Date: 2026-08-20T14:49:45.572Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index 2969835fbf..ebd26b530c 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -59,8 +59,7 @@ export const EnrollmentPageDefault = () => { attributeValues, readOnly: trackedEntityInactive, } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); - const effectiveOrgUnitId = orgUnitId ?? enrollment?.orgUnit; - const { orgUnit, error } = useCoreOrgUnit(effectiveOrgUnitId); + const { orgUnit, error } = useCoreOrgUnit(orgUnitId); const { onLinkedRecordClick } = useLinkedRecordClick(); const { pageLayout, @@ -205,7 +204,7 @@ export const EnrollmentPageDefault = () => { currentPage={EnrollmentPageKeys.OVERVIEW} availableWidgets={WidgetsForEnrollmentPageDefault} teiId={teiId} - orgUnitId={effectiveOrgUnitId} + orgUnitId={orgUnitId} program={program} stages={stages} events={enrollment?.events} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx index db203abd3c..eee137b741 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx @@ -30,6 +30,9 @@ import { EnrollmentAddEventPageDefaultComponent } from './EnrollmentAddEventPage import { convertEventAttributeOptions } from '../../../../events/convertEventAttributeOptions'; import { TrackerProgram } from '../../../../metaData'; +const getTrackerProgram = (program: any): TrackerProgram | undefined => + (program instanceof TrackerProgram ? program : undefined); + export const EnrollmentAddEventPageDefault = ({ pageLayout, enrollment, @@ -116,8 +119,9 @@ export const EnrollmentAddEventPageDefault = ({ const selectedProgramStage = [...program?.stages.values() ?? []].find((item: any) => item.id === stageId); const outputEffects = useWidgetDataFromStore(widgetReducerName); const hideWidgets = useHideWidgetByRuleLocations(program?.programRules.concat(selectedProgramStage?.programRules ?? [])); - const trackerProgram = program instanceof TrackerProgram ? program : undefined; - const { orgUnit } = useCoreOrgUnit(orgUnitId); + const trackerProgram = getTrackerProgram(program); + const effectiveOrgUnitId = orgUnitId ?? enrollment?.orgUnit; + const { orgUnit } = useCoreOrgUnit(effectiveOrgUnitId); const ruleEffects = useRuleEffects({ orgUnit, program: trackerProgram, diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts index f47b0ccf0b..029b84e539 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts @@ -50,6 +50,7 @@ export type ContainerProps = { enrolledAt?: string; occurredAt?: string; enrollment?: string; + orgUnit?: string; } | null | undefined; attributeValues: Array | null | undefined; commonDataError: boolean; diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 81887ade4f..1c9e974d82 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -60,8 +60,7 @@ const computeLoadingState = ( trackedEntityInstancesLoading: boolean, userRolesLoading: boolean, configIsFetched: boolean, - ruleEffectsPending: boolean, -) => programsLoading || trackedEntityInstancesLoading || userRolesLoading || !configIsFetched || ruleEffectsPending; +) => programsLoading || trackedEntityInstancesLoading || userRolesLoading || !configIsFetched; const computeError = ( programsError: any, @@ -125,7 +124,7 @@ const WidgetProfilePlain = ({ }, [isEditable, readOnlyMode, hasNoAttributes]); const loading = computeLoadingState( - programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched, ruleEffects === undefined, + programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched, ); const error = computeError(programsError, trackedEntityInstancesError, userRolesError); const clientAttributesWithSubvalues = useClientAttributesWithSubvalues( From d80110f20340d01932953f55e31fd5afdc1d5ae9 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Thu, 20 Aug 2026 15:57:27 +0000 Subject: [PATCH 16/40] fix: clean up --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault.container.tsx | 4 +++- .../EnrollmentAddEventPageDefault.container.tsx | 15 --------------- .../EnrollmentEditEventPage.component.tsx | 2 -- .../EnrollmentEditEventPage.container.tsx | 15 +-------------- .../EnrollmentEditEventPage.types.ts | 4 ---- .../useRuleEffects/useRuleEffects.ts | 10 +++++----- .../useRuleEffects/useRuleEffects.types.ts | 2 +- 8 files changed, 12 insertions(+), 44 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 0f81bec781..fbbd4728ea 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-20T14:49:45.572Z\n" -"PO-Revision-Date: 2026-08-20T14:49:45.572Z\n" +"POT-Creation-Date: 2026-08-20T15:57:28.252Z\n" +"PO-Revision-Date: 2026-08-20T15:57:28.252Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index ebd26b530c..d4caeb7ee3 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -60,6 +60,8 @@ export const EnrollmentPageDefault = () => { readOnly: trackedEntityInactive, } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const { orgUnit, error } = useCoreOrgUnit(orgUnitId); + const programOwnerId = useSelector(({ enrollmentPage }: any) => enrollmentPage.programOwners?.[programId]); + const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); const { onLinkedRecordClick } = useLinkedRecordClick(); const { pageLayout, @@ -94,7 +96,7 @@ export const EnrollmentPageDefault = () => { } const ruleEffects = useRuleEffects({ - orgUnit, + orgUnit: programOwnerOrgUnit ?? orgUnit, program, apiEnrollment: enrollment, apiAttributeValues: attributeValues, diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx index eee137b741..23d7df4412 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx @@ -20,9 +20,7 @@ import { showEnrollmentError, updateEnrollmentAndEvents, updateOrAddEnrollmentEvents, - useRuleEffects, } from '../../common/EnrollmentOverviewDomain'; -import { useCoreOrgUnit } from '../../../../metadataRetrieval/coreOrgUnit'; import { dataEntryHasChanges as getDataEntryHasChanges } from '../../../DataEntry/common/dataEntryHasChanges'; import type { ContainerProps } from './EnrollmentAddEventPageDefault.types'; import { WidgetsForEnrollmentEventNew } from '../PageLayout/DefaultPageLayout.constants'; @@ -30,9 +28,6 @@ import { EnrollmentAddEventPageDefaultComponent } from './EnrollmentAddEventPage import { convertEventAttributeOptions } from '../../../../events/convertEventAttributeOptions'; import { TrackerProgram } from '../../../../metaData'; -const getTrackerProgram = (program: any): TrackerProgram | undefined => - (program instanceof TrackerProgram ? program : undefined); - export const EnrollmentAddEventPageDefault = ({ pageLayout, enrollment, @@ -119,15 +114,6 @@ export const EnrollmentAddEventPageDefault = ({ const selectedProgramStage = [...program?.stages.values() ?? []].find((item: any) => item.id === stageId); const outputEffects = useWidgetDataFromStore(widgetReducerName); const hideWidgets = useHideWidgetByRuleLocations(program?.programRules.concat(selectedProgramStage?.programRules ?? [])); - const trackerProgram = getTrackerProgram(program); - const effectiveOrgUnitId = orgUnitId ?? enrollment?.orgUnit; - const { orgUnit } = useCoreOrgUnit(effectiveOrgUnitId); - const ruleEffects = useRuleEffects({ - orgUnit, - program: trackerProgram, - apiEnrollment: enrollment ?? undefined, - apiAttributeValues: attributeValues ?? undefined, - }); const trackedEntityName = (program instanceof TrackerProgram) ? program.trackedEntityType?.name ?? '' : ''; const rulesExecutionDependencies = useMemo(() => ({ @@ -206,7 +192,6 @@ export const EnrollmentAddEventPageDefault = ({ onAddNew={handleAddNew} widgetEffects={outputEffects} hideWidgets={hideWidgets} - ruleEffects={ruleEffects} widgetReducerName={widgetReducerName} pageFailure={commonDataError} rulesExecutionDependencies={rulesExecutionDependencies} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx index 0900a4e384..b5a96db2d1 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx @@ -27,7 +27,6 @@ export const EnrollmentEditEventPageComponent = ({ teiDisplayName, widgetEffects, hideWidgets, - ruleEffects, onDelete, onAddNew, onLinkedRecordClick, @@ -119,7 +118,6 @@ export const EnrollmentEditEventPageComponent = ({ pageStatus={pageStatus} widgetEffects={widgetEffects} hideWidgets={hideWidgets} - ruleEffects={ruleEffects} onDelete={onDelete} onAddNew={onAddNew} eventAccess={eventAccess} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx index 0e6f70073d..0152f65a53 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx @@ -14,7 +14,6 @@ import { updateEnrollmentAndEvents, updateEnrollmentEvent, useCommonEnrollmentDomainData, - useRuleEffects, deleteEnrollmentEvent, deleteEnrollmentEventRelationship, updateOrAddEnrollmentEvents, @@ -23,7 +22,6 @@ import { } from '../common/EnrollmentOverviewDomain'; import { useTeiDisplayName } from '../common/EnrollmentOverviewDomain/useTeiDisplayName'; import { useTrackerProgram } from '../../../hooks/useTrackerProgram'; -import { useCoreOrgUnit } from '../../../metadataRetrieval/coreOrgUnit'; import { pageStatuses } from './EnrollmentEditEventPage.constants'; import { EnrollmentEditEventPageComponent } from './EnrollmentEditEventPage.component'; import { useWidgetDataFromStore } from '../EnrollmentAddEvent/hooks'; @@ -99,7 +97,7 @@ export const EnrollmentEditEventPage = () => { const { loading, event } = useEvent(eventId ?? ''); const { program: programId, programStage: stageId, trackedEntity: teiId, enrollment: enrollmentId } = event; const { orgUnitId, eventId: urlEventId, initMode } = useLocationQuery(); - const { enrollment: enrollmentSite, attributeValues, readOnly: trackedEntityInactive } = + const { enrollment: enrollmentSite, readOnly: trackedEntityInactive } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const storedEvent = enrollmentSite?.events?.find((item: Record) => item.event === eventId); @@ -120,7 +118,6 @@ export const EnrollmentEditEventPage = () => { orgUnitId={orgUnitId} initMode={initMode} enrollmentSite={enrollmentSite} - attributeValues={attributeValues} trackedEntityInactive={trackedEntityInactive} event={storedEvent} /> @@ -135,7 +132,6 @@ const EnrollmentEditEventPageWithContextPlain = ({ orgUnitId, initMode, enrollmentSite, - attributeValues, trackedEntityInactive, event, }: Props) => { @@ -160,14 +156,6 @@ const EnrollmentEditEventPageWithContextPlain = ({ const hideWidgets = useHideWidgetByRuleLocations( program.programRules.concat(programStage?.programRules as ProgramRule[]), ); - const { orgUnit } = useCoreOrgUnit(orgUnitId); - const ruleEffects = useRuleEffects({ - orgUnit, - program, - apiEnrollment: enrollmentSite, - apiAttributeValues: attributeValues ?? undefined, - }); - const onDeleteTrackedEntitySuccess = useCallback(() => { navigate(`/?${buildUrlQueryString({ orgUnitId, programId })}`); }, [navigate, orgUnitId, programId]); @@ -338,7 +326,6 @@ const EnrollmentEditEventPageWithContextPlain = ({ onBackToViewEvent={onBackToViewEvent} widgetEffects={outputEffects} hideWidgets={hideWidgets} - ruleEffects={ruleEffects} teiId={teiId} enrollmentId={enrollmentId} eventId={eventId} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts index 5ddd7e7833..27e7190010 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts @@ -1,8 +1,6 @@ -import type { OutputEffect } from '@dhis2/rules-engine-javascript'; import type { ProgramStage } from '../../../metaData'; import { Program } from '../../../metaData'; import type { HideWidgets, WidgetEffects } from '../common/EnrollmentOverviewDomain'; -import type { AttributeValue } from '../common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; import type { UserFormField } from '../../FormFields/UserField'; import type { LinkedRecordClick } from '../../WidgetsRelationship/WidgetTrackedEntityRelationship'; import type { @@ -14,7 +12,6 @@ export type PlainProps = { programStage?: ProgramStage | null; widgetEffects: WidgetEffects; hideWidgets: HideWidgets; - ruleEffects?: Array; teiId: string; enrollmentId: string; eventId: string; @@ -73,7 +70,6 @@ export type Props = { orgUnitId: string; event: any; enrollmentSite: any; - attributeValues: Array | null | undefined; trackedEntityInactive?: boolean; initMode?: string; }; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts index 789f8b519f..b63f1603ab 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts @@ -22,8 +22,8 @@ const getClientFormattedDataValuesAsObject = (dataValues: any, elementsById: any return acc; }, {}); -const useEventsData = (enrollment: any, program?: TrackerProgram) => { - const elementsById = useMemo(() => (program ? getDataElementsInProgram(program) : {}), [program]); +const useEventsData = (enrollment: any, program: TrackerProgram) => { + const elementsById = useMemo(() => getDataElementsInProgram(program), [program]); return useMemo(() => enrollment && enrollment.events.map((event: any) => ({ eventId: event.event, @@ -59,10 +59,10 @@ const useEnrollmentData = (enrollment: any) => useMemo(() => { export const useRuleEffects = ({ orgUnit, program, apiEnrollment, apiAttributeValues }: UseRuleEffectsInput) => { const [ruleEffects, setRuleEffects] = useState(undefined); const attributesObject = useMemo(() => - (program?.attributes ?? []).reduce((acc: any, attribute: any) => { + program.attributes.reduce((acc: any, attribute: any) => { acc[attribute.id] = attribute; return acc; - }, {}), [program?.attributes]); + }, {}), [program.attributes]); const attributeValues = useMemo(() => apiAttributeValues && apiAttributeValues @@ -76,7 +76,7 @@ export const useRuleEffects = ({ orgUnit, program, apiEnrollment, apiAttributeVa const otherEvents = useEventsData(apiEnrollment, program); useEffect(() => { - if (program && orgUnit && attributeValues && enrollmentData && otherEvents) { + if (orgUnit && attributeValues && enrollmentData && otherEvents) { const effects = getApplicableRuleEffectsForTrackerProgram({ program, orgUnit, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts index 650ce4d807..236e9804f8 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts @@ -6,7 +6,7 @@ import type { export type UseRuleEffectsInput = { orgUnit?: any; - program?: TrackerProgram; + program: TrackerProgram; apiEnrollment?: EnrollmentData; apiAttributeValues?: Array; }; From a986d717b4875cdd9ba8c6e426600a42e7ada1d9 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:56:18 +0000 Subject: [PATCH 17/40] fix: clean up by reverting old changes --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault.container.tsx | 8 ++------ .../EnrollmentAddEventPageDefault.types.ts | 3 --- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index fbbd4728ea..b007baa796 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-20T15:57:28.252Z\n" -"PO-Revision-Date: 2026-08-20T15:57:28.252Z\n" +"POT-Creation-Date: 2026-08-20T16:56:19.866Z\n" +"PO-Revision-Date: 2026-08-20T16:56:19.866Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index d4caeb7ee3..6b3a75203a 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -59,7 +59,7 @@ export const EnrollmentPageDefault = () => { attributeValues, readOnly: trackedEntityInactive, } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); - const { orgUnit, error } = useCoreOrgUnit(orgUnitId); + const programOwnerId = useSelector(({ enrollmentPage }: any) => enrollmentPage.programOwners?.[programId]); const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); const { onLinkedRecordClick } = useLinkedRecordClick(); @@ -96,7 +96,7 @@ export const EnrollmentPageDefault = () => { } const ruleEffects = useRuleEffects({ - orgUnit: programOwnerOrgUnit ?? orgUnit, + orgUnit: programOwnerOrgUnit, program, apiEnrollment: enrollment, apiAttributeValues: attributeValues, @@ -195,10 +195,6 @@ export const EnrollmentPageDefault = () => { ); } - if (error) { - return error?.errorComponent; - } - return ( void; widgetEffects: WidgetEffects | null | undefined; hideWidgets: HideWidgets; - ruleEffects?: Array; rulesExecutionDependencies: Record; pageFailure: boolean; ready: boolean; @@ -50,7 +48,6 @@ export type ContainerProps = { enrolledAt?: string; occurredAt?: string; enrollment?: string; - orgUnit?: string; } | null | undefined; attributeValues: Array | null | undefined; commonDataError: boolean; From 2fe73d537f3a0d2aa73cf33c69af92317069c0e0 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:44:46 +0000 Subject: [PATCH 18/40] fix: update translations and improve loading state handling in WidgetProfile --- i18n/en.pot | 11 +++----- .../ProgramRules/getRulesActionsForTEI.ts | 1 - .../WidgetProfile/WidgetProfile.component.tsx | 27 ++++--------------- .../WidgetProfile/widgetProfile.types.ts | 4 ++- 4 files changed, 12 insertions(+), 31 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index b007baa796..2598eac898 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-20T16:56:19.866Z\n" -"PO-Revision-Date: 2026-08-20T16:56:19.866Z\n" +"POT-Creation-Date: 2026-08-20T17:44:47.776Z\n" +"PO-Revision-Date: 2026-08-20T17:44:47.776Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." @@ -1697,14 +1697,11 @@ msgstr "View profile" msgid "Profile widget could not be loaded. Please try again later" msgstr "Profile widget could not be loaded. Please try again later" -msgid "No attributes configured for {{trackedEntityTypeName}}" -msgstr "No attributes configured for {{trackedEntityTypeName}}" - msgid "No attributes configured" msgstr "No attributes configured" -msgid "Attributes are hidden by program rules" -msgstr "Attributes are hidden by program rules" +msgid "No attributes configured to display" +msgstr "No attributes configured to display" msgid "{{trackedEntityTypeName}} profile" msgstr "{{trackedEntityTypeName}} profile" diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts index a31171a849..5b32b6880f 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/getRulesActionsForTEI.ts @@ -94,7 +94,6 @@ export const getRulesActionsForTEIAsync = async ({ selectedUserRoles: userRoles, optionSets, }); - const effectsHierarchy = buildEffectsHierarchy(postProcessRulesEffects(effects, foundation)); const effectsWithValidations = await validateAssignEffects({ dataElements: foundation.getElements(), diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 1c9e974d82..d02dd9266f 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -123,9 +123,7 @@ const WidgetProfilePlain = ({ return null; }, [isEditable, readOnlyMode, hasNoAttributes]); - const loading = computeLoadingState( - programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched, - ); + const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched); const error = computeError(programsError, trackedEntityInstancesError, userRolesError); const clientAttributesWithSubvalues = useClientAttributesWithSubvalues( teiId, @@ -151,13 +149,6 @@ const WidgetProfilePlain = ({ }; }), [clientAttributesWithSubvalues, hiddenFieldIds]); - const allDisplayInListHiddenByRules = useMemo(() => { - if (!hiddenFieldIds.size) return false; - const displayInListItems = clientAttributesWithSubvalues.filter((item: any) => item.displayInList); - return displayInListItems.length > 0 - && displayInListItems.every((item: any) => hiddenFieldIds.has(item.attribute)); - }, [clientAttributesWithSubvalues, hiddenFieldIds]); - const onSaveExternal = useCallback(() => { queryClient.removeQueries([ReactQueryAppNamespace, 'changelog', CHANGELOG_ENTITY_TYPES.TRACKED_ENTITY, teiId]); }, [queryClient, teiId]); @@ -192,22 +183,17 @@ const WidgetProfilePlain = ({ return (

- {trackedEntityTypeName - ? i18n.t('No attributes configured for {{trackedEntityTypeName}}', { - trackedEntityTypeName, - interpolation: { escapeValue: false }, - }) - : i18n.t('No attributes configured')} + {i18n.t('No attributes configured')}

); } - if (allDisplayInListHiddenByRules) { + if (displayInListAttributes.length === 0) { return (

- {i18n.t('Attributes are hidden by program rules')} + {i18n.t('No attributes configured to display')}

); @@ -225,9 +211,6 @@ const WidgetProfilePlain = ({ const handleOpen = useCallback(() => setOpenStatus(true), [setOpenStatus]); const handleClose = useCallback(() => setOpenStatus(false), [setOpenStatus]); - const isEmptyList = !loading && !error && !hasNoAttributes - && displayInListAttributes.length === 0 && !allDisplayInListHiddenByRules; - const { trackedEntityProp, trackedEntityForToggle } = useMemo(() => { const resolvedId = (trackedEntity && trackedEntity.trackedEntity) || teiId; const trackedEntityTypeId = program?.trackedEntityType?.id; @@ -286,7 +269,7 @@ const WidgetProfilePlain = ({ onOpen={handleOpen} onClose={handleClose} open={open} - noncollapsible={isEmptyList} + > {renderProfile()} diff --git a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts index ceb62b046a..289daf9e31 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts @@ -1,9 +1,11 @@ +import type { OutputEffect } from '@dhis2/rules-engine-javascript'; + export type Props = { teiId: string; programId: string; orgUnitId: string; readOnlyMode?: boolean; - ruleEffects?: Array<{ type: string; id: string; [key: string]: any }>; + ruleEffects?: Array; onUpdateTeiAttributeValues?: (attributes: Array<{ [key: string]: string }>, teiDisplayName: string) => void; onDeleteSuccess?: () => void; onStatusToggleSuccess?: () => void; From 89ddcf9e04fda8dccc20d444d1b86ee7e1169e44 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Thu, 20 Aug 2026 18:21:20 +0000 Subject: [PATCH 19/40] fix: add missing org unit gard --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault.container.tsx | 1 + .../EnrollmentEditEventPage.component.tsx | 4 ++++ .../EnrollmentEditEventPage.container.tsx | 16 +++++++++++++++- .../EnrollmentEditEventPage.types.ts | 4 ++++ .../LayoutComponentConfig.ts | 4 ++-- .../WidgetProfile/WidgetProfile.component.tsx | 6 +++++- 7 files changed, 33 insertions(+), 6 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 2598eac898..49a73afd6c 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-20T17:44:47.776Z\n" -"PO-Revision-Date: 2026-08-20T17:44:47.776Z\n" +"POT-Creation-Date: 2026-08-20T18:21:21.932Z\n" +"PO-Revision-Date: 2026-08-20T18:21:21.932Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index 6b3a75203a..cd639a46cb 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -203,6 +203,7 @@ export const EnrollmentPageDefault = () => { availableWidgets={WidgetsForEnrollmentPageDefault} teiId={teiId} orgUnitId={orgUnitId} + programOwnerId={programOwnerId} program={program} stages={stages} events={enrollment?.events} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx index b5a96db2d1..c1615e4603 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx @@ -27,6 +27,8 @@ export const EnrollmentEditEventPageComponent = ({ teiDisplayName, widgetEffects, hideWidgets, + ruleEffects, + programOwnerId, onDelete, onAddNew, onLinkedRecordClick, @@ -118,6 +120,8 @@ export const EnrollmentEditEventPageComponent = ({ pageStatus={pageStatus} widgetEffects={widgetEffects} hideWidgets={hideWidgets} + ruleEffects={ruleEffects} + programOwnerId={programOwnerId} onDelete={onDelete} onAddNew={onAddNew} eventAccess={eventAccess} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx index 0152f65a53..ed2438cf07 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx @@ -14,6 +14,7 @@ import { updateEnrollmentAndEvents, updateEnrollmentEvent, useCommonEnrollmentDomainData, + useRuleEffects, deleteEnrollmentEvent, deleteEnrollmentEventRelationship, updateOrAddEnrollmentEvents, @@ -22,6 +23,7 @@ import { } from '../common/EnrollmentOverviewDomain'; import { useTeiDisplayName } from '../common/EnrollmentOverviewDomain/useTeiDisplayName'; import { useTrackerProgram } from '../../../hooks/useTrackerProgram'; +import { useCoreOrgUnit } from '../../../metadataRetrieval/coreOrgUnit'; import { pageStatuses } from './EnrollmentEditEventPage.constants'; import { EnrollmentEditEventPageComponent } from './EnrollmentEditEventPage.component'; import { useWidgetDataFromStore } from '../EnrollmentAddEvent/hooks'; @@ -97,7 +99,7 @@ export const EnrollmentEditEventPage = () => { const { loading, event } = useEvent(eventId ?? ''); const { program: programId, programStage: stageId, trackedEntity: teiId, enrollment: enrollmentId } = event; const { orgUnitId, eventId: urlEventId, initMode } = useLocationQuery(); - const { enrollment: enrollmentSite, readOnly: trackedEntityInactive } = + const { enrollment: enrollmentSite, attributeValues, readOnly: trackedEntityInactive } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const storedEvent = enrollmentSite?.events?.find((item: Record) => item.event === eventId); @@ -118,6 +120,7 @@ export const EnrollmentEditEventPage = () => { orgUnitId={orgUnitId} initMode={initMode} enrollmentSite={enrollmentSite} + attributeValues={attributeValues} trackedEntityInactive={trackedEntityInactive} event={storedEvent} /> @@ -132,6 +135,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ orgUnitId, initMode, enrollmentSite, + attributeValues, trackedEntityInactive, event, }: Props) => { @@ -151,11 +155,19 @@ const EnrollmentEditEventPageWithContextPlain = ({ dispatch(cleanUpDataEntry(dataEntryIds.ENROLLMENT_EVENT)); }, [dispatch]); + const programOwnerId = useSelector(({ enrollmentPage }: any) => enrollmentPage.programOwners?.[programId]); const program = useTrackerProgram(programId); const programStage = [...program.stages.values()].find((item: any) => item.id === stageId); const hideWidgets = useHideWidgetByRuleLocations( program.programRules.concat(programStage?.programRules as ProgramRule[]), ); + const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); + const ruleEffects = useRuleEffects({ + orgUnit: programOwnerOrgUnit, + program, + apiEnrollment: enrollmentSite, + apiAttributeValues: attributeValues ?? undefined, + }); const onDeleteTrackedEntitySuccess = useCallback(() => { navigate(`/?${buildUrlQueryString({ orgUnitId, programId })}`); }, [navigate, orgUnitId, programId]); @@ -366,6 +378,8 @@ const EnrollmentEditEventPageWithContextPlain = ({ onUpdateEnrollmentEventsSuccess={onUpdateEnrollmentEventsSuccess} onUpdateEnrollmentEventsError={onUpdateEnrollmentEventsError} userInteractionInProgress={userInteractionInProgress} + ruleEffects={ruleEffects} + programOwnerId={programOwnerId} />
); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts index 27e7190010..19622a08d8 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts @@ -1,3 +1,4 @@ +import type { OutputEffect } from '@dhis2/rules-engine-javascript'; import type { ProgramStage } from '../../../metaData'; import { Program } from '../../../metaData'; import type { HideWidgets, WidgetEffects } from '../common/EnrollmentOverviewDomain'; @@ -12,6 +13,8 @@ export type PlainProps = { programStage?: ProgramStage | null; widgetEffects: WidgetEffects; hideWidgets: HideWidgets; + ruleEffects?: Array; + programOwnerId?: string; teiId: string; enrollmentId: string; eventId: string; @@ -70,6 +73,7 @@ export type Props = { orgUnitId: string; event: any; enrollmentSite: any; + attributeValues?: Array; trackedEntityInactive?: boolean; initMode?: string; }; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts index 30ece58d84..d4c0fe6551 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts @@ -143,7 +143,7 @@ export const ProfileWidget: WidgetConfig = { getProps: ({ teiId, program, - orgUnitId, + programOwnerId, onUpdateTeiAttributeValues, onDeleteTrackedEntitySuccess, onStatusToggleSuccess, @@ -151,7 +151,7 @@ export const ProfileWidget: WidgetConfig = { }: any): WidgetProfileProps => ({ teiId, programId: program.id, - orgUnitId, + orgUnitId: programOwnerId, onUpdateTeiAttributeValues, onDeleteSuccess: onDeleteTrackedEntitySuccess, onStatusToggleSuccess, diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index d02dd9266f..cd499c0b99 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -170,6 +170,11 @@ const WidgetProfilePlain = ({ ); const renderProfile = () => { + if (!orgUnitId) { + log.error(errorCreator('Profile widget could not be loaded: missing orgUnitId')({ teiId })); + return {i18n.t('Profile widget could not be loaded. Please try again later')}; + } + if (loading) { return ; } @@ -269,7 +274,6 @@ const WidgetProfilePlain = ({ onOpen={handleOpen} onClose={handleClose} open={open} - > {renderProfile()} From de622b4e75ba592d37fcedecceb82ea7011eaa96 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Thu, 20 Aug 2026 19:03:44 +0000 Subject: [PATCH 20/40] fix: add programOwnerId to useCommonEnrollmentDomainData --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault.container.tsx | 17 ++++++++--------- .../EnrollmentAddEventPage.container.tsx | 2 ++ .../EnrollmentAddEventPageDefault.container.tsx | 14 ++++++++++++++ .../EnrollmentAddEventPageDefault.types.ts | 4 ++++ .../EnrollmentEditEventPage.container.tsx | 7 ++++--- .../EnrollmentEditEventPage.types.ts | 4 +++- .../enrollment.actions.ts | 13 +++++++++++-- .../useCommonEnrollmentDomainData.ts | 9 +++++++-- .../useCommonEnrollmentDomainData.types.ts | 1 + .../WidgetProfile/WidgetProfile.component.tsx | 12 ++++++++---- .../enrollmentDomain.reducerDescription.ts | 5 ++++- 12 files changed, 68 insertions(+), 24 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 49a73afd6c..87ac7059e5 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-20T18:21:21.932Z\n" -"PO-Revision-Date: 2026-08-20T18:21:21.932Z\n" +"POT-Creation-Date: 2026-08-20T19:03:46.232Z\n" +"PO-Revision-Date: 2026-08-20T19:03:46.233Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index cd639a46cb..08983d4c8d 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -53,15 +53,6 @@ export const EnrollmentPageDefault = () => { const { fromClientDate } = useTimeZoneConversion(); const { status: widgetEnrollmentStatus } = useSelector(({ widgetEnrollment }: any) => widgetEnrollment); const { enrollmentId, programId, teiId, orgUnitId } = useLocationQuery(); - const { - error: enrollmentsError, - enrollment, - attributeValues, - readOnly: trackedEntityInactive, - } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); - - const programOwnerId = useSelector(({ enrollmentPage }: any) => enrollmentPage.programOwners?.[programId]); - const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); const { onLinkedRecordClick } = useLinkedRecordClick(); const { pageLayout, @@ -72,7 +63,15 @@ export const EnrollmentPageDefault = () => { dataStoreKey: DataStoreKeyByPage.ENROLLMENT_OVERVIEW, }); + const { + error: enrollmentsError, + enrollment, + attributeValues, + readOnly: trackedEntityInactive, + } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const program = useTrackerProgram(programId); + const programOwnerId = useSelector(({ enrollmentPage }: any) => enrollmentPage.programOwners?.[programId]); + const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); const onStatusToggleSuccess = useCallback(() => { dispatch(setTrackedEntityInactiveStatus(!trackedEntityInactive)); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx index c8e2874f80..6267d304ca 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx @@ -66,6 +66,7 @@ const EnrollmentAddEventPagePlain = ({ classes }: WithStyles) => attributeValues, error: commonDataError, readOnly: trackedEntityInactive, + programOwnerId, } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const { pageLayout, isLoading } = useEnrollmentPageLayout({ selectedScopeId: validIds[IdTypes.PROGRAM_ID]?.id ?? null, @@ -99,6 +100,7 @@ const EnrollmentAddEventPagePlain = ({ classes }: WithStyles) => attributeValues={attributeValues} commonDataError={Boolean(commonDataError)} trackedEntityInactive={Boolean(trackedEntityInactive)} + programOwnerId={programOwnerId} /> ); } diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx index 23d7df4412..767d238f91 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx @@ -20,7 +20,10 @@ import { showEnrollmentError, updateEnrollmentAndEvents, updateOrAddEnrollmentEvents, + useRuleEffects, } from '../../common/EnrollmentOverviewDomain'; +import { useTrackerProgram } from '../../../../hooks/useTrackerProgram'; +import { useCoreOrgUnit } from '../../../../metadataRetrieval/coreOrgUnit'; import { dataEntryHasChanges as getDataEntryHasChanges } from '../../../DataEntry/common/dataEntryHasChanges'; import type { ContainerProps } from './EnrollmentAddEventPageDefault.types'; import { WidgetsForEnrollmentEventNew } from '../PageLayout/DefaultPageLayout.constants'; @@ -34,6 +37,7 @@ export const EnrollmentAddEventPageDefault = ({ attributeValues, commonDataError, trackedEntityInactive, + programOwnerId, }: ContainerProps) => { const { programId, stageId, orgUnitId, teiId, enrollmentId } = useLocationQuery(); @@ -111,6 +115,14 @@ export const EnrollmentAddEventPageDefault = ({ const dataEntryHasChanges = useSelector((state: ReduxState) => getDataEntryHasChanges(state, widgetReducerName)); const { program } = useProgramInfo(programId); + const trackerProgram = useTrackerProgram(programId); + const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId ?? ''); + const ruleEffects = useRuleEffects({ + orgUnit: programOwnerOrgUnit, + program: trackerProgram, + apiEnrollment: enrollment ?? undefined, + apiAttributeValues: attributeValues ?? undefined, + }); const selectedProgramStage = [...program?.stages.values() ?? []].find((item: any) => item.id === stageId); const outputEffects = useWidgetDataFromStore(widgetReducerName); const hideWidgets = useHideWidgetByRuleLocations(program?.programRules.concat(selectedProgramStage?.programRules ?? [])); @@ -205,6 +217,8 @@ export const EnrollmentAddEventPageDefault = ({ onUpdateEnrollmentStatusError={onUpdateEnrollmentStatusError} onAccessLostFromTransfer={onAccessLostFromTransfer} trackedEntityInactive={trackedEntityInactive} + programOwnerId={programOwnerId} + ruleEffects={ruleEffects} /> ); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts index 21728bc5c6..e39ba082c3 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts @@ -1,3 +1,4 @@ +import type { OutputEffect } from '@dhis2/rules-engine-javascript'; import type { ApiEnrollmentEvent } from 'capture-core-utils/types/api-types'; import type { HideWidgets, WidgetEffects } from '../../common/EnrollmentOverviewDomain'; import type { AttributeValue } from '../../common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; @@ -10,6 +11,8 @@ import { Program } from '../../../../metaData'; export type Props = { program: Program | null | undefined; + programOwnerId?: string; + ruleEffects?: Array; stageId: string; orgUnitId: string; teiId: string; @@ -52,4 +55,5 @@ export type ContainerProps = { attributeValues: Array | null | undefined; commonDataError: boolean; trackedEntityInactive: boolean; + programOwnerId?: string; }; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx index ed2438cf07..bb1c55802f 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx @@ -99,7 +99,7 @@ export const EnrollmentEditEventPage = () => { const { loading, event } = useEvent(eventId ?? ''); const { program: programId, programStage: stageId, trackedEntity: teiId, enrollment: enrollmentId } = event; const { orgUnitId, eventId: urlEventId, initMode } = useLocationQuery(); - const { enrollment: enrollmentSite, attributeValues, readOnly: trackedEntityInactive } = + const { enrollment: enrollmentSite, attributeValues, readOnly: trackedEntityInactive, programOwnerId } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const storedEvent = enrollmentSite?.events?.find((item: Record) => item.event === eventId); @@ -122,6 +122,7 @@ export const EnrollmentEditEventPage = () => { enrollmentSite={enrollmentSite} attributeValues={attributeValues} trackedEntityInactive={trackedEntityInactive} + programOwnerId={programOwnerId} event={storedEvent} /> ) : ; @@ -137,6 +138,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ enrollmentSite, attributeValues, trackedEntityInactive, + programOwnerId, event, }: Props) => { const { navigate } = useNavigate(); @@ -155,13 +157,12 @@ const EnrollmentEditEventPageWithContextPlain = ({ dispatch(cleanUpDataEntry(dataEntryIds.ENROLLMENT_EVENT)); }, [dispatch]); - const programOwnerId = useSelector(({ enrollmentPage }: any) => enrollmentPage.programOwners?.[programId]); const program = useTrackerProgram(programId); const programStage = [...program.stages.values()].find((item: any) => item.id === stageId); const hideWidgets = useHideWidgetByRuleLocations( program.programRules.concat(programStage?.programRules as ProgramRule[]), ); - const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); + const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId ?? ''); const ruleEffects = useRuleEffects({ orgUnit: programOwnerOrgUnit, program, diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts index 19622a08d8..bcac3451e1 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts @@ -2,6 +2,7 @@ import type { OutputEffect } from '@dhis2/rules-engine-javascript'; import type { ProgramStage } from '../../../metaData'; import { Program } from '../../../metaData'; import type { HideWidgets, WidgetEffects } from '../common/EnrollmentOverviewDomain'; +import type { AttributeValue } from '../common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; import type { UserFormField } from '../../FormFields/UserField'; import type { LinkedRecordClick } from '../../WidgetsRelationship/WidgetTrackedEntityRelationship'; import type { @@ -73,7 +74,8 @@ export type Props = { orgUnitId: string; event: any; enrollmentSite: any; - attributeValues?: Array; + attributeValues?: Array; trackedEntityInactive?: boolean; + programOwnerId?: string; initMode?: string; }; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts index 87300d6fbf..f8438b1524 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts @@ -25,8 +25,17 @@ export const enrollmentSiteActionTypes = { SET_TRACKED_ENTITY_INACTIVE_STATUS: 'Enrollment.SetTrackedEntityInactiveStatus', }; -export const setCommonEnrollmentSiteData = (enrollment: any, attributeValues: any, inactive?: boolean) => - actionCreator(enrollmentSiteActionTypes.COMMON_ENROLLMENT_SITE_DATA_SET)({ enrollment, attributeValues, inactive }); +export const setCommonEnrollmentSiteData = ( + enrollment: Record, + attributeValues: Array>, + inactive?: boolean, + programOwnerId?: string, +) => actionCreator(enrollmentSiteActionTypes.COMMON_ENROLLMENT_SITE_DATA_SET)({ + enrollment, + attributeValues, + inactive, + programOwnerId, +}); export const setTrackedEntityInactiveStatus = (inactive: boolean) => actionCreator(enrollmentSiteActionTypes.SET_TRACKED_ENTITY_INACTIVE_STATUS)({ inactive }); diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts index 488c3c5e06..387dcdbb5d 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts @@ -12,6 +12,7 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin enrollment: storedEnrollment, attributeValues: storedAttributeValues, inactive: storedInactive, + programOwnerId: storedProgramOwnerId, } = useSelector(({ enrollmentDomain }: any) => enrollmentDomain); const { data, error } = useApiDataQuery( @@ -21,7 +22,7 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin id: teiId, params: { program: programId, - fields: ['enrollments[*,!attributes],attributes,inactive'], + fields: ['enrollments[*,!attributes],attributes,inactive,programOwners[program,orgUnit]'], }, }, { @@ -37,6 +38,7 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin ?.find((enrollment: any) => enrollment.enrollment === enrollmentId), attributeValues: data?.attributes, inactive: Boolean(data?.inactive), + programOwnerId: data?.programOwners?.find((p: any) => p.program === programId)?.orgUnit, }; useEffect(() => { @@ -46,6 +48,7 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin fetchedEnrollmentData.attributeValues .map(({ attribute, value }: any) => ({ id: attribute, value })), fetchedEnrollmentData.inactive, + fetchedEnrollmentData.programOwnerId, )); } }, [ @@ -54,13 +57,15 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin fetchedEnrollmentData.enrollment, fetchedEnrollmentData.attributeValues, fetchedEnrollmentData.inactive, + fetchedEnrollmentData.programOwnerId, ]); const inEffectData = enrollmentId === storedEnrollmentId ? { enrollment: storedEnrollment, attributeValues: storedAttributeValues, readOnly: Boolean(storedInactive), - } : { enrollment: undefined, attributeValues: undefined, readOnly: false }; + programOwnerId: storedProgramOwnerId, + } : { enrollment: undefined, attributeValues: undefined, readOnly: false, programOwnerId: undefined }; return { error, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.ts index c4abec7663..d4c79e7a55 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.ts @@ -36,4 +36,5 @@ export type Output = { enrollment?: EnrollmentData; attributeValues?: Array; readOnly?: boolean; + programOwnerId?: string; }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index cd499c0b99..e0d686c05f 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -149,6 +149,9 @@ const WidgetProfilePlain = ({ }; }), [clientAttributesWithSubvalues, hiddenFieldIds]); + const isEmptyList = !loading && !error && Boolean(orgUnitId) + && (hasNoAttributes || displayInListAttributes.length === 0); + const onSaveExternal = useCallback(() => { queryClient.removeQueries([ReactQueryAppNamespace, 'changelog', CHANGELOG_ENTITY_TYPES.TRACKED_ENTITY, teiId]); }, [queryClient, teiId]); @@ -170,15 +173,15 @@ const WidgetProfilePlain = ({ ); const renderProfile = () => { + if (loading) { + return ; + } + if (!orgUnitId) { log.error(errorCreator('Profile widget could not be loaded: missing orgUnitId')({ teiId })); return {i18n.t('Profile widget could not be loaded. Please try again later')}; } - if (loading) { - return ; - } - if (error) { log.error(errorCreator('Profile widget could not be loaded')({ error })); return {i18n.t('Profile widget could not be loaded. Please try again later')}; @@ -274,6 +277,7 @@ const WidgetProfilePlain = ({ onOpen={handleOpen} onClose={handleClose} open={open} + noncollapsible={isEmptyList} > {renderProfile()} diff --git a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts index bbe958d6d6..bc95ac56ae 100644 --- a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts +++ b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts @@ -42,12 +42,15 @@ const setAssignee = (state, action) => { export const enrollmentDomainDesc = createReducerDescription( { - [COMMON_ENROLLMENT_SITE_DATA_SET]: (state, { payload: { enrollment, attributeValues, inactive } }) => ({ + [COMMON_ENROLLMENT_SITE_DATA_SET]: (state, { + payload: { enrollment, attributeValues, inactive, programOwnerId }, + }) => ({ ...state, enrollment, attributeValues, enrollmentId: enrollment?.enrollment, inactive, + programOwnerId, }), [SET_TRACKED_ENTITY_INACTIVE_STATUS]: (state, { payload: { inactive } }) => ({ ...state, From bf2ea6346e28e1ab05670db1f53a143d0319e1ae Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Fri, 21 Aug 2026 07:26:33 +0000 Subject: [PATCH 21/40] fix: handle programOwnerOrgUnit error --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault.container.tsx | 6 +++--- .../EnrollmentAddEventPageDefault.container.tsx | 1 + .../components/WidgetProfile/WidgetProfile.component.tsx | 7 +------ 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 87ac7059e5..d057c5cdf2 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-20T19:03:46.232Z\n" -"PO-Revision-Date: 2026-08-20T19:03:46.233Z\n" +"POT-Creation-Date: 2026-08-21T07:26:34.549Z\n" +"PO-Revision-Date: 2026-08-21T07:26:34.549Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index 08983d4c8d..ec9e38259c 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -71,7 +71,7 @@ export const EnrollmentPageDefault = () => { } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const program = useTrackerProgram(programId); const programOwnerId = useSelector(({ enrollmentPage }: any) => enrollmentPage.programOwners?.[programId]); - const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); + const { orgUnit: programOwnerOrgUnit, error: programOwnerOrgUnitError } = useCoreOrgUnit(programOwnerId); const onStatusToggleSuccess = useCallback(() => { dispatch(setTrackedEntityInactiveStatus(!trackedEntityInactive)); @@ -88,9 +88,9 @@ export const EnrollmentPageDefault = () => { https://dhis2.atlassian.net/browse/DHIS2-17574 */ - if (programMetaDataError || enrollmentsError) { + if (programMetaDataError || enrollmentsError || programOwnerOrgUnitError) { log.error(errorCreator('Enrollment page could not be loaded')( - { programMetaDataError, enrollmentsError }, + { programMetaDataError, enrollmentsError, programOwnerOrgUnitError }, )); } diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx index 767d238f91..5aaad0537f 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx @@ -117,6 +117,7 @@ export const EnrollmentAddEventPageDefault = ({ const { program } = useProgramInfo(programId); const trackerProgram = useTrackerProgram(programId); const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId ?? ''); + const ruleEffects = useRuleEffects({ orgUnit: programOwnerOrgUnit, program: trackerProgram, diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index e0d686c05f..c62e14ef31 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -149,7 +149,7 @@ const WidgetProfilePlain = ({ }; }), [clientAttributesWithSubvalues, hiddenFieldIds]); - const isEmptyList = !loading && !error && Boolean(orgUnitId) + const isEmptyList = !loading && !error && (hasNoAttributes || displayInListAttributes.length === 0); const onSaveExternal = useCallback(() => { @@ -177,11 +177,6 @@ const WidgetProfilePlain = ({ return ; } - if (!orgUnitId) { - log.error(errorCreator('Profile widget could not be loaded: missing orgUnitId')({ teiId })); - return {i18n.t('Profile widget could not be loaded. Please try again later')}; - } - if (error) { log.error(errorCreator('Profile widget could not be loaded')({ error })); return {i18n.t('Profile widget could not be loaded. Please try again later')}; From b49aa3d725de2ccd9da8e9e83b81d97629187eec Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Fri, 21 Aug 2026 08:38:57 +0000 Subject: [PATCH 22/40] fix: require orgUnitId --- i18n/en.pot | 4 ++-- .../EnrollmentAddEventPageDefault.container.tsx | 2 +- .../ProgramStageSelector/ProgramStageSelector.container.tsx | 4 ++-- .../EnrollmentEditEventPage.container.tsx | 2 +- .../metadataRetrieval/coreOrgUnit/useCoreOrgUnit.tsx | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index d057c5cdf2..f378afd62a 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-21T07:26:34.549Z\n" -"PO-Revision-Date: 2026-08-21T07:26:34.549Z\n" +"POT-Creation-Date: 2026-08-21T08:38:58.409Z\n" +"PO-Revision-Date: 2026-08-21T08:38:58.409Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx index 5aaad0537f..6281ffbffa 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx @@ -116,7 +116,7 @@ export const EnrollmentAddEventPageDefault = ({ const dataEntryHasChanges = useSelector((state: ReduxState) => getDataEntryHasChanges(state, widgetReducerName)); const { program } = useProgramInfo(programId); const trackerProgram = useTrackerProgram(programId); - const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId ?? ''); + const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); const ruleEffects = useRuleEffects({ orgUnit: programOwnerOrgUnit, diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx index 10e33b8309..9c80dd4e4f 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx @@ -15,7 +15,7 @@ import { useTrackerProgram } from '../../../../hooks/useTrackerProgram'; export const ProgramStageSelector = ({ programId, orgUnitId, teiId, enrollmentId }: Props) => { const { navigate } = useNavigate(); const { tab } = useLocationQuery(); - const { error: enrollmentsError, enrollment, attributeValues } = useCommonEnrollmentDomainData( + const { error: enrollmentsError, enrollment, attributeValues, programOwnerId } = useCommonEnrollmentDomainData( teiId, enrollmentId, programId, @@ -26,7 +26,7 @@ export const ProgramStageSelector = ({ programId, orgUnitId, teiId, enrollmentId isError: programError, } = useProgramFromIndexedDB(programId); - const { orgUnit } = useCoreOrgUnit(orgUnitId); + const { orgUnit } = useCoreOrgUnit(programOwnerId); const programRules = useTrackerProgram(programId); const ruleEffects = useRuleEffects({ diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx index bb1c55802f..e24d7e5cce 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx @@ -162,7 +162,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ const hideWidgets = useHideWidgetByRuleLocations( program.programRules.concat(programStage?.programRules as ProgramRule[]), ); - const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId ?? ''); + const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); const ruleEffects = useRuleEffects({ orgUnit: programOwnerOrgUnit, program, diff --git a/src/core_modules/capture-core/metadataRetrieval/coreOrgUnit/useCoreOrgUnit.tsx b/src/core_modules/capture-core/metadataRetrieval/coreOrgUnit/useCoreOrgUnit.tsx index 0b14108661..123c9c1ddf 100644 --- a/src/core_modules/capture-core/metadataRetrieval/coreOrgUnit/useCoreOrgUnit.tsx +++ b/src/core_modules/capture-core/metadataRetrieval/coreOrgUnit/useCoreOrgUnit.tsx @@ -6,14 +6,14 @@ import { useOrganisationUnit } from '../../dataQueries'; import { orgUnitFetched } from './coreOrgUnit.actions'; import type { CoreOrgUnit } from './coreOrgUnit.types'; -export function useCoreOrgUnit(orgUnitId: string): { +export function useCoreOrgUnit(orgUnitId: string | undefined): { orgUnit?: CoreOrgUnit, error?: any, } { const dispatch = useDispatch(); - const reduxOrgUnit = useSelector(({ organisationUnits }: any) => organisationUnits && organisationUnits[orgUnitId]); + const reduxOrgUnit = useSelector(({ organisationUnits }: any) => + organisationUnits && orgUnitId && organisationUnits[orgUnitId]); const fetchId = reduxOrgUnit ? undefined : orgUnitId; - // These hooks do no work when id is undefined const { orgUnit, error } = useOrganisationUnit(fetchId, 'displayName,code,path'); const { orgUnitGroups, error: groupError } = useOrgUnitGroups(fetchId); From d85b5c85e39d2715ffdf0c902fb4c4ab36f870e2 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 25 Aug 2026 20:08:11 +0000 Subject: [PATCH 23/40] fix: (review) rename orgUnitId to programOwnerId across DataEntry and WidgetProfile components --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault.container.tsx | 2 +- .../LayoutComponentConfig.ts | 2 +- .../DataEntry/DataEntry.component.tsx | 4 ++-- .../DataEntry/DataEntry.container.tsx | 16 ++++++++-------- .../DataEntry/DataEntryReadOnly.component.tsx | 6 +++--- .../WidgetProfile/DataEntry/dataEntry.actions.ts | 6 +++--- .../WidgetProfile/DataEntry/dataEntry.epics.ts | 4 ++-- .../WidgetProfile/DataEntry/dataEntry.types.ts | 6 +++--- .../DataEntry/hooks/useLifecycle.ts | 6 +++--- .../WidgetProfile/WidgetProfile.component.tsx | 4 ++-- .../WidgetProfile/widgetProfile.types.ts | 2 +- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index f378afd62a..cb5751bb1d 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-21T08:38:58.409Z\n" -"PO-Revision-Date: 2026-08-21T08:38:58.409Z\n" +"POT-Creation-Date: 2026-08-25T20:08:12.729Z\n" +"PO-Revision-Date: 2026-08-25T20:08:12.729Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index ec9e38259c..5565af5174 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -68,9 +68,9 @@ export const EnrollmentPageDefault = () => { enrollment, attributeValues, readOnly: trackedEntityInactive, + programOwnerId, } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const program = useTrackerProgram(programId); - const programOwnerId = useSelector(({ enrollmentPage }: any) => enrollmentPage.programOwners?.[programId]); const { orgUnit: programOwnerOrgUnit, error: programOwnerOrgUnitError } = useCoreOrgUnit(programOwnerId); const onStatusToggleSuccess = useCallback(() => { diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts index d4c0fe6551..0f72a11406 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts @@ -151,7 +151,7 @@ export const ProfileWidget: WidgetConfig = { }: any): WidgetProfileProps => ({ teiId, programId: program.id, - orgUnitId: programOwnerId, + programOwnerId, onUpdateTeiAttributeValues, onDeleteSuccess: onDeleteTrackedEntitySuccess, onStatusToggleSuccess, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.tsx index 88953b500a..3be0af1bea 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.tsx @@ -20,7 +20,7 @@ export const DataEntryComponent = ({ onGetValidationContext, errorsMessages, warningsMessages, - orgUnitId, + programOwnerId, pluginContext, accessReadOnly, }: PlainProps) => ( @@ -60,7 +60,7 @@ export const DataEntryComponent = ({ onUpdateFormField={onUpdateFormField} onUpdateFormFieldAsync={onUpdateFormFieldAsync} onGetValidationContext={onGetValidationContext} - orgUnitId={orgUnitId} + orgUnitId={programOwnerId} pluginContext={pluginContext} fieldOptions={{ autoSelectSingleOrgUnit: false }} /> diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.tsx b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.tsx index 5c547e8a55..2d0d3486e9 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.tsx @@ -19,7 +19,7 @@ const convertFn = pipe(convertServerToClient, convertClientToView); export const DataEntry = ({ programAPI, - orgUnitId, + programOwnerId, onCancel, onDisable, onEnable, @@ -46,16 +46,16 @@ export const DataEntry = ({ const onGetValidationContext = useCallback( () => ({ programId: programAPI.id, - orgUnitId, + orgUnitId: programOwnerId, trackedEntityInstanceId, trackedEntityTypeId: programAPI.trackedEntityType.id, }), - [programAPI, orgUnitId, trackedEntityInstanceId], + [programAPI, programOwnerId, trackedEntityInstanceId], ); const context = useLifecycle({ programAPI, - orgUnitId, + programOwnerId, clientAttributesWithSubvalues, userRoles, dataEntryId, @@ -123,7 +123,7 @@ export const DataEntry = ({ updateTeiRequest({ itemId, dataEntryId, - orgUnitId, + programOwnerId, trackedEntityInstanceId, trackedEntityTypeId: programAPI.trackedEntityType.id, onSaveExternal, @@ -137,7 +137,7 @@ export const DataEntry = ({ dispatch, itemId, dataEntryId, - orgUnitId, + programOwnerId, trackedEntityInstanceId, programAPI, formValidated, @@ -163,7 +163,7 @@ export const DataEntry = ({ onUpdateFormField={onUpdateFormField} onUpdateFormFieldAsync={onUpdateFormFieldAsync} onGetValidationContext={onGetValidationContext} - orgUnitId={orgUnitId} + programOwnerId={programOwnerId} pluginContext={pluginContext} accessReadOnly={accessReadOnly} /> @@ -184,7 +184,7 @@ export const DataEntry = ({ onGetValidationContext={onGetValidationContext} errorsMessages={errorsMessages} warningsMessages={warningsMessages} - orgUnitId={orgUnitId} + programOwnerId={programOwnerId} pluginContext={pluginContext} accessReadOnly={accessReadOnly} /> diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntryReadOnly.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntryReadOnly.component.tsx index 910e8ab1f5..ea38054e9f 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntryReadOnly.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntryReadOnly.component.tsx @@ -14,7 +14,7 @@ type Props = { onUpdateFormField: (innerAction: any) => void; onUpdateFormFieldAsync: (innerAction: any) => void; onGetValidationContext: () => Record; - orgUnitId: string; + programOwnerId: string; pluginContext?: PluginContext; accessReadOnly?: boolean; }; @@ -28,7 +28,7 @@ export const DataEntryReadOnlyComponent = ({ trackedEntityName, formFoundation, onGetValidationContext, - orgUnitId, + programOwnerId, pluginContext, accessReadOnly, }: Props) => ( @@ -53,7 +53,7 @@ export const DataEntryReadOnlyComponent = ({ onUpdateFormField={onUpdateFormField} onUpdateFormFieldAsync={onUpdateFormFieldAsync} onGetValidationContext={onGetValidationContext} - orgUnitId={orgUnitId} + orgUnitId={programOwnerId} pluginContext={pluginContext} viewMode /> diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts index 41f3f941f0..5fb5094195 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts @@ -143,7 +143,7 @@ export const cleanTeiModal = () => actionCreator(dataEntryActionTypes.CLEAN_TEI_ export const updateTeiRequest = ({ itemId, dataEntryId, - orgUnitId, + programOwnerId, trackedEntityTypeId, trackedEntityInstanceId, onSaveExternal, @@ -153,7 +153,7 @@ export const updateTeiRequest = ({ }: { itemId: string; dataEntryId: string; - orgUnitId: string; + programOwnerId: string; trackedEntityTypeId: string; trackedEntityInstanceId: string; onSaveExternal?: (eventServerValues: any, uid: string) => void; @@ -164,7 +164,7 @@ export const updateTeiRequest = ({ actionCreator(dataEntryActionTypes.TEI_UPDATE_REQUEST)({ itemId, dataEntryId, - orgUnitId, + programOwnerId, trackedEntityTypeId, trackedEntityInstanceId, formFoundation, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.epics.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.epics.ts index a6387cda29..18bf2dc89e 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.epics.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.epics.ts @@ -49,7 +49,7 @@ export const updateTeiEpic = (action$: any, store: any) => const { dataEntryId, itemId, - orgUnitId, + programOwnerId, trackedEntityTypeId, trackedEntityInstanceId, formFoundation, @@ -67,7 +67,7 @@ export const updateTeiEpic = (action$: any, store: any) => geometry: deriveGeometryFromFormValues(values), trackedEntity: trackedEntityInstanceId, trackedEntityType: trackedEntityTypeId, - orgUnit: orgUnitId, + orgUnit: programOwnerId, }, ], }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.ts index e7bbe7c132..1358ce991d 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.ts @@ -17,7 +17,7 @@ export type PlainProps = { modalState: string; errorsMessages: Array<{ id: string; message: string }>; warningsMessages: Array<{ id: string; message: string }>; - orgUnitId: string; + programOwnerId: string; pluginContext?: PluginContext; accessReadOnly?: boolean; }; @@ -31,14 +31,14 @@ export type ReadOnlyPlainProps = { onUpdateFormField: (innerAction: any) => void; onUpdateFormFieldAsync: (innerAction: any) => void; onGetValidationContext: () => Record; - orgUnitId: string; + programOwnerId: string; pluginContext?: PluginContext; accessReadOnly?: boolean; }; export type Props = { programAPI: any; - orgUnitId: string; + programOwnerId: string; dataEntryFormConfig: DataEntryFormConfig | null; onCancel: () => void; onDisable: () => void; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts index d819659829..ff4a7fa490 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts @@ -29,7 +29,7 @@ import type { QuerySingleResource } from '../../../../utils/api'; type UseLifecycleParams = { programAPI: any; - orgUnitId: string; + programOwnerId: string; clientAttributesWithSubvalues: Array; userRoles: Array; dataEntryId: string; @@ -42,7 +42,7 @@ type UseLifecycleParams = { export const useLifecycle = ({ programAPI, - orgUnitId, + programOwnerId, clientAttributesWithSubvalues, userRoles, dataEntryId, @@ -57,7 +57,7 @@ export const useLifecycle = ({ const enrollment: EnrollmentData = useSelector(({ enrollmentDomain }: any) => enrollmentDomain?.enrollment); const dataElements: DataElements = useDataElements(programAPI); const otherEvents = useEvents(enrollment, dataElements); - const orgUnit: any = useCoreOrgUnit(orgUnitId).orgUnit; + const orgUnit: any = useCoreOrgUnit(programOwnerId).orgUnit; const rulesContainer: ProgramRulesContainer = useRulesContainer(programAPI); const formFoundation: RenderFoundation = useFormFoundation(programAPI, dataEntryFormConfig); const { formValues, clientValues } = useFormValues({ formFoundation, clientAttributesWithSubvalues, orgUnit }); diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index c62e14ef31..4006ff09d8 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -72,7 +72,7 @@ const WidgetProfilePlain = ({ teiId, programId, readOnlyMode = false, - orgUnitId = '', + programOwnerId = '', ruleEffects, onUpdateTeiAttributeValues, onDeleteSuccess, @@ -284,7 +284,7 @@ const WidgetProfilePlain = ({ onEnable={handleOnEnable} programAPI={program} dataEntryFormConfig={dataEntryFormConfig} - orgUnitId={orgUnitId} + programOwnerId={programOwnerId} clientAttributesWithSubvalues={clientAttributesWithSubvalues} userRoles={userRoles} trackedEntityInstanceId={teiId} diff --git a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts index 289daf9e31..6bf7fc8c26 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts @@ -3,7 +3,7 @@ import type { OutputEffect } from '@dhis2/rules-engine-javascript'; export type Props = { teiId: string; programId: string; - orgUnitId: string; + programOwnerId: string; readOnlyMode?: boolean; ruleEffects?: Array; onUpdateTeiAttributeValues?: (attributes: Array<{ [key: string]: string }>, teiDisplayName: string) => void; From 85a39ac98f0540cd80dc17db1b1c1c5e7c13d44f Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:48:54 +0000 Subject: [PATCH 24/40] feat: refactor rule effects handling to use enrollment scope --- i18n/en.pot | 4 +- .../EnrollmentPageDefault.container.tsx | 9 ++- .../EnrollmentPageDefault.types.ts | 2 - .../EnrollmentQuickActions.component.tsx | 17 ++-- .../EnrollmentQuickActions.types.ts | 3 - .../hooks/useFilteredWidgetData.ts | 77 ++++++------------- ...nrollmentAddEventPageDefault.container.tsx | 6 +- .../EnrollmentAddEventPageDefault.types.ts | 2 - .../ProgramStageSelector.container.tsx | 18 +++-- .../EnrollmentEditEventPage.component.tsx | 2 - .../EnrollmentEditEventPage.container.tsx | 6 +- .../EnrollmentEditEventPage.types.ts | 2 - .../LayoutComponentConfig.ts | 7 +- .../common/EnrollmentOverviewDomain/index.ts | 2 +- .../getEnrollmentScopeFormId.ts | 1 + .../useRuleEffects/index.ts | 2 + .../useRuleEffects/rulesExecutionData.ts | 74 ++++++++++++++++++ .../useEnrollmentScopeRuleEffects.ts | 65 ++++++++++++++++ .../useRuleEffects/useRuleEffects.ts | 72 +---------------- .../WidgetProfile/WidgetProfile.component.tsx | 15 ++-- .../WidgetProfile/widgetProfile.types.ts | 3 - .../Stages/Stage/Stage.component.tsx | 14 ++-- .../types/common.types.ts | 2 - 23 files changed, 220 insertions(+), 185 deletions(-) create mode 100644 src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/getEnrollmentScopeFormId.ts create mode 100644 src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts create mode 100644 src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts diff --git a/i18n/en.pot b/i18n/en.pot index cb5751bb1d..9156d8af60 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-25T20:08:12.729Z\n" -"PO-Revision-Date: 2026-08-25T20:08:12.729Z\n" +"POT-Creation-Date: 2026-08-26T15:48:56.295Z\n" +"PO-Revision-Date: 2026-08-26T15:48:56.295Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index 5565af5174..186384bf79 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -16,7 +16,7 @@ import { updateEnrollmentDate, updateIncidentDate, useCommonEnrollmentDomainData, - useRuleEffects, + useEnrollmentScopeRuleEffects, } from '../../common/EnrollmentOverviewDomain'; import { deleteEnrollment, @@ -94,14 +94,16 @@ export const EnrollmentPageDefault = () => { )); } - const ruleEffects = useRuleEffects({ + useEnrollmentScopeRuleEffects({ + enrollmentId, orgUnit: programOwnerOrgUnit, program, apiEnrollment: enrollment, apiAttributeValues: attributeValues, + force: true, }); - const outputEffects = useFilteredWidgetData(ruleEffects); + const outputEffects = useFilteredWidgetData(enrollmentId); const hideWidgets = useHideWidgetByRuleLocations(program.programRules); const onDeleteTrackedEntitySuccess = useCallback(() => { @@ -228,7 +230,6 @@ export const EnrollmentPageDefault = () => { onUpdateEnrollmentStatus={onUpdateEnrollmentStatus} onUpdateEnrollmentStatusSuccess={onUpdateEnrollmentStatusSuccess} onUpdateEnrollmentStatusError={onUpdateEnrollmentStatusError} - ruleEffects={ruleEffects} widgetEnrollmentStatus={widgetEnrollmentStatus} onAccessLostFromTransfer={onAccessLostFromTransfer} feedbackEmptyText={i18n.t('No feedback for this enrollment yet')} diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.types.ts b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.types.ts index 9f9490949f..c19b3f034e 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.types.ts +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.types.ts @@ -1,4 +1,3 @@ -import { effectActions } from '@dhis2/rules-engine-javascript'; import type { TrackerProgram } from 'capture-core/metaData'; import type { HideWidgets, WidgetEffects } from '../../common/EnrollmentOverviewDomain'; import type { Event } from '../../common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; @@ -44,7 +43,6 @@ export type Props = { onUpdateEnrollmentStatus: (enrollment: any) => void; onUpdateEnrollmentStatusSuccess: (params?: { redirect?: boolean }) => void; onUpdateEnrollmentStatusError: (message: string) => void; - ruleEffects?: Array<{id: string; type: keyof typeof effectActions}>; widgetEnrollmentStatus?: string | null; pageLayout: PageLayoutConfig; availableWidgets: Readonly<{ [key: string]: WidgetConfig }>; diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.component.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.component.tsx index 6bb4b27e7d..0e4dfb2459 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.component.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.component.tsx @@ -1,13 +1,14 @@ import React, { useState, useMemo, ComponentType } from 'react'; +import { useSelector } from 'react-redux'; import i18n from '@dhis2/d2-i18n'; import { colors, spacers, IconAdd16, IconCalendar16 } from '@dhis2/ui'; import { withStyles, WithStyles } from 'capture-core-utils/styles'; -import type { OutputEffect } from '@dhis2/rules-engine-javascript'; import { Widget } from '../../../../Widget'; import { QuickActionButton } from './QuickActionButton/QuickActionButton'; import { tabMode } from '../../../EnrollmentAddEvent/NewEventWorkspace/newEventWorkspace.constants'; import { useNavigate, buildUrlQueryString, useLocationQuery } from '../../../../../utils/routing'; import { useEnrollmentAccessContext } from '../../../common/EnrollmentOverviewDomain/EnrollmentAccessContext'; +import { getEnrollmentScopeFormId } from '../../../common/EnrollmentOverviewDomain'; import { OwnProps, ProgramStage, EventCount } from './EnrollmentQuickActions.types'; const styles = { @@ -23,7 +24,6 @@ type Props = OwnProps & WithStyles; const EnrollmentQuickActionsComponentPlain = ({ stages, events, - ruleEffects, classes, }: Props) => { const [open, setOpen] = useState(true); @@ -31,6 +31,9 @@ const EnrollmentQuickActionsComponentPlain = ({ const { enrollmentId, programId, teiId, orgUnitId } = useLocationQuery(); const { anyStageWriteAccess } = useEnrollmentAccessContext(); + const hiddenProgramStageIds = useSelector(({ rulesEffectsHiddenProgramStage }: any) => + (enrollmentId ? rulesEffectsHiddenProgramStage?.[getEnrollmentScopeFormId(enrollmentId)] : undefined)); + const stagesWithEventCount = useMemo(() => stages.map((stage) => { const mutatedStage = { ...stage }; mutatedStage.eventCount = (events @@ -40,21 +43,15 @@ const EnrollmentQuickActionsComponentPlain = ({ return mutatedStage; }), [events, stages]); - const hiddenProgramStageRuleEffects = useMemo( - () => ruleEffects?.filter((ruleEffect: OutputEffect): boolean => ruleEffect.type === 'HIDEPROGRAMSTAGE'), - [ruleEffects], - ); - const noStageAvailable = useMemo( () => stagesWithEventCount.every( (programStage: ProgramStage & EventCount) => (!programStage.dataAccess?.write) || (!programStage.repeatable && (programStage.eventCount ?? 0) > 0) || - hiddenProgramStageRuleEffects - ?.find((ruleEffect: OutputEffect) => ruleEffect.id === programStage.id), + Boolean(hiddenProgramStageIds?.[programStage.id]), ), - [stagesWithEventCount, hiddenProgramStageRuleEffects], + [stagesWithEventCount, hiddenProgramStageIds], ); const onNavigationFromQuickActions = (tab: string) => { diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.types.ts b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.types.ts index 2fedaf010f..5172fee92c 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.types.ts +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.types.ts @@ -1,5 +1,3 @@ -import type { OutputEffect } from '@dhis2/rules-engine-javascript'; - export type EventCount = { eventCount?: number }; export type ProgramStage = { @@ -15,5 +13,4 @@ export type Event = { programStage: string }; export type OwnProps = { stages: Array; events: Array; - ruleEffects?: Array; }; diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts index 55abc67ab6..fe52e6aa91 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts @@ -1,56 +1,29 @@ import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { getEnrollmentScopeFormId } from '../../../common/EnrollmentOverviewDomain'; -type RulesProps = { - type: string; - id: string; - error?: { id: string; message: string }; - warning?: { id: string; message: string }; - displayText?: { id: string; message: string }; - displayKeyValuePair?: { id: string; key: string; value: string }; -}; - -export const useFilteredWidgetData = (rulesEffects?: Array | null) => useMemo(() => { - let warnings: any[] = []; - let errors: any[] = []; - let feedbacks: any[] = []; - let indicators: any[] = []; - - const effectTypes = Object.freeze({ - SHOWWARNING: 'SHOWWARNING', - SHOWERROR: 'SHOWERROR', - DISPLAYKEYVALUEPAIRS: 'DISPLAYKEYVALUEPAIRS', - DISPLAYTEXT: 'DISPLAYTEXT', - }); +export const useFilteredWidgetData = (enrollmentId?: string) => { + const formId = enrollmentId ? getEnrollmentScopeFormId(enrollmentId) : undefined; - const effectIDs = Object.freeze({ - general: 'general', - feedback: 'feedback', - indicators: 'indicators', - }); + const generalErrors = useSelector(({ rulesEffectsGeneralErrors }: any) => + (formId ? rulesEffectsGeneralErrors?.[formId] : undefined)); + const generalWarnings = useSelector(({ rulesEffectsGeneralWarnings }: any) => + (formId ? rulesEffectsGeneralWarnings?.[formId] : undefined)); + const feedback = useSelector(({ rulesEffectsFeedback }: any) => + (formId ? rulesEffectsFeedback?.[formId] : undefined)); + const indicators = useSelector(({ rulesEffectsIndicators }: any) => + (formId ? rulesEffectsIndicators?.[formId] : undefined)); - rulesEffects?.forEach((effect) => { - if (effect.id === effectIDs.general) { - switch (effect.type) { - case effectTypes.SHOWWARNING: - warnings = [...warnings, effect.warning]; - break; - case effectTypes.SHOWERROR: - errors = [...errors, effect.error]; - break; - default: - break; - } - } else if (effect.id === effectIDs.feedback) { - feedbacks = [...feedbacks, effect?.displayText || effect?.displayKeyValuePair]; - } else if (effect.id === effectIDs.indicators) { - indicators = [...indicators, effect?.displayText || effect?.displayKeyValuePair]; - } - }); - - return { - warnings, - errors, - feedbacks, - indicators, - }; -}, [rulesEffects]); + return useMemo(() => ({ + warnings: generalWarnings?.warning ?? [], + errors: generalErrors?.error ?? [], + feedbacks: [ + ...(feedback?.displayTexts ?? []), + ...(feedback?.displayKeyValuePairs ?? []), + ], + indicators: [ + ...(indicators?.displayTexts ?? []), + ...(indicators?.displayKeyValuePairs ?? []), + ], + }), [generalErrors, generalWarnings, feedback, indicators]); +}; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx index 6281ffbffa..0c24805eb1 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx @@ -20,7 +20,7 @@ import { showEnrollmentError, updateEnrollmentAndEvents, updateOrAddEnrollmentEvents, - useRuleEffects, + useEnrollmentScopeRuleEffects, } from '../../common/EnrollmentOverviewDomain'; import { useTrackerProgram } from '../../../../hooks/useTrackerProgram'; import { useCoreOrgUnit } from '../../../../metadataRetrieval/coreOrgUnit'; @@ -118,7 +118,8 @@ export const EnrollmentAddEventPageDefault = ({ const trackerProgram = useTrackerProgram(programId); const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); - const ruleEffects = useRuleEffects({ + useEnrollmentScopeRuleEffects({ + enrollmentId, orgUnit: programOwnerOrgUnit, program: trackerProgram, apiEnrollment: enrollment ?? undefined, @@ -219,7 +220,6 @@ export const EnrollmentAddEventPageDefault = ({ onAccessLostFromTransfer={onAccessLostFromTransfer} trackedEntityInactive={trackedEntityInactive} programOwnerId={programOwnerId} - ruleEffects={ruleEffects} /> ); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts index e39ba082c3..39c3d42aa9 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts @@ -1,4 +1,3 @@ -import type { OutputEffect } from '@dhis2/rules-engine-javascript'; import type { ApiEnrollmentEvent } from 'capture-core-utils/types/api-types'; import type { HideWidgets, WidgetEffects } from '../../common/EnrollmentOverviewDomain'; import type { AttributeValue } from '../../common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; @@ -12,7 +11,6 @@ import { Program } from '../../../../metaData'; export type Props = { program: Program | null | undefined; programOwnerId?: string; - ruleEffects?: Array; stageId: string; orgUnitId: string; teiId: string; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx index 9c80dd4e4f..5dd46cfa44 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx @@ -1,10 +1,15 @@ import React, { useEffect, useMemo, useCallback } from 'react'; +import { useSelector } from 'react-redux'; import i18n from '@dhis2/d2-i18n'; import log from 'loglevel'; import { errorCreator } from 'capture-core-utils'; import { ProgramStageSelectorComponent } from './ProgramStageSelector.component'; import { Widget } from '../../../Widget'; -import { useCommonEnrollmentDomainData, useRuleEffects } from '../../common/EnrollmentOverviewDomain'; +import { + useCommonEnrollmentDomainData, + useEnrollmentScopeRuleEffects, + getEnrollmentScopeFormId, +} from '../../common/EnrollmentOverviewDomain'; import type { Props } from './ProgramStageSelector.types'; import { useProgramFromIndexedDB } from '../../../../utils/cachedDataHooks/useProgramFromIndexedDB'; import { useNavigate, useLocationQuery, buildUrlQueryString } from '../../../../utils/routing'; @@ -29,12 +34,15 @@ export const ProgramStageSelector = ({ programId, orgUnitId, teiId, enrollmentId const { orgUnit } = useCoreOrgUnit(programOwnerId); const programRules = useTrackerProgram(programId); - const ruleEffects = useRuleEffects({ + useEnrollmentScopeRuleEffects({ + enrollmentId, orgUnit, program: programRules, apiEnrollment: enrollment, apiAttributeValues: attributeValues, }); + const hiddenProgramStageIds = useSelector(({ rulesEffectsHiddenProgramStage }: any) => + (enrollmentId ? rulesEffectsHiddenProgramStage?.[getEnrollmentScopeFormId(enrollmentId)] : undefined)); useEffect(() => { if (enrollmentsError || programError) { @@ -57,12 +65,10 @@ export const ProgramStageSelector = ({ programId, orgUnitId, teiId, enrollmentId displayName: currentStage.displayName, style: currentStage.style, repeatable: currentStage.repeatable, - hiddenProgramStage: ruleEffects?.find( - (ruleEffect: any) => ruleEffect.type === 'HIDEPROGRAMSTAGE' && ruleEffect.id === currentStage.id, - ), + hiddenProgramStage: Boolean(hiddenProgramStageIds?.[currentStage.id]), }); return accStage; - }, []), [enrollment?.events, program?.programStages, programLoading, ruleEffects]); + }, []), [enrollment?.events, program?.programStages, programLoading, hiddenProgramStageIds]); const onSelectProgramStage = useCallback((newStageId: string) => navigate(`enrollmentEventNew?${buildUrlQueryString({ diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx index c1615e4603..6f80a936e8 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx @@ -27,7 +27,6 @@ export const EnrollmentEditEventPageComponent = ({ teiDisplayName, widgetEffects, hideWidgets, - ruleEffects, programOwnerId, onDelete, onAddNew, @@ -120,7 +119,6 @@ export const EnrollmentEditEventPageComponent = ({ pageStatus={pageStatus} widgetEffects={widgetEffects} hideWidgets={hideWidgets} - ruleEffects={ruleEffects} programOwnerId={programOwnerId} onDelete={onDelete} onAddNew={onAddNew} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx index e24d7e5cce..25608ca73b 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx @@ -14,7 +14,7 @@ import { updateEnrollmentAndEvents, updateEnrollmentEvent, useCommonEnrollmentDomainData, - useRuleEffects, + useEnrollmentScopeRuleEffects, deleteEnrollmentEvent, deleteEnrollmentEventRelationship, updateOrAddEnrollmentEvents, @@ -163,7 +163,8 @@ const EnrollmentEditEventPageWithContextPlain = ({ program.programRules.concat(programStage?.programRules as ProgramRule[]), ); const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); - const ruleEffects = useRuleEffects({ + useEnrollmentScopeRuleEffects({ + enrollmentId, orgUnit: programOwnerOrgUnit, program, apiEnrollment: enrollmentSite, @@ -379,7 +380,6 @@ const EnrollmentEditEventPageWithContextPlain = ({ onUpdateEnrollmentEventsSuccess={onUpdateEnrollmentEventsSuccess} onUpdateEnrollmentEventsError={onUpdateEnrollmentEventsError} userInteractionInProgress={userInteractionInProgress} - ruleEffects={ruleEffects} programOwnerId={programOwnerId} /> diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts index bcac3451e1..7cbefde56a 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts @@ -1,4 +1,3 @@ -import type { OutputEffect } from '@dhis2/rules-engine-javascript'; import type { ProgramStage } from '../../../metaData'; import { Program } from '../../../metaData'; import type { HideWidgets, WidgetEffects } from '../common/EnrollmentOverviewDomain'; @@ -14,7 +13,6 @@ export type PlainProps = { programStage?: ProgramStage | null; widgetEffects: WidgetEffects; hideWidgets: HideWidgets; - ruleEffects?: Array; programOwnerId?: string; teiId: string; enrollmentId: string; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts index 0f72a11406..b5ed2b9076 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts @@ -39,10 +39,9 @@ import { export const QuickActions: WidgetConfig = { Component: EnrollmentQuickActions, - getProps: ({ stages, events, ruleEffects }: any) => ({ + getProps: ({ stages, events }: any) => ({ stages, events, - ruleEffects, }), }; @@ -58,7 +57,6 @@ export const StagesAndEvents: WidgetConfig = { onUpdateEventStatus, onRollbackDeleteEvent, onEventClick, - ruleEffects, }: any): StagesAndEventProps => ({ programId: program.id, stages, @@ -69,7 +67,6 @@ export const StagesAndEvents: WidgetConfig = { onUpdateEventStatus, onRollbackDeleteEvent, onEventClick, - ruleEffects, }), }; @@ -147,7 +144,6 @@ export const ProfileWidget: WidgetConfig = { onUpdateTeiAttributeValues, onDeleteTrackedEntitySuccess, onStatusToggleSuccess, - ruleEffects, }: any): WidgetProfileProps => ({ teiId, programId: program.id, @@ -155,7 +151,6 @@ export const ProfileWidget: WidgetConfig = { onUpdateTeiAttributeValues, onDeleteSuccess: onDeleteTrackedEntitySuccess, onStatusToggleSuccess, - ruleEffects, }), }; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts index fba2ed7c0a..c1464cef72 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts @@ -20,7 +20,7 @@ export { rollbackEnrollmentEvents, } from './enrollment.actions'; export { useCommonEnrollmentDomainData } from './useCommonEnrollmentDomainData'; -export { useRuleEffects } from './useRuleEffects'; +export { useRuleEffects, useEnrollmentScopeRuleEffects, getEnrollmentScopeFormId } from './useRuleEffects'; export { EnrollmentAccessProvider, useEnrollmentAccessContext, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/getEnrollmentScopeFormId.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/getEnrollmentScopeFormId.ts new file mode 100644 index 0000000000..0a2ccaca9e --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/getEnrollmentScopeFormId.ts @@ -0,0 +1 @@ +export const getEnrollmentScopeFormId = (enrollmentId: string) => `enrollment-${enrollmentId}`; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts index 6db0ef93c9..c06114ff22 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts @@ -1,2 +1,4 @@ export { useRuleEffects } from './useRuleEffects'; +export { useEnrollmentScopeRuleEffects } from './useEnrollmentScopeRuleEffects'; +export { getEnrollmentScopeFormId } from './getEnrollmentScopeFormId'; export type { UseRuleEffectsInput } from './useRuleEffects.types'; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts new file mode 100644 index 0000000000..469202a421 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts @@ -0,0 +1,74 @@ +import { useMemo } from 'react'; +import { convertValue } from '../../../../../converters/serverToClient'; +import { dataElementTypes, getTrackerProgramThrowIfNotFound, type TrackerProgram } from '../../../../../metaData'; +import type { AttributeValue, EnrollmentData } from '../useCommonEnrollmentDomainData'; + +const convertDate = (date: string): string => convertValue(date, dataElementTypes.DATE); + +const getDataElementsInProgram = (program: TrackerProgram) => + [...program.stages.values()] + .reduce((acc, { stageForm }: any) => ({ + ...acc, + ...stageForm.getElementsById(), + }), {}); + +const getClientFormattedDataValuesAsObject = (dataValues: any, elementsById: any) => + dataValues.reduce((acc: any, { dataElement: id, value }: any) => { + const dataElement = elementsById[id]; + if (dataElement) { + acc[id] = convertValue(value, elementsById[id].type); + } + return acc; + }, {}); + +export const useEventsData = (enrollment: EnrollmentData | undefined, program: TrackerProgram) => { + const elementsById = useMemo(() => getDataElementsInProgram(program), [program]); + + return useMemo(() => enrollment && enrollment.events.map((event: any) => ({ + eventId: event.event, + programId: event.program, + programStageId: event.programStage, + orgUnitId: event.orgUnit, + trackedEntityInstanceId: event.trackedEntity, + enrollmentId: event.enrollment, + enrollmentStatus: event.enrollmentStatus, + status: event.status, + occurredAt: convertDate(event.occurredAt), + scheduledAt: convertDate(event.scheduledAt), + ...getClientFormattedDataValuesAsObject(event.dataValues, elementsById), + })), [elementsById, enrollment]); +}; + +export const useEnrollmentData = (enrollment: EnrollmentData | undefined) => useMemo(() => { + if (!enrollment) { + return undefined; + } + + const { enrollment: enrollmentId, enrolledAt, occurredAt, status, program } = enrollment; + + return { + enrolledAt: convertDate(enrolledAt), + occurredAt: occurredAt ? convertDate(occurredAt) : undefined, + enrollmentId, + enrollmentStatus: status, + programName: getTrackerProgramThrowIfNotFound(program).name, + }; +}, [enrollment]); + +export const useAttributeValuesForRules = ( + program: TrackerProgram, + apiAttributeValues: Array | undefined, +) => { + const attributesObject = useMemo(() => + program.attributes.reduce((acc: any, attribute: any) => { + acc[attribute.id] = attribute; + return acc; + }, {}), [program.attributes]); + + return useMemo(() => apiAttributeValues && + apiAttributeValues + .reduce((acc: any, { id, value }) => { + acc[id] = convertValue(value, attributesObject[id]?.type); + return acc; + }, {}), [apiAttributeValues, attributesObject]); +}; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts new file mode 100644 index 0000000000..0313918dfa --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts @@ -0,0 +1,65 @@ +import { useEffect, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { getApplicableRuleEffectsForTrackerProgram, updateRulesEffects } from '../../../../../rules'; +import type { TrackerProgram } from '../../../../../metaData'; +import { getEnrollmentScopeFormId } from './getEnrollmentScopeFormId'; +import { useAttributeValuesForRules, useEnrollmentData, useEventsData } from './rulesExecutionData'; +import type { AttributeValue, EnrollmentData } from '../useCommonEnrollmentDomainData'; + +type Input = { + enrollmentId?: string; + orgUnit?: any; + program?: TrackerProgram; + apiEnrollment?: EnrollmentData; + apiAttributeValues?: Array; + force?: boolean; +}; + +export const useEnrollmentScopeRuleEffects = ({ + enrollmentId, + orgUnit, + program, + apiEnrollment, + apiAttributeValues, + force = false, +}: Input) => { + const dispatch = useDispatch(); + const formId = enrollmentId ? getEnrollmentScopeFormId(enrollmentId) : undefined; + + const hasWarmStoreOnMount = useSelector(({ rulesEffectsHiddenFields }: any) => + (formId ? rulesEffectsHiddenFields?.[formId] !== undefined : false)); + const skipOnce = useRef(!force && hasWarmStoreOnMount); + + const attributeValues = useAttributeValuesForRules( + program as TrackerProgram, + apiAttributeValues, + ); + const enrollmentData = useEnrollmentData(apiEnrollment); + const otherEvents = useEventsData(apiEnrollment, program as TrackerProgram); + + useEffect(() => { + if (!formId || !program) return; + if (!(orgUnit && attributeValues && enrollmentData && otherEvents)) return; + if (skipOnce.current) { + skipOnce.current = false; + return; + } + + const effects = getApplicableRuleEffectsForTrackerProgram({ + program, + orgUnit, + otherEvents, + attributeValues, + enrollmentData, + }); + dispatch(updateRulesEffects(effects, formId)); + }, [ + dispatch, + formId, + program, + orgUnit, + attributeValues, + enrollmentData, + otherEvents, + ]); +}; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts index b63f1603ab..4cbfa20677 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts @@ -1,78 +1,12 @@ -import { useEffect, useMemo, useState } from 'react'; -import { convertValue } from '../../../../../converters/serverToClient'; +import { useEffect, useState } from 'react'; import { getApplicableRuleEffectsForTrackerProgram } from '../../../../../rules'; -import { dataElementTypes, getTrackerProgramThrowIfNotFound, type TrackerProgram } from '../../../../../metaData'; +import { useAttributeValuesForRules, useEnrollmentData, useEventsData } from './rulesExecutionData'; import type { UseRuleEffectsInput } from './useRuleEffects.types'; -const convertDate = (date: string): string => convertValue(date, dataElementTypes.DATE); - -const getDataElementsInProgram = (program: TrackerProgram) => - [...program.stages.values()] - .reduce((acc, { stageForm }: any) => ({ - ...acc, - ...stageForm.getElementsById(), - }), {}); - -const getClientFormattedDataValuesAsObject = (dataValues: any, elementsById: any) => - dataValues.reduce((acc: any, { dataElement: id, value }: any) => { - const dataElement = elementsById[id]; - if (dataElement) { - acc[id] = convertValue(value, elementsById[id].type); - } - return acc; - }, {}); - -const useEventsData = (enrollment: any, program: TrackerProgram) => { - const elementsById = useMemo(() => getDataElementsInProgram(program), [program]); - - return useMemo(() => enrollment && enrollment.events.map((event: any) => ({ - eventId: event.event, - programId: event.program, - programStageId: event.programStage, - orgUnitId: event.orgUnit, - trackedEntityInstanceId: event.trackedEntity, - enrollmentId: event.enrollment, - enrollmentStatus: event.enrollmentStatus, - status: event.status, - occurredAt: convertDate(event.occurredAt), - scheduledAt: convertDate(event.scheduledAt), - ...getClientFormattedDataValuesAsObject(event.dataValues, elementsById), - })), [elementsById, enrollment]); -}; - -const useEnrollmentData = (enrollment: any) => useMemo(() => { - if (!enrollment) { - return undefined; - } - - const { enrollment: enrollmentId, enrolledAt, occurredAt, status, program } = enrollment; - - return { - enrolledAt: convertDate(enrolledAt), - occurredAt: occurredAt ? convertDate(occurredAt) : undefined, - enrollmentId, - enrollmentStatus: status, - programName: getTrackerProgramThrowIfNotFound(program).name, - }; -}, [enrollment]); - export const useRuleEffects = ({ orgUnit, program, apiEnrollment, apiAttributeValues }: UseRuleEffectsInput) => { const [ruleEffects, setRuleEffects] = useState(undefined); - const attributesObject = useMemo(() => - program.attributes.reduce((acc: any, attribute: any) => { - acc[attribute.id] = attribute; - return acc; - }, {}), [program.attributes]); - - const attributeValues = useMemo(() => apiAttributeValues && - apiAttributeValues - .reduce((acc: any, { id, value }) => { - acc[id] = convertValue(value, attributesObject[id]?.type); - return acc; - }, {}), [apiAttributeValues, attributesObject]); - + const attributeValues = useAttributeValuesForRules(program, apiAttributeValues); const enrollmentData = useEnrollmentData(apiEnrollment); - const otherEvents = useEventsData(apiEnrollment, program); useEffect(() => { diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 4006ff09d8..2cec7c407d 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -7,7 +7,6 @@ import log from 'loglevel'; import { FlatList } from 'capture-ui'; import { useQueryClient } from '@tanstack/react-query'; import { errorCreator } from 'capture-core-utils'; -import { effectActions } from '@dhis2/rules-engine-javascript'; import { Widget } from '../Widget'; import { LoadingMaskElementCenter } from '../LoadingMasks'; import { NoticeBox } from '../NoticeBox'; @@ -27,6 +26,7 @@ import { useDataEntryFormConfig, } from '../DataEntries/common/TEIAndEnrollment'; import { useEnrollmentAccessContext } from '../Pages/common/EnrollmentOverviewDomain/EnrollmentAccessContext'; +import { getEnrollmentScopeFormId } from '../Pages/common/EnrollmentOverviewDomain'; const styles: Readonly = { header: { @@ -73,7 +73,6 @@ const WidgetProfilePlain = ({ programId, readOnlyMode = false, programOwnerId = '', - ruleEffects, onUpdateTeiAttributeValues, onDeleteSuccess, onStatusToggleSuccess, @@ -88,6 +87,9 @@ const WidgetProfilePlain = ({ storedGeometry: trackedEntityInstance?.geometry, hasError: trackedEntityInstance?.hasError, })); + const enrollmentId = useSelector(({ enrollmentDomain }: any) => enrollmentDomain?.enrollmentId); + const hiddenAttributeIds = useSelector(({ rulesEffectsHiddenFields }: any) => + (enrollmentId ? rulesEffectsHiddenFields?.[getEnrollmentScopeFormId(enrollmentId)] : undefined)); const { configIsFetched, dataEntryFormConfig } = useDataEntryFormConfig({ selectedScopeId: programId }); const { loading: trackedEntityInstancesLoading, @@ -133,11 +135,10 @@ const WidgetProfilePlain = ({ const teiDisplayName = useTeiDisplayName(program, storedAttributeValues, clientAttributesWithSubvalues, teiId); const displayChangelog = program?.trackedEntityType?.changelogEnabled; - const hiddenFieldIds = useMemo(() => new Set( - (ruleEffects ?? []) - .filter(effect => effect.type === effectActions.HIDE_FIELD) - .map(effect => effect.id), - ), [ruleEffects]); + const hiddenFieldIds = useMemo( + () => new Set(hiddenAttributeIds ? Object.keys(hiddenAttributeIds) : []), + [hiddenAttributeIds], + ); const displayInListAttributes = useMemo(() => clientAttributesWithSubvalues .filter((item: any) => item.displayInList && !hiddenFieldIds.has(item.attribute)) diff --git a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts index 6bf7fc8c26..903e52096c 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts @@ -1,11 +1,8 @@ -import type { OutputEffect } from '@dhis2/rules-engine-javascript'; - export type Props = { teiId: string; programId: string; programOwnerId: string; readOnlyMode?: boolean; - ruleEffects?: Array; onUpdateTeiAttributeValues?: (attributes: Array<{ [key: string]: string }>, teiDisplayName: string) => void; onDeleteSuccess?: () => void; onStatusToggleSuccess?: () => void; diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.tsx b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.tsx index 0950bee130..915af5cf17 100644 --- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.tsx +++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.tsx @@ -1,4 +1,5 @@ import React, { useState, useCallback } from 'react'; +import { useSelector } from 'react-redux'; import { withStyles, type WithStyles } from 'capture-core-utils/styles'; import { spacersNum } from '@dhis2/ui'; import { StageOverview } from './StageOverview'; @@ -7,6 +8,8 @@ import { Widget } from '../../../Widget'; import { StageDetail } from './StageDetail/StageDetail.component'; import { StageCreateNewButton } from './StageCreateNewButton'; import { useEnrollmentAccessContext } from '../../../Pages/common/EnrollmentOverviewDomain/EnrollmentAccessContext'; +import { getEnrollmentScopeFormId } from '../../../Pages/common/EnrollmentOverviewDomain'; +import { useLocationQuery } from '../../../../utils/routing'; const styles = { overview: { @@ -22,16 +25,15 @@ const styles = { }, }; -const rulesEffectHideProgramStage = (ruleEffects: Array<{id: string, type: string}> | undefined, stageId: string) => ( - Boolean(ruleEffects?.find(ruleEffect => ruleEffect.type === 'HIDEPROGRAMSTAGE' && ruleEffect.id === stageId)) -); - export const StagePlain = ({ - stage, events, classes, onCreateNew, ruleEffects, ...passOnProps + stage, events, classes, onCreateNew, ...passOnProps }: Props & WithStyles) => { const [open, setOpenStatus] = useState(true); const { id, name, icon, description, dataElements, hideDueDate, repeatable, enableUserAssignment } = stage; - const preventAddingNewEvents = rulesEffectHideProgramStage(ruleEffects, id); + const { enrollmentId } = useLocationQuery(); + const hiddenProgramStageIds = useSelector(({ rulesEffectsHiddenProgramStage }: any) => + (enrollmentId ? rulesEffectsHiddenProgramStage?.[getEnrollmentScopeFormId(enrollmentId)] : undefined)); + const preventAddingNewEvents = Boolean(hiddenProgramStageIds?.[id]); const hideProgramStage = preventAddingNewEvents && events.length === 0; const { stageWriteAccessById } = useEnrollmentAccessContext(); const effectiveStageWriteAccess = stageWriteAccessById[stage.id] ?? stage.dataAccess.write; diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/types/common.types.ts b/src/core_modules/capture-core/components/WidgetStagesAndEvents/types/common.types.ts index 6bd599fae1..d3465f9c2a 100644 --- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/types/common.types.ts +++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/types/common.types.ts @@ -1,4 +1,3 @@ -import { effectActions } from '@dhis2/rules-engine-javascript'; import type { Icon } from 'capture-core/metaData'; import type { ApiAssignedUser } from 'capture-core-utils/types/api-types'; import { dataElementTypes, Option } from '../../../metaData'; @@ -43,7 +42,6 @@ export type StageCommonProps = { onViewAll: (stageId: string) => void; onCreateNew: (stageId: string) => void; onEventClick: (eventId: string) => void; - ruleEffects?: Array<{id: string; type: typeof effectActions[keyof typeof effectActions]}>; } export type DataValue = { From 7044c91f9e4a8e0cbe4c5a3d17f62ce6a9dcf111 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:10:59 +0000 Subject: [PATCH 25/40] fix: clean up --- i18n/en.pot | 4 +- .../enrollment.actions.ts | 5 +- .../useRuleEffects/rulesExecutionData.ts | 2 +- .../useEnrollmentScopeRuleEffects.ts | 50 +++++++------------ 4 files changed, 24 insertions(+), 37 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 9156d8af60..ed2e4605fb 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-26T15:48:56.295Z\n" -"PO-Revision-Date: 2026-08-26T15:48:56.295Z\n" +"POT-Creation-Date: 2026-08-26T18:11:01.274Z\n" +"PO-Revision-Date: 2026-08-26T18:11:01.274Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts index f8438b1524..84dcf5e26a 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts @@ -1,4 +1,5 @@ import { actionCreator } from '../../../../actions/actions.utils'; +import type { AttributeValue, EnrollmentData } from './useCommonEnrollmentDomainData'; export const enrollmentSiteActionTypes = { COMMON_ENROLLMENT_SITE_DATA_SET: 'EnrollmentSite.SetCommonData', @@ -26,8 +27,8 @@ export const enrollmentSiteActionTypes = { }; export const setCommonEnrollmentSiteData = ( - enrollment: Record, - attributeValues: Array>, + enrollment: EnrollmentData, + attributeValues: Array, inactive?: boolean, programOwnerId?: string, ) => actionCreator(enrollmentSiteActionTypes.COMMON_ENROLLMENT_SITE_DATA_SET)({ diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts index 469202a421..156119e732 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts @@ -24,7 +24,7 @@ const getClientFormattedDataValuesAsObject = (dataValues: any, elementsById: any export const useEventsData = (enrollment: EnrollmentData | undefined, program: TrackerProgram) => { const elementsById = useMemo(() => getDataElementsInProgram(program), [program]); - return useMemo(() => enrollment && enrollment.events.map((event: any) => ({ + return useMemo(() => enrollment?.events.map((event: any) => ({ eventId: event.event, programId: event.program, programStageId: event.programStage, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts index 0313918dfa..9092626872 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts @@ -9,7 +9,7 @@ import type { AttributeValue, EnrollmentData } from '../useCommonEnrollmentDomai type Input = { enrollmentId?: string; orgUnit?: any; - program?: TrackerProgram; + program: TrackerProgram; apiEnrollment?: EnrollmentData; apiAttributeValues?: Array; force?: boolean; @@ -24,42 +24,28 @@ export const useEnrollmentScopeRuleEffects = ({ force = false, }: Input) => { const dispatch = useDispatch(); - const formId = enrollmentId ? getEnrollmentScopeFormId(enrollmentId) : undefined; - + const formId = enrollmentId && getEnrollmentScopeFormId(enrollmentId); const hasWarmStoreOnMount = useSelector(({ rulesEffectsHiddenFields }: any) => (formId ? rulesEffectsHiddenFields?.[formId] !== undefined : false)); const skipOnce = useRef(!force && hasWarmStoreOnMount); - - const attributeValues = useAttributeValuesForRules( - program as TrackerProgram, - apiAttributeValues, - ); + const attributeValues = useAttributeValuesForRules(program, apiAttributeValues); const enrollmentData = useEnrollmentData(apiEnrollment); - const otherEvents = useEventsData(apiEnrollment, program as TrackerProgram); + const otherEvents = useEventsData(apiEnrollment, program); useEffect(() => { - if (!formId || !program) return; - if (!(orgUnit && attributeValues && enrollmentData && otherEvents)) return; - if (skipOnce.current) { - skipOnce.current = false; - return; + if (formId && orgUnit && attributeValues && enrollmentData && otherEvents) { + if (skipOnce.current) { + skipOnce.current = false; + return; + } + const effects = getApplicableRuleEffectsForTrackerProgram({ + program, + orgUnit, + otherEvents, + attributeValues, + enrollmentData, + }); + dispatch(updateRulesEffects(effects, formId)); } - - const effects = getApplicableRuleEffectsForTrackerProgram({ - program, - orgUnit, - otherEvents, - attributeValues, - enrollmentData, - }); - dispatch(updateRulesEffects(effects, formId)); - }, [ - dispatch, - formId, - program, - orgUnit, - attributeValues, - enrollmentData, - otherEvents, - ]); + }, [dispatch, formId, attributeValues, enrollmentData, orgUnit, otherEvents, program]); }; From b75282eff493f0d7c3ed1f189277e4b0a6b895e8 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:12:05 +0000 Subject: [PATCH 26/40] fix: hide progam stage bug --- i18n/en.pot | 4 ++-- .../useRuleEffects/rulesExecutionData.ts | 11 +++++------ .../hooks/useCanAddNewEventToStage.ts | 2 +- .../descriptions/rulesEffects.reducerDescription.ts | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index ed2e4605fb..20bb15b91c 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-26T18:11:01.274Z\n" -"PO-Revision-Date: 2026-08-26T18:11:01.274Z\n" +"POT-Creation-Date: 2026-08-26T19:12:06.813Z\n" +"PO-Revision-Date: 2026-08-26T19:12:06.814Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts index 156119e732..9fe52eb626 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/rulesExecutionData.ts @@ -65,10 +65,9 @@ export const useAttributeValuesForRules = ( return acc; }, {}), [program.attributes]); - return useMemo(() => apiAttributeValues && - apiAttributeValues - .reduce((acc: any, { id, value }) => { - acc[id] = convertValue(value, attributesObject[id]?.type); - return acc; - }, {}), [apiAttributeValues, attributesObject]); + return useMemo(() => apiAttributeValues + ?.reduce((acc: any, { id, value }) => { + acc[id] = convertValue(value, attributesObject[id]?.type); + return acc; + }, {}), [apiAttributeValues, attributesObject]); }; diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useCanAddNewEventToStage.ts b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useCanAddNewEventToStage.ts index 3b7897b28f..2ebcd50d17 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useCanAddNewEventToStage.ts +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useCanAddNewEventToStage.ts @@ -5,7 +5,7 @@ import type { RelatedStagesEvents } from '../RelatedStagesActions/RelatedStagesA export const useCanAddNewEventToStage = (programStage?: ProgramStage, existingRelatedEvents: RelatedStagesEvents[] = []) => { const hiddenProgramStages = useSelector((state: any) => - state.rulesEffectsHiddenProgramStageDesc?.['enrollmentEvent-newEvent'], + state.rulesEffectsHiddenProgramStage?.['enrollmentEvent-newEvent'], ); return useMemo(() => { diff --git a/src/core_modules/capture-core/reducers/descriptions/rulesEffects.reducerDescription.ts b/src/core_modules/capture-core/reducers/descriptions/rulesEffects.reducerDescription.ts index 88681b4a23..a299d3711a 100644 --- a/src/core_modules/capture-core/reducers/descriptions/rulesEffects.reducerDescription.ts +++ b/src/core_modules/capture-core/reducers/descriptions/rulesEffects.reducerDescription.ts @@ -83,7 +83,7 @@ export const rulesEffectsHiddenProgramStageDesc = createReducerDescription({ return newState; }, -}, 'rulesEffectsHiddenProgramStageDesc'); +}, 'rulesEffectsHiddenProgramStage'); const mapMessageEffectTypeToStateKey = { From f21ee948fede783de4b3f8d44b730b2545959726 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:17:36 +0000 Subject: [PATCH 27/40] fix: (review) remove unused rule effects logic and types --- i18n/en.pot | 4 +-- .../common/EnrollmentOverviewDomain/index.ts | 2 +- .../useRuleEffects/index.ts | 2 -- .../useRuleEffects/useRuleEffects.ts | 28 ------------------- .../useRuleEffects/useRuleEffects.types.ts | 12 -------- 5 files changed, 3 insertions(+), 45 deletions(-) delete mode 100644 src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts delete mode 100644 src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts diff --git a/i18n/en.pot b/i18n/en.pot index 20bb15b91c..0f21815df0 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-26T19:12:06.813Z\n" -"PO-Revision-Date: 2026-08-26T19:12:06.814Z\n" +"POT-Creation-Date: 2026-09-01T12:17:38.501Z\n" +"PO-Revision-Date: 2026-09-01T12:17:38.501Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts index c1464cef72..5d2da97f71 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts @@ -20,7 +20,7 @@ export { rollbackEnrollmentEvents, } from './enrollment.actions'; export { useCommonEnrollmentDomainData } from './useCommonEnrollmentDomainData'; -export { useRuleEffects, useEnrollmentScopeRuleEffects, getEnrollmentScopeFormId } from './useRuleEffects'; +export { useEnrollmentScopeRuleEffects, getEnrollmentScopeFormId } from './useRuleEffects'; export { EnrollmentAccessProvider, useEnrollmentAccessContext, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts index c06114ff22..364cc9a404 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts @@ -1,4 +1,2 @@ -export { useRuleEffects } from './useRuleEffects'; export { useEnrollmentScopeRuleEffects } from './useEnrollmentScopeRuleEffects'; export { getEnrollmentScopeFormId } from './getEnrollmentScopeFormId'; -export type { UseRuleEffectsInput } from './useRuleEffects.types'; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts deleted file mode 100644 index 4cbfa20677..0000000000 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect, useState } from 'react'; -import { getApplicableRuleEffectsForTrackerProgram } from '../../../../../rules'; -import { useAttributeValuesForRules, useEnrollmentData, useEventsData } from './rulesExecutionData'; -import type { UseRuleEffectsInput } from './useRuleEffects.types'; - -export const useRuleEffects = ({ orgUnit, program, apiEnrollment, apiAttributeValues }: UseRuleEffectsInput) => { - const [ruleEffects, setRuleEffects] = useState(undefined); - const attributeValues = useAttributeValuesForRules(program, apiAttributeValues); - const enrollmentData = useEnrollmentData(apiEnrollment); - const otherEvents = useEventsData(apiEnrollment, program); - - useEffect(() => { - if (orgUnit && attributeValues && enrollmentData && otherEvents) { - const effects = getApplicableRuleEffectsForTrackerProgram({ - program, - orgUnit, - otherEvents, - attributeValues, - enrollmentData, - }, true); - if (Array.isArray(effects)) { - setRuleEffects(effects); - } - } - }, [attributeValues, enrollmentData, orgUnit, otherEvents, program]); - - return ruleEffects; -}; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts deleted file mode 100644 index 236e9804f8..0000000000 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { TrackerProgram } from 'capture-core/metaData'; -import type { - EnrollmentData, - AttributeValue, -} from '../useCommonEnrollmentDomainData'; - -export type UseRuleEffectsInput = { - orgUnit?: any; - program: TrackerProgram; - apiEnrollment?: EnrollmentData; - apiAttributeValues?: Array; -}; From ea9238ba2e2e89b7f7652fd1dc6797ec5c21241b Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:49:16 +0000 Subject: [PATCH 28/40] fix: (review) update condition in useEffect to prevent unnecessary dispatch --- i18n/en.pot | 4 ++-- .../useCommonEnrollmentDomainData.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 0f21815df0..bbdb68ee58 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-01T12:17:38.501Z\n" -"PO-Revision-Date: 2026-09-01T12:17:38.501Z\n" +"POT-Creation-Date: 2026-09-01T14:49:17.844Z\n" +"PO-Revision-Date: 2026-09-01T14:49:17.844Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts index 387dcdbb5d..efc2a09944 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts @@ -42,7 +42,7 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin }; useEffect(() => { - if (fetchedEnrollmentData.reference) { + if (fetchedEnrollmentData.reference && storedEnrollmentId !== enrollmentId) { dispatch(setCommonEnrollmentSiteData( fetchedEnrollmentData.enrollment, fetchedEnrollmentData.attributeValues @@ -53,6 +53,8 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin } }, [ dispatch, + enrollmentId, + storedEnrollmentId, fetchedEnrollmentData.reference, fetchedEnrollmentData.enrollment, fetchedEnrollmentData.attributeValues, From 459b0b0fc1dd01e5f16fa51665511ff2b0769314 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:56:23 +0000 Subject: [PATCH 29/40] fix: align edit profile modal rule engine input with other scopes --- i18n/en.pot | 4 ++-- .../useCommonEnrollmentDomainData.ts | 17 +++++++++++++---- .../DataEntry/ProgramRules/rulesContainer.ts | 2 ++ .../DataEntry/helpers/convertors.ts | 3 +++ .../WidgetProfile/DataEntry/helpers/index.ts | 1 + 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index bbdb68ee58..7fc2db105a 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-01T14:49:17.844Z\n" -"PO-Revision-Date: 2026-09-01T14:49:17.844Z\n" +"POT-Creation-Date: 2026-09-03T11:56:25.377Z\n" +"PO-Revision-Date: 2026-09-03T11:56:25.377Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts index efc2a09944..00e96ab70a 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts @@ -4,6 +4,13 @@ import { setCommonEnrollmentSiteData } from '../enrollment.actions'; import type { Output } from './useCommonEnrollmentDomainData.types'; import { useApiDataQuery } from '../../../../../utils/reactQueryHelpers'; +// Program rules can reference program-scoped TEAs (stored on the enrollment) as well as +// TET-scoped TEAs. Merge both so the value map fed to the rules engine is complete. +const mergeAttributeValues = (enrollmentAttrs: Array = [], teAttrs: Array = []) => [ + ...enrollmentAttrs, + ...teAttrs.filter(ta => !enrollmentAttrs.some(ea => ea.attribute === ta.attribute)), +]; + export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: string, programId: string): Output => { const dispatch = useDispatch(); @@ -22,7 +29,7 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin id: teiId, params: { program: programId, - fields: ['enrollments[*,!attributes],attributes,inactive,programOwners[program,orgUnit]'], + fields: ['enrollments[*],attributes,inactive,programOwners[program,orgUnit]'], }, }, { @@ -32,11 +39,13 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin }, ) as any; + const enrollment = data?.enrollments?.find((e: any) => e.enrollment === enrollmentId); + const attributeValues = mergeAttributeValues(enrollment?.attributes, data?.attributes); + const fetchedEnrollmentData = { reference: data, - enrollment: data?.enrollments - ?.find((enrollment: any) => enrollment.enrollment === enrollmentId), - attributeValues: data?.attributes, + enrollment, + attributeValues, inactive: Boolean(data?.inactive), programOwnerId: data?.programOwners?.find((p: any) => p.program === programId)?.orgUnit, }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.ts index 1ce26826e3..6c7e0b086b 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.ts @@ -5,6 +5,7 @@ import { getProgramId, getProgramRuleActions, getProgramStageId, + getUseNameForOptionSet, } from '../helpers'; const addProgramVariables = (program: any, programRuleVariables: any[]) => { @@ -13,6 +14,7 @@ const addProgramVariables = (program: any, programRuleVariables: any[]) => { programId: getProgramId(programRulesVariable), dataElementId: getDataElementId(programRulesVariable), trackedEntityAttributeId: getTrackedEntityAttributeId(programRulesVariable), + useNameForOptionSet: getUseNameForOptionSet(programRulesVariable), })); }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/convertors.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/convertors.ts index 4626c508e7..a93a3159f3 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/convertors.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/convertors.ts @@ -14,6 +14,9 @@ export const getOptionSetId = (valueApi: any) => valueApi?.optionSet?.id; export const getProgramId = (valueApi: any) => valueApi?.program?.id; +export const getUseNameForOptionSet = (valueApi: any) => + (valueApi?.useCodeForOptionSet != null ? !valueApi.useCodeForOptionSet : false); + export const getTrackedEntityTypeId = (valueApi: any) => valueApi?.trackedEntityType?.id; export const getProgramTrackedEntityAttributes = (valueApi: any) => diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/index.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/index.ts index faa955bdb1..dbad3e1d0f 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/index.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/index.ts @@ -7,6 +7,7 @@ export { getOptionId, getProgramId, getProgramRuleActions, + getUseNameForOptionSet, getOptionSetId, getTrackedEntityTypeId, getProgramTrackedEntityAttributes, From 3d172a9843a57958ca382f100aa7aecaa3b241bb Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:49:31 +0000 Subject: [PATCH 30/40] fix: revert and splitt fixes into two tickets --- i18n/en.pot | 4 ++-- .../useCommonEnrollmentDomainData.ts | 17 ++++------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 7fc2db105a..d052b9f1b9 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-03T11:56:25.377Z\n" -"PO-Revision-Date: 2026-09-03T11:56:25.377Z\n" +"POT-Creation-Date: 2026-09-08T07:49:34.048Z\n" +"PO-Revision-Date: 2026-09-08T07:49:34.048Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts index 00e96ab70a..efc2a09944 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts @@ -4,13 +4,6 @@ import { setCommonEnrollmentSiteData } from '../enrollment.actions'; import type { Output } from './useCommonEnrollmentDomainData.types'; import { useApiDataQuery } from '../../../../../utils/reactQueryHelpers'; -// Program rules can reference program-scoped TEAs (stored on the enrollment) as well as -// TET-scoped TEAs. Merge both so the value map fed to the rules engine is complete. -const mergeAttributeValues = (enrollmentAttrs: Array = [], teAttrs: Array = []) => [ - ...enrollmentAttrs, - ...teAttrs.filter(ta => !enrollmentAttrs.some(ea => ea.attribute === ta.attribute)), -]; - export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: string, programId: string): Output => { const dispatch = useDispatch(); @@ -29,7 +22,7 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin id: teiId, params: { program: programId, - fields: ['enrollments[*],attributes,inactive,programOwners[program,orgUnit]'], + fields: ['enrollments[*,!attributes],attributes,inactive,programOwners[program,orgUnit]'], }, }, { @@ -39,13 +32,11 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin }, ) as any; - const enrollment = data?.enrollments?.find((e: any) => e.enrollment === enrollmentId); - const attributeValues = mergeAttributeValues(enrollment?.attributes, data?.attributes); - const fetchedEnrollmentData = { reference: data, - enrollment, - attributeValues, + enrollment: data?.enrollments + ?.find((enrollment: any) => enrollment.enrollment === enrollmentId), + attributeValues: data?.attributes, inactive: Boolean(data?.inactive), programOwnerId: data?.programOwners?.find((p: any) => p.program === programId)?.orgUnit, }; From f91e5bb00f6d3535a382529c840476400ab10245 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 9 Sep 2026 09:12:43 +0000 Subject: [PATCH 31/40] fix: (review) refactor enrollment rule effects handling --- i18n/en.pot | 4 +- .../EnrollmentPageDefault.container.tsx | 2 +- .../EnrollmentQuickActions.component.tsx | 5 +- .../hooks/useFilteredWidgetData.ts | 29 +---------- .../ProgramStageSelector.container.tsx | 5 +- .../enrollment.actions.ts | 4 ++ .../common/EnrollmentOverviewDomain/index.ts | 7 ++- .../enrollmentRuleEffects.selectors.ts | 50 +++++++++++++++++++ .../getEnrollmentScopeFormId.ts | 1 - .../useRuleEffects/index.ts | 6 ++- .../useEnrollmentScopeRuleEffects.ts | 15 +++--- .../WidgetProfile/WidgetProfile.component.tsx | 6 +-- .../Stages/Stage/Stage.component.tsx | 7 +-- .../enrollmentDomain.reducerDescription.ts | 6 +++ 14 files changed, 91 insertions(+), 56 deletions(-) create mode 100644 src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/enrollmentRuleEffects.selectors.ts delete mode 100644 src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/getEnrollmentScopeFormId.ts diff --git a/i18n/en.pot b/i18n/en.pot index d052b9f1b9..63c451adcc 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-08T07:49:34.048Z\n" -"PO-Revision-Date: 2026-09-08T07:49:34.048Z\n" +"POT-Creation-Date: 2026-09-09T09:12:44.956Z\n" +"PO-Revision-Date: 2026-09-09T09:12:44.956Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index 186384bf79..224c6ba9d8 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -103,7 +103,7 @@ export const EnrollmentPageDefault = () => { force: true, }); - const outputEffects = useFilteredWidgetData(enrollmentId); + const outputEffects = useFilteredWidgetData(); const hideWidgets = useHideWidgetByRuleLocations(program.programRules); const onDeleteTrackedEntitySuccess = useCallback(() => { diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.component.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.component.tsx index 0e4dfb2459..1395f898c7 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.component.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/EnrollmentQuickActions.component.tsx @@ -8,7 +8,7 @@ import { QuickActionButton } from './QuickActionButton/QuickActionButton'; import { tabMode } from '../../../EnrollmentAddEvent/NewEventWorkspace/newEventWorkspace.constants'; import { useNavigate, buildUrlQueryString, useLocationQuery } from '../../../../../utils/routing'; import { useEnrollmentAccessContext } from '../../../common/EnrollmentOverviewDomain/EnrollmentAccessContext'; -import { getEnrollmentScopeFormId } from '../../../common/EnrollmentOverviewDomain'; +import { selectEnrollmentHiddenProgramStageIds } from '../../../common/EnrollmentOverviewDomain'; import { OwnProps, ProgramStage, EventCount } from './EnrollmentQuickActions.types'; const styles = { @@ -31,8 +31,7 @@ const EnrollmentQuickActionsComponentPlain = ({ const { enrollmentId, programId, teiId, orgUnitId } = useLocationQuery(); const { anyStageWriteAccess } = useEnrollmentAccessContext(); - const hiddenProgramStageIds = useSelector(({ rulesEffectsHiddenProgramStage }: any) => - (enrollmentId ? rulesEffectsHiddenProgramStage?.[getEnrollmentScopeFormId(enrollmentId)] : undefined)); + const hiddenProgramStageIds = useSelector(selectEnrollmentHiddenProgramStageIds); const stagesWithEventCount = useMemo(() => stages.map((stage) => { const mutatedStage = { ...stage }; diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts index fe52e6aa91..d3497813f9 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts @@ -1,29 +1,4 @@ -import { useMemo } from 'react'; import { useSelector } from 'react-redux'; -import { getEnrollmentScopeFormId } from '../../../common/EnrollmentOverviewDomain'; +import { selectEnrollmentWidgetEffects } from '../../../common/EnrollmentOverviewDomain'; -export const useFilteredWidgetData = (enrollmentId?: string) => { - const formId = enrollmentId ? getEnrollmentScopeFormId(enrollmentId) : undefined; - - const generalErrors = useSelector(({ rulesEffectsGeneralErrors }: any) => - (formId ? rulesEffectsGeneralErrors?.[formId] : undefined)); - const generalWarnings = useSelector(({ rulesEffectsGeneralWarnings }: any) => - (formId ? rulesEffectsGeneralWarnings?.[formId] : undefined)); - const feedback = useSelector(({ rulesEffectsFeedback }: any) => - (formId ? rulesEffectsFeedback?.[formId] : undefined)); - const indicators = useSelector(({ rulesEffectsIndicators }: any) => - (formId ? rulesEffectsIndicators?.[formId] : undefined)); - - return useMemo(() => ({ - warnings: generalWarnings?.warning ?? [], - errors: generalErrors?.error ?? [], - feedbacks: [ - ...(feedback?.displayTexts ?? []), - ...(feedback?.displayKeyValuePairs ?? []), - ], - indicators: [ - ...(indicators?.displayTexts ?? []), - ...(indicators?.displayKeyValuePairs ?? []), - ], - }), [generalErrors, generalWarnings, feedback, indicators]); -}; +export const useFilteredWidgetData = () => useSelector(selectEnrollmentWidgetEffects); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx index 5dd46cfa44..bc2d60701f 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx @@ -8,7 +8,7 @@ import { Widget } from '../../../Widget'; import { useCommonEnrollmentDomainData, useEnrollmentScopeRuleEffects, - getEnrollmentScopeFormId, + selectEnrollmentHiddenProgramStageIds, } from '../../common/EnrollmentOverviewDomain'; import type { Props } from './ProgramStageSelector.types'; import { useProgramFromIndexedDB } from '../../../../utils/cachedDataHooks/useProgramFromIndexedDB'; @@ -41,8 +41,7 @@ export const ProgramStageSelector = ({ programId, orgUnitId, teiId, enrollmentId apiEnrollment: enrollment, apiAttributeValues: attributeValues, }); - const hiddenProgramStageIds = useSelector(({ rulesEffectsHiddenProgramStage }: any) => - (enrollmentId ? rulesEffectsHiddenProgramStage?.[getEnrollmentScopeFormId(enrollmentId)] : undefined)); + const hiddenProgramStageIds = useSelector(selectEnrollmentHiddenProgramStageIds); useEffect(() => { if (enrollmentsError || programError) { diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts index 84dcf5e26a..b4ead983f1 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts @@ -24,8 +24,12 @@ export const enrollmentSiteActionTypes = { COMMIT_ENROLLMENT_AND_EVENTS: 'Enrollment.CommitEnrollmentAndEvents', SET_EXTERNAL_ENROLLMENT_STATUS: 'Enrollment.SetExternalEnrollmentStatus', SET_TRACKED_ENTITY_INACTIVE_STATUS: 'Enrollment.SetTrackedEntityInactiveStatus', + SET_ENROLLMENT_RULE_EFFECTS: 'Enrollment.SetEnrollmentRuleEffects', }; +export const setEnrollmentRuleEffects = (rulesEffects: any) => + actionCreator(enrollmentSiteActionTypes.SET_ENROLLMENT_RULE_EFFECTS)({ rulesEffects }); + export const setCommonEnrollmentSiteData = ( enrollment: EnrollmentData, attributeValues: Array, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts index 5d2da97f71..a10dc0b566 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/index.ts @@ -20,7 +20,12 @@ export { rollbackEnrollmentEvents, } from './enrollment.actions'; export { useCommonEnrollmentDomainData } from './useCommonEnrollmentDomainData'; -export { useEnrollmentScopeRuleEffects, getEnrollmentScopeFormId } from './useRuleEffects'; +export { + useEnrollmentScopeRuleEffects, + selectEnrollmentHiddenAttributeIds, + selectEnrollmentHiddenProgramStageIds, + selectEnrollmentWidgetEffects, +} from './useRuleEffects'; export { EnrollmentAccessProvider, useEnrollmentAccessContext, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/enrollmentRuleEffects.selectors.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/enrollmentRuleEffects.selectors.ts new file mode 100644 index 0000000000..785469d7ed --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/enrollmentRuleEffects.selectors.ts @@ -0,0 +1,50 @@ +import { createSelector } from 'reselect'; +import { effectActions } from '@dhis2/rules-engine-javascript'; + +type HiddenFieldMeta = { content?: string; name?: string; hadValue?: boolean }; + +const getRuleEffects = (state: any) => state.enrollmentDomain?.ruleEffects; + +export const selectEnrollmentHiddenAttributeIds = createSelector( + getRuleEffects, + (effects) => { + const hide = effects?.[effectActions.HIDE_FIELD]; + if (!hide) return undefined; + return Object.keys(hide).reduce((acc: Record, key) => { + const [value] = hide[key]; + acc[key] = { content: value.content, name: value.name, hadValue: value.hadValue }; + return acc; + }, {}); + }, +); + +export const selectEnrollmentHiddenProgramStageIds = createSelector( + getRuleEffects, + (effects) => { + const hide = effects?.[effectActions.HIDE_PROGRAM_STAGE]; + if (!hide) return undefined; + return Object.keys(hide).reduce((acc: Record, key) => { + acc[key] = true; + return acc; + }, {}); + }, +); + +const mapGeneralMessages = (byId: any, pick: (effect: any) => string) => + (byId?.general ? byId.general.map(pick) : []); + +export const selectEnrollmentWidgetEffects = createSelector( + getRuleEffects, + effects => ({ + errors: mapGeneralMessages(effects?.[effectActions.SHOW_ERROR], (e: any) => e.error), + warnings: mapGeneralMessages(effects?.[effectActions.SHOW_WARNING], (w: any) => w.warning), + feedbacks: [ + ...(effects?.[effectActions.DISPLAY_TEXT]?.feedback?.map((e: any) => e.displayText) ?? []), + ...(effects?.[effectActions.DISPLAY_KEY_VALUE_PAIR]?.feedback?.map((e: any) => e.displayKeyValuePair) ?? []), + ], + indicators: [ + ...(effects?.[effectActions.DISPLAY_TEXT]?.indicators?.map((e: any) => e.displayText) ?? []), + ...(effects?.[effectActions.DISPLAY_KEY_VALUE_PAIR]?.indicators?.map((e: any) => e.displayKeyValuePair) ?? []), + ], + }), +); diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/getEnrollmentScopeFormId.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/getEnrollmentScopeFormId.ts deleted file mode 100644 index 0a2ccaca9e..0000000000 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/getEnrollmentScopeFormId.ts +++ /dev/null @@ -1 +0,0 @@ -export const getEnrollmentScopeFormId = (enrollmentId: string) => `enrollment-${enrollmentId}`; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts index 364cc9a404..d4781d6b9d 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/index.ts @@ -1,2 +1,6 @@ export { useEnrollmentScopeRuleEffects } from './useEnrollmentScopeRuleEffects'; -export { getEnrollmentScopeFormId } from './getEnrollmentScopeFormId'; +export { + selectEnrollmentHiddenAttributeIds, + selectEnrollmentHiddenProgramStageIds, + selectEnrollmentWidgetEffects, +} from './enrollmentRuleEffects.selectors'; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts index 9092626872..acab37df69 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts @@ -1,8 +1,8 @@ import { useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { getApplicableRuleEffectsForTrackerProgram, updateRulesEffects } from '../../../../../rules'; +import { getApplicableRuleEffectsForTrackerProgram } from '../../../../../rules'; import type { TrackerProgram } from '../../../../../metaData'; -import { getEnrollmentScopeFormId } from './getEnrollmentScopeFormId'; +import { setEnrollmentRuleEffects } from '../enrollment.actions'; import { useAttributeValuesForRules, useEnrollmentData, useEventsData } from './rulesExecutionData'; import type { AttributeValue, EnrollmentData } from '../useCommonEnrollmentDomainData'; @@ -24,16 +24,15 @@ export const useEnrollmentScopeRuleEffects = ({ force = false, }: Input) => { const dispatch = useDispatch(); - const formId = enrollmentId && getEnrollmentScopeFormId(enrollmentId); - const hasWarmStoreOnMount = useSelector(({ rulesEffectsHiddenFields }: any) => - (formId ? rulesEffectsHiddenFields?.[formId] !== undefined : false)); + const hasWarmStoreOnMount = useSelector(({ enrollmentDomain }: any) => + enrollmentDomain?.enrollmentId === enrollmentId && enrollmentDomain?.ruleEffects != null); const skipOnce = useRef(!force && hasWarmStoreOnMount); const attributeValues = useAttributeValuesForRules(program, apiAttributeValues); const enrollmentData = useEnrollmentData(apiEnrollment); const otherEvents = useEventsData(apiEnrollment, program); useEffect(() => { - if (formId && orgUnit && attributeValues && enrollmentData && otherEvents) { + if (enrollmentId && orgUnit && attributeValues && enrollmentData && otherEvents) { if (skipOnce.current) { skipOnce.current = false; return; @@ -45,7 +44,7 @@ export const useEnrollmentScopeRuleEffects = ({ attributeValues, enrollmentData, }); - dispatch(updateRulesEffects(effects, formId)); + dispatch(setEnrollmentRuleEffects(effects)); } - }, [dispatch, formId, attributeValues, enrollmentData, orgUnit, otherEvents, program]); + }, [dispatch, enrollmentId, attributeValues, enrollmentData, orgUnit, otherEvents, program]); }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 2cec7c407d..9181454a02 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -26,7 +26,7 @@ import { useDataEntryFormConfig, } from '../DataEntries/common/TEIAndEnrollment'; import { useEnrollmentAccessContext } from '../Pages/common/EnrollmentOverviewDomain/EnrollmentAccessContext'; -import { getEnrollmentScopeFormId } from '../Pages/common/EnrollmentOverviewDomain'; +import { selectEnrollmentHiddenAttributeIds } from '../Pages/common/EnrollmentOverviewDomain'; const styles: Readonly = { header: { @@ -87,9 +87,7 @@ const WidgetProfilePlain = ({ storedGeometry: trackedEntityInstance?.geometry, hasError: trackedEntityInstance?.hasError, })); - const enrollmentId = useSelector(({ enrollmentDomain }: any) => enrollmentDomain?.enrollmentId); - const hiddenAttributeIds = useSelector(({ rulesEffectsHiddenFields }: any) => - (enrollmentId ? rulesEffectsHiddenFields?.[getEnrollmentScopeFormId(enrollmentId)] : undefined)); + const hiddenAttributeIds = useSelector(selectEnrollmentHiddenAttributeIds); const { configIsFetched, dataEntryFormConfig } = useDataEntryFormConfig({ selectedScopeId: programId }); const { loading: trackedEntityInstancesLoading, diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.tsx b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.tsx index 915af5cf17..d859f048d9 100644 --- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.tsx +++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.tsx @@ -8,8 +8,7 @@ import { Widget } from '../../../Widget'; import { StageDetail } from './StageDetail/StageDetail.component'; import { StageCreateNewButton } from './StageCreateNewButton'; import { useEnrollmentAccessContext } from '../../../Pages/common/EnrollmentOverviewDomain/EnrollmentAccessContext'; -import { getEnrollmentScopeFormId } from '../../../Pages/common/EnrollmentOverviewDomain'; -import { useLocationQuery } from '../../../../utils/routing'; +import { selectEnrollmentHiddenProgramStageIds } from '../../../Pages/common/EnrollmentOverviewDomain'; const styles = { overview: { @@ -30,9 +29,7 @@ export const StagePlain = ({ }: Props & WithStyles) => { const [open, setOpenStatus] = useState(true); const { id, name, icon, description, dataElements, hideDueDate, repeatable, enableUserAssignment } = stage; - const { enrollmentId } = useLocationQuery(); - const hiddenProgramStageIds = useSelector(({ rulesEffectsHiddenProgramStage }: any) => - (enrollmentId ? rulesEffectsHiddenProgramStage?.[getEnrollmentScopeFormId(enrollmentId)] : undefined)); + const hiddenProgramStageIds = useSelector(selectEnrollmentHiddenProgramStageIds); const preventAddingNewEvents = Boolean(hiddenProgramStageIds?.[id]); const hideProgramStage = preventAddingNewEvents && events.length === 0; const { stageWriteAccessById } = useEnrollmentAccessContext(); diff --git a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts index bc95ac56ae..cc6b52b12f 100644 --- a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts +++ b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts @@ -27,6 +27,7 @@ const { UPDATE_ENROLLMENT_EVENT_STATUS, DELETE_ENROLLMENT_EVENT_RELATIONSHIP, SET_TRACKED_ENTITY_INACTIVE_STATUS, + SET_ENROLLMENT_RULE_EFFECTS, } = enrollmentSiteActionTypes; const setAssignee = (state, action) => { @@ -51,6 +52,11 @@ export const enrollmentDomainDesc = createReducerDescription( enrollmentId: enrollment?.enrollment, inactive, programOwnerId, + ruleEffects: null, + }), + [SET_ENROLLMENT_RULE_EFFECTS]: (state, { payload: { rulesEffects } }) => ({ + ...state, + ruleEffects: rulesEffects ?? null, }), [SET_TRACKED_ENTITY_INACTIVE_STATUS]: (state, { payload: { inactive } }) => ({ ...state, From 9f4da4295bda1067004a341249d9db8bfb1f887d Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 9 Sep 2026 09:19:21 +0000 Subject: [PATCH 32/40] fix: (review) add ruleEffectsPending to stop hidden fields from flashing --- i18n/en.pot | 4 ++-- .../LayoutComponentConfig/LayoutComponentConfig.ts | 2 ++ .../WidgetProfile/WidgetProfile.component.tsx | 10 ++++++++-- .../components/WidgetProfile/widgetProfile.types.ts | 1 + 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 63c451adcc..4b3816dd1a 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-09T09:12:44.956Z\n" -"PO-Revision-Date: 2026-09-09T09:12:44.956Z\n" +"POT-Creation-Date: 2026-09-09T09:19:22.559Z\n" +"PO-Revision-Date: 2026-09-09T09:19:22.559Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts index b5ed2b9076..7c678c59a4 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts @@ -141,6 +141,7 @@ export const ProfileWidget: WidgetConfig = { teiId, program, programOwnerId, + enrollmentId, onUpdateTeiAttributeValues, onDeleteTrackedEntitySuccess, onStatusToggleSuccess, @@ -148,6 +149,7 @@ export const ProfileWidget: WidgetConfig = { teiId, programId: program.id, programOwnerId, + enrollmentId, onUpdateTeiAttributeValues, onDeleteSuccess: onDeleteTrackedEntitySuccess, onStatusToggleSuccess, diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 9181454a02..36d27d8cf2 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -60,7 +60,8 @@ const computeLoadingState = ( trackedEntityInstancesLoading: boolean, userRolesLoading: boolean, configIsFetched: boolean, -) => programsLoading || trackedEntityInstancesLoading || userRolesLoading || !configIsFetched; + ruleEffectsPending: boolean, +) => programsLoading || trackedEntityInstancesLoading || userRolesLoading || !configIsFetched || ruleEffectsPending; const computeError = ( programsError: any, @@ -71,6 +72,7 @@ const computeError = ( const WidgetProfilePlain = ({ teiId, programId, + enrollmentId, readOnlyMode = false, programOwnerId = '', onUpdateTeiAttributeValues, @@ -88,6 +90,8 @@ const WidgetProfilePlain = ({ hasError: trackedEntityInstance?.hasError, })); const hiddenAttributeIds = useSelector(selectEnrollmentHiddenAttributeIds); + const ruleEffectsPending = useSelector(({ enrollmentDomain }: any) => + Boolean(enrollmentId) && (enrollmentDomain?.enrollmentId !== enrollmentId || enrollmentDomain?.ruleEffects == null)); const { configIsFetched, dataEntryFormConfig } = useDataEntryFormConfig({ selectedScopeId: programId }); const { loading: trackedEntityInstancesLoading, @@ -123,7 +127,9 @@ const WidgetProfilePlain = ({ return null; }, [isEditable, readOnlyMode, hasNoAttributes]); - const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched); + const loading = computeLoadingState( + programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched, ruleEffectsPending, + ); const error = computeError(programsError, trackedEntityInstancesError, userRolesError); const clientAttributesWithSubvalues = useClientAttributesWithSubvalues( teiId, diff --git a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts index 903e52096c..8e818bec4b 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts @@ -2,6 +2,7 @@ export type Props = { teiId: string; programId: string; programOwnerId: string; + enrollmentId?: string; readOnlyMode?: boolean; onUpdateTeiAttributeValues?: (attributes: Array<{ [key: string]: string }>, teiDisplayName: string) => void; onDeleteSuccess?: () => void; From ac0d1504dc01a32cf8643ad746c01d5d4946406b Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 9 Sep 2026 09:22:05 +0000 Subject: [PATCH 33/40] fix: (review) remove unused force parameter --- i18n/en.pot | 4 +-- .../EnrollmentPageDefault.container.tsx | 1 - .../useEnrollmentScopeRuleEffects.ts | 34 +++++++------------ 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 4b3816dd1a..1e2b587afd 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-09T09:19:22.559Z\n" -"PO-Revision-Date: 2026-09-09T09:19:22.559Z\n" +"POT-Creation-Date: 2026-09-09T09:22:06.807Z\n" +"PO-Revision-Date: 2026-09-09T09:22:06.807Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index 224c6ba9d8..bee39c1d6a 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -100,7 +100,6 @@ export const EnrollmentPageDefault = () => { program, apiEnrollment: enrollment, apiAttributeValues: attributeValues, - force: true, }); const outputEffects = useFilteredWidgetData(); diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts index acab37df69..c54bcab281 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { getApplicableRuleEffectsForTrackerProgram } from '../../../../../rules'; import type { TrackerProgram } from '../../../../../metaData'; @@ -12,7 +12,6 @@ type Input = { program: TrackerProgram; apiEnrollment?: EnrollmentData; apiAttributeValues?: Array; - force?: boolean; }; export const useEnrollmentScopeRuleEffects = ({ @@ -21,30 +20,23 @@ export const useEnrollmentScopeRuleEffects = ({ program, apiEnrollment, apiAttributeValues, - force = false, }: Input) => { const dispatch = useDispatch(); - const hasWarmStoreOnMount = useSelector(({ enrollmentDomain }: any) => - enrollmentDomain?.enrollmentId === enrollmentId && enrollmentDomain?.ruleEffects != null); - const skipOnce = useRef(!force && hasWarmStoreOnMount); + const domainAligned = useSelector(({ enrollmentDomain }: any) => + Boolean(enrollmentId) && enrollmentDomain?.enrollmentId === enrollmentId); const attributeValues = useAttributeValuesForRules(program, apiAttributeValues); const enrollmentData = useEnrollmentData(apiEnrollment); const otherEvents = useEventsData(apiEnrollment, program); useEffect(() => { - if (enrollmentId && orgUnit && attributeValues && enrollmentData && otherEvents) { - if (skipOnce.current) { - skipOnce.current = false; - return; - } - const effects = getApplicableRuleEffectsForTrackerProgram({ - program, - orgUnit, - otherEvents, - attributeValues, - enrollmentData, - }); - dispatch(setEnrollmentRuleEffects(effects)); - } - }, [dispatch, enrollmentId, attributeValues, enrollmentData, orgUnit, otherEvents, program]); + if (!domainAligned || !orgUnit || !attributeValues || !enrollmentData || !otherEvents) return; + const effects = getApplicableRuleEffectsForTrackerProgram({ + program, + orgUnit, + otherEvents, + attributeValues, + enrollmentData, + }); + dispatch(setEnrollmentRuleEffects(effects)); + }, [dispatch, domainAligned, attributeValues, enrollmentData, orgUnit, otherEvents, program]); }; From ab1bb0465e5dee3ff9debcacfb1b063c5391f47d Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:50:29 +0000 Subject: [PATCH 34/40] fix: replace useFilteredWidgetData with direct selector for enrollment widget effects --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault/EnrollmentPageDefault.container.tsx | 4 ++-- .../EnrollmentPageDefault/hooks/useFilteredWidgetData.ts | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts diff --git a/i18n/en.pot b/i18n/en.pot index 1e2b587afd..4058b8889d 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-09T09:22:06.807Z\n" -"PO-Revision-Date: 2026-09-09T09:22:06.807Z\n" +"POT-Creation-Date: 2026-09-09T13:50:30.544Z\n" +"PO-Revision-Date: 2026-09-09T13:50:30.545Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index bee39c1d6a..2d2bc805b2 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -17,6 +17,7 @@ import { updateIncidentDate, useCommonEnrollmentDomainData, useEnrollmentScopeRuleEffects, + selectEnrollmentWidgetEffects, } from '../../common/EnrollmentOverviewDomain'; import { deleteEnrollment, @@ -28,7 +29,6 @@ import { useCoreOrgUnit } from '../../../../metadataRetrieval/coreOrgUnit'; import { DataStoreKeyByPage, EnrollmentPageLayout } from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout'; import { useProgramMetadata, useProgramStages } from './hooks'; import { useNavigate, buildUrlQueryString, useLocationQuery } from '../../../../utils/routing'; -import { useFilteredWidgetData } from './hooks/useFilteredWidgetData'; import { useLinkedRecordClick } from '../../common/TEIRelationshipsWidget'; import { useEnrollmentPageLayout, @@ -102,7 +102,7 @@ export const EnrollmentPageDefault = () => { apiAttributeValues: attributeValues, }); - const outputEffects = useFilteredWidgetData(); + const outputEffects = useSelector(selectEnrollmentWidgetEffects); const hideWidgets = useHideWidgetByRuleLocations(program.programRules); const onDeleteTrackedEntitySuccess = useCallback(() => { diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts deleted file mode 100644 index d3497813f9..0000000000 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useFilteredWidgetData.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { useSelector } from 'react-redux'; -import { selectEnrollmentWidgetEffects } from '../../../common/EnrollmentOverviewDomain'; - -export const useFilteredWidgetData = () => useSelector(selectEnrollmentWidgetEffects); From 64e3a49bff1e7478406571ffd000a2336d15bc4a Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:00:28 +0000 Subject: [PATCH 35/40] fix: add updateEnrollmentProgramOwner action and integrate with ownership transfer --- i18n/en.pot | 4 ++-- .../common/EnrollmentOverviewDomain/enrollment.actions.ts | 4 ++++ .../Actions/Transfer/hooks/useUpdateOwnership.ts | 6 +++++- .../descriptions/enrollmentDomain.reducerDescription.ts | 6 ++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 4058b8889d..575ec99780 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-09T13:50:30.544Z\n" -"PO-Revision-Date: 2026-09-09T13:50:30.545Z\n" +"POT-Creation-Date: 2026-09-09T14:00:29.593Z\n" +"PO-Revision-Date: 2026-09-09T14:00:29.593Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts index b4ead983f1..08b28153e4 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts @@ -25,11 +25,15 @@ export const enrollmentSiteActionTypes = { SET_EXTERNAL_ENROLLMENT_STATUS: 'Enrollment.SetExternalEnrollmentStatus', SET_TRACKED_ENTITY_INACTIVE_STATUS: 'Enrollment.SetTrackedEntityInactiveStatus', SET_ENROLLMENT_RULE_EFFECTS: 'Enrollment.SetEnrollmentRuleEffects', + UPDATE_ENROLLMENT_PROGRAM_OWNER: 'Enrollment.UpdateProgramOwner', }; export const setEnrollmentRuleEffects = (rulesEffects: any) => actionCreator(enrollmentSiteActionTypes.SET_ENROLLMENT_RULE_EFFECTS)({ rulesEffects }); +export const updateEnrollmentProgramOwner = (programOwnerId: string) => + actionCreator(enrollmentSiteActionTypes.UPDATE_ENROLLMENT_PROGRAM_OWNER)({ programOwnerId }); + export const setCommonEnrollmentSiteData = ( enrollment: EnrollmentData, attributeValues: Array, diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Transfer/hooks/useUpdateOwnership.ts b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Transfer/hooks/useUpdateOwnership.ts index d7620de186..aa6f1256ce 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Transfer/hooks/useUpdateOwnership.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Transfer/hooks/useUpdateOwnership.ts @@ -2,10 +2,12 @@ import { useAlert, useDataEngine } from '@dhis2/app-runtime'; import log from 'loglevel'; import i18n from '@dhis2/d2-i18n'; import { useMutation } from '@tanstack/react-query'; +import { useDispatch } from 'react-redux'; import { errorCreator, FEATURES, useFeature } from 'capture-core-utils'; import type { QueryRefetchFunction } from 'capture-core-utils/types/app-runtime'; import { ProgramAccessLevels } from '../../../TransferModal/hooks/useProgramAccessLevel'; import { OrgUnitScopes } from '../../../TransferModal/hooks/useTransferValidation'; +import { updateEnrollmentProgramOwner } from '../../../../Pages/common/EnrollmentOverviewDomain/enrollment.actions'; export type UpdateEnrollmentOwnership = (params: { orgUnitId: string; @@ -45,6 +47,7 @@ export const useUpdateOwnership = ({ onAccessLostFromTransfer, }: Props): ReturnTypes => { const dataEngine = useDataEngine(); + const dispatch = useDispatch(); const { show: showErrorAlert } = useAlert( i18n.t('An error occurred while transferring ownership'), { critical: true }, @@ -63,7 +66,8 @@ export const useUpdateOwnership = ({ }, }), { - onSuccess: (_, { programAccessLevel, orgUnitScopes }: any) => { + onSuccess: (_, { orgUnitId, programAccessLevel, orgUnitScopes }: any) => { + dispatch(updateEnrollmentProgramOwner(orgUnitId)); // If the user is transferring ownership to a capture scope, we stay on the same page if (orgUnitScopes.destination === OrgUnitScopes.CAPTURE) { refetchTEI(); diff --git a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts index cc6b52b12f..d808f3a344 100644 --- a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts +++ b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts @@ -28,6 +28,7 @@ const { DELETE_ENROLLMENT_EVENT_RELATIONSHIP, SET_TRACKED_ENTITY_INACTIVE_STATUS, SET_ENROLLMENT_RULE_EFFECTS, + UPDATE_ENROLLMENT_PROGRAM_OWNER, } = enrollmentSiteActionTypes; const setAssignee = (state, action) => { @@ -58,6 +59,11 @@ export const enrollmentDomainDesc = createReducerDescription( ...state, ruleEffects: rulesEffects ?? null, }), + [UPDATE_ENROLLMENT_PROGRAM_OWNER]: (state, { payload: { programOwnerId } }) => ({ + ...state, + programOwnerId, + ruleEffects: null, + }), [SET_TRACKED_ENTITY_INACTIVE_STATUS]: (state, { payload: { inactive } }) => ({ ...state, inactive, From 51ad33600c94a61fa15f0619ad73755871b28dc7 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:09:13 +0000 Subject: [PATCH 36/40] fix: remove unused getUseNameForOptionSet function and its references --- i18n/en.pot | 4 ++-- .../WidgetProfile/DataEntry/ProgramRules/rulesContainer.ts | 2 -- .../components/WidgetProfile/DataEntry/helpers/convertors.ts | 3 --- .../components/WidgetProfile/DataEntry/helpers/index.ts | 1 - 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 575ec99780..b93620f027 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-09T14:00:29.593Z\n" -"PO-Revision-Date: 2026-09-09T14:00:29.593Z\n" +"POT-Creation-Date: 2026-09-09T14:09:14.653Z\n" +"PO-Revision-Date: 2026-09-09T14:09:14.654Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.ts index 6c7e0b086b..1ce26826e3 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.ts @@ -5,7 +5,6 @@ import { getProgramId, getProgramRuleActions, getProgramStageId, - getUseNameForOptionSet, } from '../helpers'; const addProgramVariables = (program: any, programRuleVariables: any[]) => { @@ -14,7 +13,6 @@ const addProgramVariables = (program: any, programRuleVariables: any[]) => { programId: getProgramId(programRulesVariable), dataElementId: getDataElementId(programRulesVariable), trackedEntityAttributeId: getTrackedEntityAttributeId(programRulesVariable), - useNameForOptionSet: getUseNameForOptionSet(programRulesVariable), })); }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/convertors.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/convertors.ts index a93a3159f3..4626c508e7 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/convertors.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/convertors.ts @@ -14,9 +14,6 @@ export const getOptionSetId = (valueApi: any) => valueApi?.optionSet?.id; export const getProgramId = (valueApi: any) => valueApi?.program?.id; -export const getUseNameForOptionSet = (valueApi: any) => - (valueApi?.useCodeForOptionSet != null ? !valueApi.useCodeForOptionSet : false); - export const getTrackedEntityTypeId = (valueApi: any) => valueApi?.trackedEntityType?.id; export const getProgramTrackedEntityAttributes = (valueApi: any) => diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/index.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/index.ts index dbad3e1d0f..faa955bdb1 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/index.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/helpers/index.ts @@ -7,7 +7,6 @@ export { getOptionId, getProgramId, getProgramRuleActions, - getUseNameForOptionSet, getOptionSetId, getTrackedEntityTypeId, getProgramTrackedEntityAttributes, From e6a3f139ccee9d1c030b164a080e6f8622d4ee22 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:25:08 +0000 Subject: [PATCH 37/40] fix: rename programOwnerId to ownerOrgUnitId across components and actions --- i18n/en.pot | 4 ++-- .../EnrollmentPageDefault.container.tsx | 12 ++++++------ .../EnrollmentAddEventPage.container.tsx | 4 ++-- .../EnrollmentAddEventPageDefault.container.tsx | 8 ++++---- .../EnrollmentAddEventPageDefault.types.ts | 4 ++-- .../ProgramStageSelector.container.tsx | 4 ++-- .../EnrollmentEditEventPage.component.tsx | 4 ++-- .../EnrollmentEditEventPage.container.tsx | 12 ++++++------ .../EnrollmentEditEventPage.types.ts | 4 ++-- .../LayoutComponentConfig.ts | 4 ++-- .../enrollment.actions.ts | 10 +++++----- .../useCommonEnrollmentDomainData.ts | 12 ++++++------ .../useCommonEnrollmentDomainData.types.ts | 2 +- .../Actions/Transfer/hooks/useUpdateOwnership.ts | 4 ++-- .../DataEntry/DataEntry.component.tsx | 4 ++-- .../DataEntry/DataEntry.container.tsx | 16 ++++++++-------- .../DataEntry/DataEntryReadOnly.component.tsx | 6 +++--- .../WidgetProfile/DataEntry/dataEntry.actions.ts | 6 +++--- .../WidgetProfile/DataEntry/dataEntry.epics.ts | 4 ++-- .../WidgetProfile/DataEntry/dataEntry.types.ts | 6 +++--- .../DataEntry/hooks/useLifecycle.ts | 6 +++--- .../WidgetProfile/WidgetProfile.component.tsx | 4 ++-- .../WidgetProfile/widgetProfile.types.ts | 2 +- .../enrollmentDomain.reducerDescription.ts | 10 +++++----- 24 files changed, 76 insertions(+), 76 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index b93620f027..afe3ed875d 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-09T14:09:14.653Z\n" -"PO-Revision-Date: 2026-09-09T14:09:14.654Z\n" +"POT-Creation-Date: 2026-09-09T14:25:09.433Z\n" +"PO-Revision-Date: 2026-09-09T14:25:09.434Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index 2d2bc805b2..93dfe0f219 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -68,10 +68,10 @@ export const EnrollmentPageDefault = () => { enrollment, attributeValues, readOnly: trackedEntityInactive, - programOwnerId, + ownerOrgUnitId, } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const program = useTrackerProgram(programId); - const { orgUnit: programOwnerOrgUnit, error: programOwnerOrgUnitError } = useCoreOrgUnit(programOwnerId); + const { orgUnit: ownerOrgUnit, error: ownerOrgUnitError } = useCoreOrgUnit(ownerOrgUnitId); const onStatusToggleSuccess = useCallback(() => { dispatch(setTrackedEntityInactiveStatus(!trackedEntityInactive)); @@ -88,15 +88,15 @@ export const EnrollmentPageDefault = () => { https://dhis2.atlassian.net/browse/DHIS2-17574 */ - if (programMetaDataError || enrollmentsError || programOwnerOrgUnitError) { + if (programMetaDataError || enrollmentsError || ownerOrgUnitError) { log.error(errorCreator('Enrollment page could not be loaded')( - { programMetaDataError, enrollmentsError, programOwnerOrgUnitError }, + { programMetaDataError, enrollmentsError, ownerOrgUnitError }, )); } useEnrollmentScopeRuleEffects({ enrollmentId, - orgUnit: programOwnerOrgUnit, + orgUnit: ownerOrgUnit, program, apiEnrollment: enrollment, apiAttributeValues: attributeValues, @@ -203,7 +203,7 @@ export const EnrollmentPageDefault = () => { availableWidgets={WidgetsForEnrollmentPageDefault} teiId={teiId} orgUnitId={orgUnitId} - programOwnerId={programOwnerId} + ownerOrgUnitId={ownerOrgUnitId} program={program} stages={stages} events={enrollment?.events} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx index 6267d304ca..ac74b44486 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.tsx @@ -66,7 +66,7 @@ const EnrollmentAddEventPagePlain = ({ classes }: WithStyles) => attributeValues, error: commonDataError, readOnly: trackedEntityInactive, - programOwnerId, + ownerOrgUnitId, } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const { pageLayout, isLoading } = useEnrollmentPageLayout({ selectedScopeId: validIds[IdTypes.PROGRAM_ID]?.id ?? null, @@ -100,7 +100,7 @@ const EnrollmentAddEventPagePlain = ({ classes }: WithStyles) => attributeValues={attributeValues} commonDataError={Boolean(commonDataError)} trackedEntityInactive={Boolean(trackedEntityInactive)} - programOwnerId={programOwnerId} + ownerOrgUnitId={ownerOrgUnitId} /> ); } diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx index 0c24805eb1..2e06693f0d 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.tsx @@ -37,7 +37,7 @@ export const EnrollmentAddEventPageDefault = ({ attributeValues, commonDataError, trackedEntityInactive, - programOwnerId, + ownerOrgUnitId, }: ContainerProps) => { const { programId, stageId, orgUnitId, teiId, enrollmentId } = useLocationQuery(); @@ -116,11 +116,11 @@ export const EnrollmentAddEventPageDefault = ({ const dataEntryHasChanges = useSelector((state: ReduxState) => getDataEntryHasChanges(state, widgetReducerName)); const { program } = useProgramInfo(programId); const trackerProgram = useTrackerProgram(programId); - const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); + const { orgUnit: ownerOrgUnit } = useCoreOrgUnit(ownerOrgUnitId); useEnrollmentScopeRuleEffects({ enrollmentId, - orgUnit: programOwnerOrgUnit, + orgUnit: ownerOrgUnit, program: trackerProgram, apiEnrollment: enrollment ?? undefined, apiAttributeValues: attributeValues ?? undefined, @@ -219,7 +219,7 @@ export const EnrollmentAddEventPageDefault = ({ onUpdateEnrollmentStatusError={onUpdateEnrollmentStatusError} onAccessLostFromTransfer={onAccessLostFromTransfer} trackedEntityInactive={trackedEntityInactive} - programOwnerId={programOwnerId} + ownerOrgUnitId={ownerOrgUnitId} /> ); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts index 39c3d42aa9..c1e0177a57 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.ts @@ -10,7 +10,7 @@ import { Program } from '../../../../metaData'; export type Props = { program: Program | null | undefined; - programOwnerId?: string; + ownerOrgUnitId?: string; stageId: string; orgUnitId: string; teiId: string; @@ -53,5 +53,5 @@ export type ContainerProps = { attributeValues: Array | null | undefined; commonDataError: boolean; trackedEntityInactive: boolean; - programOwnerId?: string; + ownerOrgUnitId?: string; }; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx index bc2d60701f..eae268bc1f 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/ProgramStageSelector/ProgramStageSelector.container.tsx @@ -20,7 +20,7 @@ import { useTrackerProgram } from '../../../../hooks/useTrackerProgram'; export const ProgramStageSelector = ({ programId, orgUnitId, teiId, enrollmentId }: Props) => { const { navigate } = useNavigate(); const { tab } = useLocationQuery(); - const { error: enrollmentsError, enrollment, attributeValues, programOwnerId } = useCommonEnrollmentDomainData( + const { error: enrollmentsError, enrollment, attributeValues, ownerOrgUnitId } = useCommonEnrollmentDomainData( teiId, enrollmentId, programId, @@ -31,7 +31,7 @@ export const ProgramStageSelector = ({ programId, orgUnitId, teiId, enrollmentId isError: programError, } = useProgramFromIndexedDB(programId); - const { orgUnit } = useCoreOrgUnit(programOwnerId); + const { orgUnit } = useCoreOrgUnit(ownerOrgUnitId); const programRules = useTrackerProgram(programId); useEnrollmentScopeRuleEffects({ diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx index 6f80a936e8..7dbf6e1daa 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.tsx @@ -27,7 +27,7 @@ export const EnrollmentEditEventPageComponent = ({ teiDisplayName, widgetEffects, hideWidgets, - programOwnerId, + ownerOrgUnitId, onDelete, onAddNew, onLinkedRecordClick, @@ -119,7 +119,7 @@ export const EnrollmentEditEventPageComponent = ({ pageStatus={pageStatus} widgetEffects={widgetEffects} hideWidgets={hideWidgets} - programOwnerId={programOwnerId} + ownerOrgUnitId={ownerOrgUnitId} onDelete={onDelete} onAddNew={onAddNew} eventAccess={eventAccess} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx index 25608ca73b..510613469f 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.tsx @@ -99,7 +99,7 @@ export const EnrollmentEditEventPage = () => { const { loading, event } = useEvent(eventId ?? ''); const { program: programId, programStage: stageId, trackedEntity: teiId, enrollment: enrollmentId } = event; const { orgUnitId, eventId: urlEventId, initMode } = useLocationQuery(); - const { enrollment: enrollmentSite, attributeValues, readOnly: trackedEntityInactive, programOwnerId } = + const { enrollment: enrollmentSite, attributeValues, readOnly: trackedEntityInactive, ownerOrgUnitId } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); const storedEvent = enrollmentSite?.events?.find((item: Record) => item.event === eventId); @@ -122,7 +122,7 @@ export const EnrollmentEditEventPage = () => { enrollmentSite={enrollmentSite} attributeValues={attributeValues} trackedEntityInactive={trackedEntityInactive} - programOwnerId={programOwnerId} + ownerOrgUnitId={ownerOrgUnitId} event={storedEvent} /> ) : ; @@ -138,7 +138,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ enrollmentSite, attributeValues, trackedEntityInactive, - programOwnerId, + ownerOrgUnitId, event, }: Props) => { const { navigate } = useNavigate(); @@ -162,10 +162,10 @@ const EnrollmentEditEventPageWithContextPlain = ({ const hideWidgets = useHideWidgetByRuleLocations( program.programRules.concat(programStage?.programRules as ProgramRule[]), ); - const { orgUnit: programOwnerOrgUnit } = useCoreOrgUnit(programOwnerId); + const { orgUnit: ownerOrgUnit } = useCoreOrgUnit(ownerOrgUnitId); useEnrollmentScopeRuleEffects({ enrollmentId, - orgUnit: programOwnerOrgUnit, + orgUnit: ownerOrgUnit, program, apiEnrollment: enrollmentSite, apiAttributeValues: attributeValues ?? undefined, @@ -380,7 +380,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ onUpdateEnrollmentEventsSuccess={onUpdateEnrollmentEventsSuccess} onUpdateEnrollmentEventsError={onUpdateEnrollmentEventsError} userInteractionInProgress={userInteractionInProgress} - programOwnerId={programOwnerId} + ownerOrgUnitId={ownerOrgUnitId} /> ); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts index 7cbefde56a..2613fbf1e6 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.ts @@ -13,7 +13,7 @@ export type PlainProps = { programStage?: ProgramStage | null; widgetEffects: WidgetEffects; hideWidgets: HideWidgets; - programOwnerId?: string; + ownerOrgUnitId?: string; teiId: string; enrollmentId: string; eventId: string; @@ -74,6 +74,6 @@ export type Props = { enrollmentSite: any; attributeValues?: Array; trackedEntityInactive?: boolean; - programOwnerId?: string; + ownerOrgUnitId?: string; initMode?: string; }; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts index 7c678c59a4..61d8a96469 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.ts @@ -140,7 +140,7 @@ export const ProfileWidget: WidgetConfig = { getProps: ({ teiId, program, - programOwnerId, + ownerOrgUnitId, enrollmentId, onUpdateTeiAttributeValues, onDeleteTrackedEntitySuccess, @@ -148,7 +148,7 @@ export const ProfileWidget: WidgetConfig = { }: any): WidgetProfileProps => ({ teiId, programId: program.id, - programOwnerId, + ownerOrgUnitId, enrollmentId, onUpdateTeiAttributeValues, onDeleteSuccess: onDeleteTrackedEntitySuccess, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts index 08b28153e4..ec23278c61 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/enrollment.actions.ts @@ -25,25 +25,25 @@ export const enrollmentSiteActionTypes = { SET_EXTERNAL_ENROLLMENT_STATUS: 'Enrollment.SetExternalEnrollmentStatus', SET_TRACKED_ENTITY_INACTIVE_STATUS: 'Enrollment.SetTrackedEntityInactiveStatus', SET_ENROLLMENT_RULE_EFFECTS: 'Enrollment.SetEnrollmentRuleEffects', - UPDATE_ENROLLMENT_PROGRAM_OWNER: 'Enrollment.UpdateProgramOwner', + UPDATE_ENROLLMENT_OWNER_ORG_UNIT: 'Enrollment.UpdateOwnerOrgUnit', }; export const setEnrollmentRuleEffects = (rulesEffects: any) => actionCreator(enrollmentSiteActionTypes.SET_ENROLLMENT_RULE_EFFECTS)({ rulesEffects }); -export const updateEnrollmentProgramOwner = (programOwnerId: string) => - actionCreator(enrollmentSiteActionTypes.UPDATE_ENROLLMENT_PROGRAM_OWNER)({ programOwnerId }); +export const updateEnrollmentOwnerOrgUnit = (ownerOrgUnitId: string) => + actionCreator(enrollmentSiteActionTypes.UPDATE_ENROLLMENT_OWNER_ORG_UNIT)({ ownerOrgUnitId }); export const setCommonEnrollmentSiteData = ( enrollment: EnrollmentData, attributeValues: Array, inactive?: boolean, - programOwnerId?: string, + ownerOrgUnitId?: string, ) => actionCreator(enrollmentSiteActionTypes.COMMON_ENROLLMENT_SITE_DATA_SET)({ enrollment, attributeValues, inactive, - programOwnerId, + ownerOrgUnitId, }); export const setTrackedEntityInactiveStatus = (inactive: boolean) => diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts index efc2a09944..f295553aa6 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.ts @@ -12,7 +12,7 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin enrollment: storedEnrollment, attributeValues: storedAttributeValues, inactive: storedInactive, - programOwnerId: storedProgramOwnerId, + ownerOrgUnitId: storedProgramOwnerId, } = useSelector(({ enrollmentDomain }: any) => enrollmentDomain); const { data, error } = useApiDataQuery( @@ -38,7 +38,7 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin ?.find((enrollment: any) => enrollment.enrollment === enrollmentId), attributeValues: data?.attributes, inactive: Boolean(data?.inactive), - programOwnerId: data?.programOwners?.find((p: any) => p.program === programId)?.orgUnit, + ownerOrgUnitId: data?.programOwners?.find((p: any) => p.program === programId)?.orgUnit, }; useEffect(() => { @@ -48,7 +48,7 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin fetchedEnrollmentData.attributeValues .map(({ attribute, value }: any) => ({ id: attribute, value })), fetchedEnrollmentData.inactive, - fetchedEnrollmentData.programOwnerId, + fetchedEnrollmentData.ownerOrgUnitId, )); } }, [ @@ -59,15 +59,15 @@ export const useCommonEnrollmentDomainData = (teiId: string, enrollmentId: strin fetchedEnrollmentData.enrollment, fetchedEnrollmentData.attributeValues, fetchedEnrollmentData.inactive, - fetchedEnrollmentData.programOwnerId, + fetchedEnrollmentData.ownerOrgUnitId, ]); const inEffectData = enrollmentId === storedEnrollmentId ? { enrollment: storedEnrollment, attributeValues: storedAttributeValues, readOnly: Boolean(storedInactive), - programOwnerId: storedProgramOwnerId, - } : { enrollment: undefined, attributeValues: undefined, readOnly: false, programOwnerId: undefined }; + ownerOrgUnitId: storedProgramOwnerId, + } : { enrollment: undefined, attributeValues: undefined, readOnly: false, ownerOrgUnitId: undefined }; return { error, diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.ts index d4c79e7a55..23dc4c6f3a 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.ts @@ -36,5 +36,5 @@ export type Output = { enrollment?: EnrollmentData; attributeValues?: Array; readOnly?: boolean; - programOwnerId?: string; + ownerOrgUnitId?: string; }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Transfer/hooks/useUpdateOwnership.ts b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Transfer/hooks/useUpdateOwnership.ts index aa6f1256ce..b92131b612 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Transfer/hooks/useUpdateOwnership.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Transfer/hooks/useUpdateOwnership.ts @@ -7,7 +7,7 @@ import { errorCreator, FEATURES, useFeature } from 'capture-core-utils'; import type { QueryRefetchFunction } from 'capture-core-utils/types/app-runtime'; import { ProgramAccessLevels } from '../../../TransferModal/hooks/useProgramAccessLevel'; import { OrgUnitScopes } from '../../../TransferModal/hooks/useTransferValidation'; -import { updateEnrollmentProgramOwner } from '../../../../Pages/common/EnrollmentOverviewDomain/enrollment.actions'; +import { updateEnrollmentOwnerOrgUnit } from '../../../../Pages/common/EnrollmentOverviewDomain/enrollment.actions'; export type UpdateEnrollmentOwnership = (params: { orgUnitId: string; @@ -67,7 +67,7 @@ export const useUpdateOwnership = ({ }), { onSuccess: (_, { orgUnitId, programAccessLevel, orgUnitScopes }: any) => { - dispatch(updateEnrollmentProgramOwner(orgUnitId)); + dispatch(updateEnrollmentOwnerOrgUnit(orgUnitId)); // If the user is transferring ownership to a capture scope, we stay on the same page if (orgUnitScopes.destination === OrgUnitScopes.CAPTURE) { refetchTEI(); diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.tsx index 3be0af1bea..ed7cbbc809 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.tsx @@ -20,7 +20,7 @@ export const DataEntryComponent = ({ onGetValidationContext, errorsMessages, warningsMessages, - programOwnerId, + ownerOrgUnitId, pluginContext, accessReadOnly, }: PlainProps) => ( @@ -60,7 +60,7 @@ export const DataEntryComponent = ({ onUpdateFormField={onUpdateFormField} onUpdateFormFieldAsync={onUpdateFormFieldAsync} onGetValidationContext={onGetValidationContext} - orgUnitId={programOwnerId} + orgUnitId={ownerOrgUnitId} pluginContext={pluginContext} fieldOptions={{ autoSelectSingleOrgUnit: false }} /> diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.tsx b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.tsx index 2d0d3486e9..4f2b674d7d 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.container.tsx @@ -19,7 +19,7 @@ const convertFn = pipe(convertServerToClient, convertClientToView); export const DataEntry = ({ programAPI, - programOwnerId, + ownerOrgUnitId, onCancel, onDisable, onEnable, @@ -46,16 +46,16 @@ export const DataEntry = ({ const onGetValidationContext = useCallback( () => ({ programId: programAPI.id, - orgUnitId: programOwnerId, + orgUnitId: ownerOrgUnitId, trackedEntityInstanceId, trackedEntityTypeId: programAPI.trackedEntityType.id, }), - [programAPI, programOwnerId, trackedEntityInstanceId], + [programAPI, ownerOrgUnitId, trackedEntityInstanceId], ); const context = useLifecycle({ programAPI, - programOwnerId, + ownerOrgUnitId, clientAttributesWithSubvalues, userRoles, dataEntryId, @@ -123,7 +123,7 @@ export const DataEntry = ({ updateTeiRequest({ itemId, dataEntryId, - programOwnerId, + ownerOrgUnitId, trackedEntityInstanceId, trackedEntityTypeId: programAPI.trackedEntityType.id, onSaveExternal, @@ -137,7 +137,7 @@ export const DataEntry = ({ dispatch, itemId, dataEntryId, - programOwnerId, + ownerOrgUnitId, trackedEntityInstanceId, programAPI, formValidated, @@ -163,7 +163,7 @@ export const DataEntry = ({ onUpdateFormField={onUpdateFormField} onUpdateFormFieldAsync={onUpdateFormFieldAsync} onGetValidationContext={onGetValidationContext} - programOwnerId={programOwnerId} + ownerOrgUnitId={ownerOrgUnitId} pluginContext={pluginContext} accessReadOnly={accessReadOnly} /> @@ -184,7 +184,7 @@ export const DataEntry = ({ onGetValidationContext={onGetValidationContext} errorsMessages={errorsMessages} warningsMessages={warningsMessages} - programOwnerId={programOwnerId} + ownerOrgUnitId={ownerOrgUnitId} pluginContext={pluginContext} accessReadOnly={accessReadOnly} /> diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntryReadOnly.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntryReadOnly.component.tsx index ea38054e9f..3dfd8926b5 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntryReadOnly.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntryReadOnly.component.tsx @@ -14,7 +14,7 @@ type Props = { onUpdateFormField: (innerAction: any) => void; onUpdateFormFieldAsync: (innerAction: any) => void; onGetValidationContext: () => Record; - programOwnerId: string; + ownerOrgUnitId: string; pluginContext?: PluginContext; accessReadOnly?: boolean; }; @@ -28,7 +28,7 @@ export const DataEntryReadOnlyComponent = ({ trackedEntityName, formFoundation, onGetValidationContext, - programOwnerId, + ownerOrgUnitId, pluginContext, accessReadOnly, }: Props) => ( @@ -53,7 +53,7 @@ export const DataEntryReadOnlyComponent = ({ onUpdateFormField={onUpdateFormField} onUpdateFormFieldAsync={onUpdateFormFieldAsync} onGetValidationContext={onGetValidationContext} - orgUnitId={programOwnerId} + orgUnitId={ownerOrgUnitId} pluginContext={pluginContext} viewMode /> diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts index 5fb5094195..89dabe5527 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.actions.ts @@ -143,7 +143,7 @@ export const cleanTeiModal = () => actionCreator(dataEntryActionTypes.CLEAN_TEI_ export const updateTeiRequest = ({ itemId, dataEntryId, - programOwnerId, + ownerOrgUnitId, trackedEntityTypeId, trackedEntityInstanceId, onSaveExternal, @@ -153,7 +153,7 @@ export const updateTeiRequest = ({ }: { itemId: string; dataEntryId: string; - programOwnerId: string; + ownerOrgUnitId: string; trackedEntityTypeId: string; trackedEntityInstanceId: string; onSaveExternal?: (eventServerValues: any, uid: string) => void; @@ -164,7 +164,7 @@ export const updateTeiRequest = ({ actionCreator(dataEntryActionTypes.TEI_UPDATE_REQUEST)({ itemId, dataEntryId, - programOwnerId, + ownerOrgUnitId, trackedEntityTypeId, trackedEntityInstanceId, formFoundation, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.epics.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.epics.ts index 18bf2dc89e..1146651e7b 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.epics.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.epics.ts @@ -49,7 +49,7 @@ export const updateTeiEpic = (action$: any, store: any) => const { dataEntryId, itemId, - programOwnerId, + ownerOrgUnitId, trackedEntityTypeId, trackedEntityInstanceId, formFoundation, @@ -67,7 +67,7 @@ export const updateTeiEpic = (action$: any, store: any) => geometry: deriveGeometryFromFormValues(values), trackedEntity: trackedEntityInstanceId, trackedEntityType: trackedEntityTypeId, - orgUnit: programOwnerId, + orgUnit: ownerOrgUnitId, }, ], }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.ts index 1358ce991d..841e0d7d01 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.ts @@ -17,7 +17,7 @@ export type PlainProps = { modalState: string; errorsMessages: Array<{ id: string; message: string }>; warningsMessages: Array<{ id: string; message: string }>; - programOwnerId: string; + ownerOrgUnitId: string; pluginContext?: PluginContext; accessReadOnly?: boolean; }; @@ -31,14 +31,14 @@ export type ReadOnlyPlainProps = { onUpdateFormField: (innerAction: any) => void; onUpdateFormFieldAsync: (innerAction: any) => void; onGetValidationContext: () => Record; - programOwnerId: string; + ownerOrgUnitId: string; pluginContext?: PluginContext; accessReadOnly?: boolean; }; export type Props = { programAPI: any; - programOwnerId: string; + ownerOrgUnitId: string; dataEntryFormConfig: DataEntryFormConfig | null; onCancel: () => void; onDisable: () => void; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts index ff4a7fa490..7baf8fd118 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.ts @@ -29,7 +29,7 @@ import type { QuerySingleResource } from '../../../../utils/api'; type UseLifecycleParams = { programAPI: any; - programOwnerId: string; + ownerOrgUnitId: string; clientAttributesWithSubvalues: Array; userRoles: Array; dataEntryId: string; @@ -42,7 +42,7 @@ type UseLifecycleParams = { export const useLifecycle = ({ programAPI, - programOwnerId, + ownerOrgUnitId, clientAttributesWithSubvalues, userRoles, dataEntryId, @@ -57,7 +57,7 @@ export const useLifecycle = ({ const enrollment: EnrollmentData = useSelector(({ enrollmentDomain }: any) => enrollmentDomain?.enrollment); const dataElements: DataElements = useDataElements(programAPI); const otherEvents = useEvents(enrollment, dataElements); - const orgUnit: any = useCoreOrgUnit(programOwnerId).orgUnit; + const orgUnit: any = useCoreOrgUnit(ownerOrgUnitId).orgUnit; const rulesContainer: ProgramRulesContainer = useRulesContainer(programAPI); const formFoundation: RenderFoundation = useFormFoundation(programAPI, dataEntryFormConfig); const { formValues, clientValues } = useFormValues({ formFoundation, clientAttributesWithSubvalues, orgUnit }); diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 36d27d8cf2..568ba53593 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -74,7 +74,7 @@ const WidgetProfilePlain = ({ programId, enrollmentId, readOnlyMode = false, - programOwnerId = '', + ownerOrgUnitId = '', onUpdateTeiAttributeValues, onDeleteSuccess, onStatusToggleSuccess, @@ -289,7 +289,7 @@ const WidgetProfilePlain = ({ onEnable={handleOnEnable} programAPI={program} dataEntryFormConfig={dataEntryFormConfig} - programOwnerId={programOwnerId} + ownerOrgUnitId={ownerOrgUnitId} clientAttributesWithSubvalues={clientAttributesWithSubvalues} userRoles={userRoles} trackedEntityInstanceId={teiId} diff --git a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts index 8e818bec4b..25c255328e 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.ts @@ -1,7 +1,7 @@ export type Props = { teiId: string; programId: string; - programOwnerId: string; + ownerOrgUnitId: string; enrollmentId?: string; readOnlyMode?: boolean; onUpdateTeiAttributeValues?: (attributes: Array<{ [key: string]: string }>, teiDisplayName: string) => void; diff --git a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts index d808f3a344..ea09d30f48 100644 --- a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts +++ b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.ts @@ -28,7 +28,7 @@ const { DELETE_ENROLLMENT_EVENT_RELATIONSHIP, SET_TRACKED_ENTITY_INACTIVE_STATUS, SET_ENROLLMENT_RULE_EFFECTS, - UPDATE_ENROLLMENT_PROGRAM_OWNER, + UPDATE_ENROLLMENT_OWNER_ORG_UNIT, } = enrollmentSiteActionTypes; const setAssignee = (state, action) => { @@ -45,23 +45,23 @@ const setAssignee = (state, action) => { export const enrollmentDomainDesc = createReducerDescription( { [COMMON_ENROLLMENT_SITE_DATA_SET]: (state, { - payload: { enrollment, attributeValues, inactive, programOwnerId }, + payload: { enrollment, attributeValues, inactive, ownerOrgUnitId }, }) => ({ ...state, enrollment, attributeValues, enrollmentId: enrollment?.enrollment, inactive, - programOwnerId, + ownerOrgUnitId, ruleEffects: null, }), [SET_ENROLLMENT_RULE_EFFECTS]: (state, { payload: { rulesEffects } }) => ({ ...state, ruleEffects: rulesEffects ?? null, }), - [UPDATE_ENROLLMENT_PROGRAM_OWNER]: (state, { payload: { programOwnerId } }) => ({ + [UPDATE_ENROLLMENT_OWNER_ORG_UNIT]: (state, { payload: { ownerOrgUnitId } }) => ({ ...state, - programOwnerId, + ownerOrgUnitId, ruleEffects: null, }), [SET_TRACKED_ENTITY_INACTIVE_STATUS]: (state, { payload: { inactive } }) => ({ From 17d4c235e4b899417b0a221a7ce6afda840cef66 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Thu, 10 Sep 2026 09:12:50 +0000 Subject: [PATCH 38/40] fix: move isEmptyList declaration to a single location for clarity --- i18n/en.pot | 4 ++-- .../components/WidgetProfile/WidgetProfile.component.tsx | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index afe3ed875d..60698a324e 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-09T14:25:09.433Z\n" -"PO-Revision-Date: 2026-09-09T14:25:09.434Z\n" +"POT-Creation-Date: 2026-09-10T09:12:51.059Z\n" +"PO-Revision-Date: 2026-09-10T09:12:51.059Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx index 568ba53593..671a652199 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -154,9 +154,6 @@ const WidgetProfilePlain = ({ }; }), [clientAttributesWithSubvalues, hiddenFieldIds]); - const isEmptyList = !loading && !error - && (hasNoAttributes || displayInListAttributes.length === 0); - const onSaveExternal = useCallback(() => { queryClient.removeQueries([ReactQueryAppNamespace, 'changelog', CHANGELOG_ENTITY_TYPES.TRACKED_ENTITY, teiId]); }, [queryClient, teiId]); @@ -219,6 +216,8 @@ const WidgetProfilePlain = ({ const handleOpen = useCallback(() => setOpenStatus(true), [setOpenStatus]); const handleClose = useCallback(() => setOpenStatus(false), [setOpenStatus]); + const isEmptyList = !loading && !error && (hasNoAttributes || displayInListAttributes.length === 0); + const { trackedEntityProp, trackedEntityForToggle } = useMemo(() => { const resolvedId = (trackedEntity && trackedEntity.trackedEntity) || teiId; const trackedEntityTypeId = program?.trackedEntityType?.id; From 3cff0d6741f25284ae71b22d36d1517be1fae90a Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Thu, 10 Sep 2026 09:13:27 +0000 Subject: [PATCH 39/40] fix: (review) add initial run skip logic for enrollment rule effects --- i18n/en.pot | 4 ++-- .../useRuleEffects/useEnrollmentScopeRuleEffects.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 60698a324e..912f76070d 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-09-10T09:12:51.059Z\n" -"PO-Revision-Date: 2026-09-10T09:12:51.059Z\n" +"POT-Creation-Date: 2026-09-10T09:13:28.812Z\n" +"PO-Revision-Date: 2026-09-10T09:13:28.812Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts index c54bcab281..90cac73d4f 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useEnrollmentScopeRuleEffects.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { getApplicableRuleEffectsForTrackerProgram } from '../../../../../rules'; import type { TrackerProgram } from '../../../../../metaData'; @@ -24,12 +24,19 @@ export const useEnrollmentScopeRuleEffects = ({ const dispatch = useDispatch(); const domainAligned = useSelector(({ enrollmentDomain }: any) => Boolean(enrollmentId) && enrollmentDomain?.enrollmentId === enrollmentId); + const hasFreshEffectsOnMount = useSelector(({ enrollmentDomain }: any) => + enrollmentDomain?.enrollmentId === enrollmentId && enrollmentDomain?.ruleEffects != null); + const shouldSkipInitialRun = useRef(hasFreshEffectsOnMount); const attributeValues = useAttributeValuesForRules(program, apiAttributeValues); const enrollmentData = useEnrollmentData(apiEnrollment); const otherEvents = useEventsData(apiEnrollment, program); useEffect(() => { if (!domainAligned || !orgUnit || !attributeValues || !enrollmentData || !otherEvents) return; + if (shouldSkipInitialRun.current) { + shouldSkipInitialRun.current = false; + return; + } const effects = getApplicableRuleEffectsForTrackerProgram({ program, orgUnit, From d56788712bf6e2e2034e2eea248d9c4991540010 Mon Sep 17 00:00:00 2001 From: henrikmv <110386561+henrikmv@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:37:20 +0200 Subject: [PATCH 40/40] fix: merge conflict in pot file --- i18n/en.pot | 5 ----- 1 file changed, 5 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index dafc23ad38..912f76070d 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,13 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -<<<<<<< hv/fix/DHIS2-18620_hidden-TEA-displayed-in-TEI-profile "POT-Creation-Date: 2026-09-10T09:13:28.812Z\n" "PO-Revision-Date: 2026-09-10T09:13:28.812Z\n" -======= -"POT-Creation-Date: 2026-09-10T12:46:54.077Z\n" -"PO-Revision-Date: 2026-09-10T12:46:54.077Z\n" ->>>>>>> master msgid "The application could not be loaded." msgstr "The application could not be loaded."