From 90ecd9ee02aff2fc337c57fe2cc9f8594a90d112 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Fri, 29 May 2026 19:41:54 +0200 Subject: [PATCH 1/3] fix: restore so profile widget is self contained --- .../WidgetProfile/WidgetProfile.component.tsx | 18 ++++++---- .../components/WidgetProfile/hooks/index.ts | 1 + .../WidgetProfile/hooks/useUserRoles.ts | 36 +++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 src/core_modules/capture-core/components/WidgetProfile/hooks/useUserRoles.ts 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 70a3c6c444..e4b5cc05df 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.tsx @@ -15,9 +15,9 @@ import { useProgram, useTrackedEntityInstances, useClientAttributesWithSubvalues, + useUserRoles, useTeiDisplayName, } from './hooks'; -import { CurrentUser } from '../../utils/userInfo/CurrentUser'; import { DataEntry, dataEntryActionTypes, TEI_MODAL_STATE, convertClientToView } from './DataEntry'; import { ReactQueryAppNamespace } from '../../utils/reactQueryHelpers'; import { CHANGELOG_ENTITY_TYPES } from '../WidgetsChangelog'; @@ -57,13 +57,15 @@ const showEditModal = (loading: boolean, error: any, showEdit: boolean, modalSta const computeLoadingState = ( programsLoading: boolean, trackedEntityInstancesLoading: boolean, + userRolesLoading: boolean, configIsFetched: boolean, -) => programsLoading || trackedEntityInstancesLoading || !configIsFetched; +) => programsLoading || trackedEntityInstancesLoading || userRolesLoading || !configIsFetched; const computeError = ( programsError: any, trackedEntityInstancesError: any, -) => programsError || trackedEntityInstancesError; + userRolesError: any, +) => programsError || trackedEntityInstancesError || userRolesError; const WidgetProfilePlain = ({ teiId, @@ -96,8 +98,12 @@ const WidgetProfilePlain = ({ programWriteAccess, trackedEntityTypeWriteAccess, } = useEnrollmentAccessContext(); + const { + loading: userRolesLoading, + error: userRolesError, + userRoles, + } = useUserRoles(); const trackedEntityTypeName = program?.trackedEntityType?.displayName; - const userRoles = CurrentUser.get().userRoles; const hasNoAttributes = !program?.programTrackedEntityAttributes?.length; @@ -113,8 +119,8 @@ const WidgetProfilePlain = ({ return null; }, [isEditable, readOnlyMode, hasNoAttributes]); - const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, configIsFetched); - const error = computeError(programsError, trackedEntityInstancesError); + const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched); + const error = computeError(programsError, trackedEntityInstancesError, userRolesError); const clientAttributesWithSubvalues = useClientAttributesWithSubvalues( teiId, program as any, diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/index.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/index.ts index d5fc613397..9796657555 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/index.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/index.ts @@ -1,4 +1,5 @@ export { useProgram } from './useProgram'; export { useTrackedEntityInstances } from './useTrackedEntityInstances'; export { useClientAttributesWithSubvalues } from './useClientAttributesWithSubvalues'; +export { useUserRoles } from './useUserRoles'; export { useTeiDisplayName } from './useTeiDisplayName'; diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useUserRoles.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useUserRoles.ts new file mode 100644 index 0000000000..4a5e9d10f3 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useUserRoles.ts @@ -0,0 +1,36 @@ +import { useMemo } from 'react'; +import { useDataQuery } from '@dhis2/app-runtime'; + +const fields = 'userCredentials[userRoles]'; + +export const useUserRoles = () => { + const { error, loading, data } = useDataQuery( + useMemo( + () => ({ + userData: { + resource: 'me.json', + params: { + fields, + }, + }, + }), + [], + ), + ); + + const userRoles = useMemo( + () => { + if (!loading && data?.userData) { + const userData = data.userData as { + userCredentials?: { + userRoles?: Array<{ id: string }> + } + }; + return userData.userCredentials?.userRoles?.map(({ id }) => id) ?? []; + } + return []; + }, + [loading, data], + ); + return { error, loading, userRoles }; +}; From cc173bca0a8f4320028c08b0207287a69150ea47 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Mon, 1 Jun 2026 09:21:43 +0200 Subject: [PATCH 2/3] fix: restore enrollment and transfer --- .../useSearchScopeWithFallback.ts | 28 +++++++++++++----- .../WidgetEnrollment.container.tsx | 12 ++++---- .../utils/localeData/useUserLocale.ts | 29 +++++++++++++++++++ 3 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 src/core_modules/capture-core/utils/localeData/useUserLocale.ts diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/OrgUnitField/useSearchScopeWithFallback.ts b/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/OrgUnitField/useSearchScopeWithFallback.ts index 679946ab8c..ceb27ec78e 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/OrgUnitField/useSearchScopeWithFallback.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/OrgUnitField/useSearchScopeWithFallback.ts @@ -1,16 +1,28 @@ -import { useMemo } from 'react'; import { useApiMetadataQuery } from '../../../../utils/reactQueryHelpers'; -import { CurrentUser } from '../../../../utils/userInfo/CurrentUser'; type Props = { searchText?: string; }; export const useSearchScopeWithFallback = ({ searchText }: Props) => { - const orgUnitRootsFromUser = useMemo(() => { - const { teiSearchOrganisationUnits, organisationUnits } = CurrentUser.get(); - return teiSearchOrganisationUnits.length ? teiSearchOrganisationUnits : organisationUnits; - }, []); + const { data: orgUnitRoots, isInitialLoading } = useApiMetadataQuery( + ['organisationUnits', 'userOrgUnitScope'], + { + resource: 'me', + params: { + fields: 'teiSearchOrganisationUnits[id,path],organisationUnits[id,path]', + }, + }, + { + enabled: !searchText, + select: (data) => { + const { teiSearchOrganisationUnits, organisationUnits } = data as any; + return teiSearchOrganisationUnits.length + ? teiSearchOrganisationUnits + : organisationUnits; + }, + }, + ); const { data: searchOrgUnits, isInitialLoading: isInitialLoadingSearch } = useApiMetadataQuery( ['organisationUnits', 'userOrgUnitScope', 'search', searchText], @@ -35,7 +47,7 @@ export const useSearchScopeWithFallback = ({ searchText }: Props) => { ); return { - orgUnitRoots: searchText?.length ? searchOrgUnits : orgUnitRootsFromUser, - isLoading: searchText?.length ? isInitialLoadingSearch : false, + orgUnitRoots: searchText?.length ? searchOrgUnits : orgUnitRoots, + isLoading: searchText?.length ? isInitialLoadingSearch : isInitialLoading, }; }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.tsx b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.tsx index b12329ae40..0dc21430ee 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.tsx +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.tsx @@ -6,14 +6,14 @@ import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName import { useTrackedEntities } from './hooks/useTrackedEntities'; import { useEnrollment } from './hooks/useEnrollment'; import { useProgram } from './hooks/useProgram'; -import { CurrentUser } from '../../utils/userInfo/CurrentUser'; +import { useUserLocale } from '../../utils/localeData/useUserLocale'; import type { Props } from './enrollment.types'; import { plainStatus } from './constants/status.const'; -const useError = (errorEnrollment: any, errorProgram: any, errorOwnerOrgUnit: any, errorOrgUnit: any) => +const useError = (errorEnrollment: any, errorProgram: any, errorOwnerOrgUnit: any, errorOrgUnit: any, errorLocale: any) => useMemo( - () => errorEnrollment ?? errorProgram ?? errorOwnerOrgUnit ?? errorOrgUnit, - [errorEnrollment, errorProgram, errorOwnerOrgUnit, errorOrgUnit], + () => errorEnrollment ?? errorProgram ?? errorOwnerOrgUnit ?? errorOrgUnit ?? errorLocale, + [errorEnrollment, errorProgram, errorOwnerOrgUnit, errorOrgUnit, errorLocale], ); const useContainsAutoGeneratedEvent = (program: any) => @@ -73,10 +73,10 @@ export const WidgetEnrollment = ({ const { error: errorOrgUnit, displayName } = useOrgUnitNameWithAncestors( typeof ownerOrgUnit === 'string' ? ownerOrgUnit : undefined, ); - const locale = CurrentUser.get().uiLocale; + const { error: errorLocale, locale } = useUserLocale(); const canAddNew = useCanAddNew(enrollments, programId, program?.trackedEntityType.access); const containsAutoGeneratedEvent = useContainsAutoGeneratedEvent(program); - const error = useError(errorEnrollment, errorProgram, errorOwnerOrgUnit, errorOrgUnit); + const error = useError(errorEnrollment, errorProgram, errorOwnerOrgUnit, errorOrgUnit, errorLocale); const events = useEnrollmentEvents(externalData); if (error) { diff --git a/src/core_modules/capture-core/utils/localeData/useUserLocale.ts b/src/core_modules/capture-core/utils/localeData/useUserLocale.ts new file mode 100644 index 0000000000..902fcc507a --- /dev/null +++ b/src/core_modules/capture-core/utils/localeData/useUserLocale.ts @@ -0,0 +1,29 @@ +import { useDataEngine } from '@dhis2/app-runtime'; +import { useQuery } from '@tanstack/react-query'; + +export const useUserLocale = (): { + locale: any; + isLoading: boolean; + isError: boolean; + error: unknown; +} => { + const dataEngine = useDataEngine(); + + const { data, isInitialLoading, isError, error } = useQuery( + ['userLocale'], + () => dataEngine.query({ + userSettings: { + resource: 'me', + params: { + fields: 'settings[keyUiLocale]', + }, + }, + })); + + return { + locale: (data as any)?.userSettings?.settings?.keyUiLocale, + isLoading: isInitialLoading, + isError, + error, + }; +}; From 0bd164e3cc4976aa12e6ab83628489e8c36bca76 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Mon, 1 Jun 2026 09:35:17 +0200 Subject: [PATCH 3/3] fix: sonar qube --- .../useSearchScopeWithFallback.ts | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/OrgUnitField/useSearchScopeWithFallback.ts b/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/OrgUnitField/useSearchScopeWithFallback.ts index ceb27ec78e..ee70ea1d9f 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/OrgUnitField/useSearchScopeWithFallback.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/OrgUnitField/useSearchScopeWithFallback.ts @@ -4,8 +4,15 @@ type Props = { searchText?: string; }; +type OrgUnit = { id: string; path: string }; +type MeOrgUnitScope = { + teiSearchOrganisationUnits: Array; + organisationUnits: Array; +}; +type OrgUnitsResponse = { organisationUnits: Array }; + export const useSearchScopeWithFallback = ({ searchText }: Props) => { - const { data: orgUnitRoots, isInitialLoading } = useApiMetadataQuery( + const { data: orgUnitRoots, isInitialLoading } = useApiMetadataQuery>( ['organisationUnits', 'userOrgUnitScope'], { resource: 'me', @@ -15,16 +22,15 @@ export const useSearchScopeWithFallback = ({ searchText }: Props) => { }, { enabled: !searchText, - select: (data) => { - const { teiSearchOrganisationUnits, organisationUnits } = data as any; - return teiSearchOrganisationUnits.length - ? teiSearchOrganisationUnits - : organisationUnits; - }, + select: ({ teiSearchOrganisationUnits, organisationUnits }) => + (teiSearchOrganisationUnits.length ? teiSearchOrganisationUnits : organisationUnits), }, ); - const { data: searchOrgUnits, isInitialLoading: isInitialLoadingSearch } = useApiMetadataQuery( + const { + data: searchOrgUnits, + isInitialLoading: isInitialLoadingSearch, + } = useApiMetadataQuery>( ['organisationUnits', 'userOrgUnitScope', 'search', searchText], { resource: 'organisationUnits', @@ -39,10 +45,7 @@ export const useSearchScopeWithFallback = ({ searchText }: Props) => { { enabled: Boolean(searchText), cacheTime: 120 * 60 * 1000, - select: (data) => { - const { organisationUnits } = data as any; - return organisationUnits; - }, + select: ({ organisationUnits }) => organisationUnits, }, );