From 6bb5a5580b545c4def5c4c8fd88ec226d1dcf32a Mon Sep 17 00:00:00 2001 From: henrikmv Date: Tue, 12 May 2026 09:25:08 +0200 Subject: [PATCH 01/18] feat: init commit --- src/components/AppLoader/init.ts | 29 +++++-- .../epics/addNoteForNewSingleEvent.epics.ts | 41 ++++------ .../useMetadataForProgramStage.ts | 4 +- .../useMetadataForRegistrationForm.ts | 4 +- .../OrgUnitField/OrgUnitField.component.tsx | 20 +---- .../ViewEvent/Notes/viewEventNotes.epics.ts | 49 +++++------- .../SearchBox/hooks/useSearchOption.ts | 4 +- .../useSearchScopeWithFallback.ts | 28 ++----- .../WidgetEnrollment.container.tsx | 12 +-- .../DataEntry/epics/dataEntryNote.epics.ts | 41 ++++------ .../WidgetEnrollmentNote.epics.ts | 56 ++++++------- .../WidgetEventNote/WidgetEventNote.epics.ts | 80 +++++++++---------- .../WidgetEventSchedule.container.tsx | 26 +++--- .../WidgetEventSchedule/hooks/index.ts | 2 - .../hooks/useNoteDetails.ts | 22 ----- .../WidgetProfile/hooks/useApiProgram.ts | 5 +- .../WidgetProfile/hooks/useOptionSets.ts | 75 +++++++++++++++++ .../WidgetProfile/hooks/useProgram.ts | 44 +++++++++- .../getEventListData/getEventListData.ts | 3 +- .../helpers/getTeiListData/getTeiListData.ts | 2 +- ...useBulkDataEntryDatastoreConfigurations.ts | 4 +- .../utils/localeData/useUserLocale.ts | 29 ------- .../utils/userInfo/CurrentUser.ts | 33 ++++++++ 23 files changed, 330 insertions(+), 283 deletions(-) delete mode 100644 src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts create mode 100644 src/core_modules/capture-core/components/WidgetProfile/hooks/useOptionSets.ts delete mode 100644 src/core_modules/capture-core/utils/localeData/useUserLocale.ts create mode 100644 src/core_modules/capture-core/utils/userInfo/CurrentUser.ts diff --git a/src/components/AppLoader/init.ts b/src/components/AppLoader/init.ts index f8cdaefc13..6760ba7ab3 100644 --- a/src/components/AppLoader/init.ts +++ b/src/components/AppLoader/init.ts @@ -3,6 +3,7 @@ import { environments } from 'capture-core/constants/environments'; import moment from 'moment'; import { CurrentLocaleData } from 'capture-core/utils/localeData/CurrentLocaleData'; import type { LocaleDataType } from 'capture-core/utils/localeData/CurrentLocaleData'; +import { CurrentUser } from 'capture-core/utils/userInfo/CurrentUser'; import i18n from '@dhis2/d2-i18n'; import { loadMetaData, cacheSystemSettings } from 'capture-core/metaDataStoreLoaders'; import { buildMetaDataAsync, buildSystemSettingsAsync } from 'capture-core/metaDataMemoryStoreBuilders'; @@ -145,18 +146,34 @@ export async function initializeAsync({ }) { setLogLevel(); + const meResponse = await querySingleResource({ + resource: 'me', + params: { + fields: 'id,firstName,surname,username,userRoles,userGroups,' + + 'organisationUnits[id,path],teiSearchOrganisationUnits[id,path],settings', + }, + }); + const { id: currentUserId, + firstName = '', + surname = '', + username = '', userRoles, userGroups, + organisationUnits: captureScope = [], + teiSearchOrganisationUnits: searchScope = [], + settings: userSettings, + } = meResponse; + + CurrentUser.set({ + id: currentUserId, + firstName, + surname, + username, + uiLocale: userSettings?.keyUiLocale ?? '', organisationUnits: captureScope, teiSearchOrganisationUnits: searchScope, - settings: userSettings, - } = await querySingleResource({ - resource: 'me', - params: { - fields: 'id,userRoles,userGroups,organisationUnits,teiSearchOrganisationUnits,settings', - }, }); const systemSettings = await querySingleResource({ diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts index 6b8e93764b..9ee62255b6 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts @@ -1,8 +1,9 @@ import uuid from 'd2-utilizr/lib/uuid'; import { ofType } from 'redux-observable'; -import { switchMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import moment from 'moment'; import type { ReduxStore, ApiUtils, EpicAction } from 'capture-core-utils/types'; +import { CurrentUser } from '../../../../../../utils/userInfo/CurrentUser'; import { actionTypes as newEventDataEntryActionTypes, } from '../actions/dataEntry.actions'; @@ -20,33 +21,25 @@ type AddNotePayload = { export const addNoteForNewSingleEventEpic = ( action$: EpicAction, store: ReduxStore, - { querySingleResource, fromClientDate }: ApiUtils, + { fromClientDate }: ApiUtils, ) => action$.pipe( ofType(newEventDataEntryActionTypes.ADD_NEW_EVENT_NOTE), - switchMap((action) => { + map((action) => { const payload = action.payload; - - return querySingleResource({ - resource: 'me', - params: { - fields: 'firstName,surname,userName', + const { firstName, surname, username } = CurrentUser.get(); + const clientId = uuid(); + const note = { + value: payload.note, + createdBy: { + firstName, + surname, + uid: clientId, }, - }).then((user) => { - const { userName, firstName, surname } = user; - const clientId = uuid(); - const note = { - value: payload.note, - createdBy: { - firstName, - surname, - uid: clientId, - }, - storedBy: userName, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - clientId: uuid(), - }; + storedBy: username, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + clientId: uuid(), + }; - return addNote(payload.dataEntryId, payload.itemId, note); - }); + return addNote(payload.dataEntryId, payload.itemId, note); })); diff --git a/src/core_modules/capture-core/components/DataEntries/common/ProgramStage/useMetadataForProgramStage.ts b/src/core_modules/capture-core/components/DataEntries/common/ProgramStage/useMetadataForProgramStage.ts index c9b7468ba6..2b5b52b77b 100644 --- a/src/core_modules/capture-core/components/DataEntries/common/ProgramStage/useMetadataForProgramStage.ts +++ b/src/core_modules/capture-core/components/DataEntries/common/ProgramStage/useMetadataForProgramStage.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { useConfig } from '@dhis2/app-runtime'; import { useProgramFromIndexedDB } from '../../../../utils/cachedDataHooks/useProgramFromIndexedDB'; -import { useUserLocale } from '../../../../utils/localeData/useUserLocale'; +import { CurrentUser } from '../../../../utils/userInfo/CurrentUser'; import { useDataEntryFormConfig, useOptionSetsForAttributes } from '../TEIAndEnrollment'; import { useDataElementsForStage } from './useDataElementsForStage'; import { useIndexedDBQuery } from '../../../../utils/reactQueryHelpers'; @@ -42,7 +42,7 @@ export const useMetadataForProgramStage = ({ }: Props): ReturnType => { const scopeId = stageId || programId; const { program } = useProgramFromIndexedDB(programId, { enabled: !!programId }); - const { locale } = useUserLocale(); + const locale = CurrentUser.get().uiLocale; const { serverVersion } = useConfig(); const minor = serverVersion?.minor; const { diff --git a/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/useMetadataForRegistrationForm.ts b/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/useMetadataForRegistrationForm.ts index 6ce2a8a009..4a1eeb3b1c 100644 --- a/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/useMetadataForRegistrationForm.ts +++ b/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/useMetadataForRegistrationForm.ts @@ -6,7 +6,7 @@ import { useScopeInfo } from '../../../../../hooks/useScopeInfo'; import { useTrackedEntityTypeCollection } from './hooks/useTrackedEntityTypeCollection'; import { useEnrollmentFormFoundation } from './hooks/useEnrollmentFormFoundation'; import { useTrackedEntityTypeFromIndexedDB } from '../../../../../utils/cachedDataHooks/useTrackedEntityTypeFromIndexedDB'; -import { useUserLocale } from '../../../../../utils/localeData/useUserLocale'; +import { CurrentUser } from '../../../../../utils/userInfo/CurrentUser'; import { useTrackedEntityAttributes } from './hooks/useTrackedEntityAttributes'; import { useDataEntryFormConfig } from './hooks/useDataEntryFormConfig'; @@ -25,7 +25,7 @@ export const useMetadataForRegistrationForm = ({ selectedScopeId }: Props) => { const { program } = useProgramFromIndexedDB(selectedScopeId, { enabled: !!(scopeType === scopeTypes.TRACKER_PROGRAM && selectedScopeId), }); - const { locale } = useUserLocale(); + const locale = CurrentUser.get().uiLocale; const { trackedEntityType } = useTrackedEntityTypeFromIndexedDB(tetId, { enabled: !!tetId }); const cachedTrackedEntityAttributeIds = useMemo(() => { diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx index 50bf9a9a48..3538750c5d 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx @@ -4,6 +4,7 @@ import i18n from '@dhis2/d2-i18n'; import { useDataQuery } from '@dhis2/app-runtime'; import { withStyles, type WithStyles } from 'capture-core-utils/styles'; import { DebounceField } from 'capture-ui'; +import { CurrentUser } from '../../../../../utils/userInfo/CurrentUser'; import { OrgUnitTree } from './OrgUnitTree.component'; const getStyles = () => ({ @@ -63,20 +64,7 @@ const OrgUnitFieldPlain = (props: Props) => { const [searchText, setSearchText] = React.useState(undefined); const [key, setKey] = React.useState(undefined); - const { loading, data } = useDataQuery( - React.useMemo( - () => ({ - orgUnits: { - resource: 'me', - params: { - fields: ['organisationUnits[id,path]'], - }, - - }, - }), - [], - ), - ); + const initialRoots = React.useMemo(() => CurrentUser.get().organisationUnits, []); const { loading: searchLoading, data: searchData, refetch: refetchOrg } = useDataQuery( React.useMemo( @@ -99,7 +87,7 @@ const OrgUnitFieldPlain = (props: Props) => { { lazy: true }, ); - const ready = searchText?.length ? !searchLoading : !loading; + const ready = searchText?.length ? !searchLoading : true; React.useEffect(() => { if (searchText?.length) { @@ -121,7 +109,7 @@ const OrgUnitFieldPlain = (props: Props) => { />); } return ( ], viewEventNotesBatchActionTypes.LOAD_EVENT_NOTES_BATCH); })); -export const addNoteForViewEventEpic = (action$: any, store: any, { querySingleResource, fromClientDate }: any) => +export const addNoteForViewEventEpic = (action$: any, store: any, { fromClientDate }: any) => action$.pipe( ofType(viewEventNotesActionTypes.REQUEST_SAVE_EVENT_NOTE), - switchMap((action: any) => { + map((action: any) => { const state = store.value; const payload = action.payload; const eventId = state.viewEventPage.eventId; const useNewEndpoint = featureAvailable(FEATURES.newNoteEndpoint); + const { firstName, surname, username } = CurrentUser.get(); + const clientId = uuid(); + const serverData = createServerData(eventId, payload.note, useNewEndpoint); - return querySingleResource({ - resource: 'me', - params: { - fields: 'userName,firstName,surname', + const clientNote = { + value: payload.note, + createdBy: { + firstName, + surname, + uid: clientId, }, - }).then((user: any) => { - const { userName, firstName, surname } = user; - const clientId = uuid(); - const serverData = createServerData(eventId, payload.note, useNewEndpoint); - - const clientNote = { - value: payload.note, - createdBy: { - firstName, - surname, - uid: clientId, - }, - storedBy: userName, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - clientId: uuid(), - }; - return batchActions([ - startSaveEventNote(eventId, serverData, state.currentSelections, clientNote.clientId), - addNote(noteKey, clientNote), - ], viewEventNotesBatchActionTypes.SAVE_EVENT_NOTE_BATCH); - }); + storedBy: username, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + clientId: uuid(), + }; + return batchActions([ + startSaveEventNote(eventId, serverData, state.currentSelections, clientNote.clientId), + addNote(noteKey, clientNote), + ], viewEventNotesBatchActionTypes.SAVE_EVENT_NOTE_BATCH); })); export const saveNoteForViewEventFailedEpic = (action$: any) => diff --git a/src/core_modules/capture-core/components/SearchBox/hooks/useSearchOption.ts b/src/core_modules/capture-core/components/SearchBox/hooks/useSearchOption.ts index b24fa2c09d..638789a392 100644 --- a/src/core_modules/capture-core/components/SearchBox/hooks/useSearchOption.ts +++ b/src/core_modules/capture-core/components/SearchBox/hooks/useSearchOption.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useProgramFromIndexedDB } from '../../../utils/cachedDataHooks/useProgramFromIndexedDB'; import { buildSearchOption } from '../../../hooks/useSearchOptions'; import { useTrackedEntityTypeFromIndexedDB } from '../../../utils/cachedDataHooks/useTrackedEntityTypeFromIndexedDB'; -import { useUserLocale } from '../../../utils/localeData/useUserLocale'; +import { CurrentUser } from '../../../utils/userInfo/CurrentUser'; import type { AvailableSearchOption } from '../SearchBox.types'; import type { SearchGroup } from '../../../metaData/SearchGroup/SearchGroup'; import { useIndexedDBQuery } from '../../../utils/reactQueryHelpers'; @@ -49,7 +49,7 @@ export const useSearchOption = ({ programId, trackedEntityTypeId, }: Props): { searchOption?: AvailableSearchOption; isLoading: boolean; isError: boolean } => { - const { locale } = useUserLocale(); + const locale = CurrentUser.get().uiLocale; const searchScope = useMemo(() => { if (programId) { 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..679946ab8c 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,28 +1,16 @@ +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 { 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 orgUnitRootsFromUser = useMemo(() => { + const { teiSearchOrganisationUnits, organisationUnits } = CurrentUser.get(); + return teiSearchOrganisationUnits.length ? teiSearchOrganisationUnits : organisationUnits; + }, []); const { data: searchOrgUnits, isInitialLoading: isInitialLoadingSearch } = useApiMetadataQuery( ['organisationUnits', 'userOrgUnitScope', 'search', searchText], @@ -47,7 +35,7 @@ export const useSearchScopeWithFallback = ({ searchText }: Props) => { ); return { - orgUnitRoots: searchText?.length ? searchOrgUnits : orgUnitRoots, - isLoading: searchText?.length ? isInitialLoadingSearch : isInitialLoading, + orgUnitRoots: searchText?.length ? searchOrgUnits : orgUnitRootsFromUser, + isLoading: searchText?.length ? isInitialLoadingSearch : false, }; }; 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 c8acf44885..8c51462aaa 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 { useUserLocale } from '../../utils/localeData/useUserLocale'; +import { CurrentUser } from '../../utils/userInfo/CurrentUser'; import type { Props } from './enrollment.types'; import { plainStatus } from './constants/status.const'; -const useError = (errorEnrollment: any, errorProgram: any, errorOwnerOrgUnit: any, errorOrgUnit: any, errorLocale: any) => +const useError = (errorEnrollment: any, errorProgram: any, errorOwnerOrgUnit: any, errorOrgUnit: any) => useMemo( - () => errorEnrollment ?? errorProgram ?? errorOwnerOrgUnit ?? errorOrgUnit ?? errorLocale, - [errorEnrollment, errorProgram, errorOwnerOrgUnit, errorOrgUnit, errorLocale], + () => errorEnrollment ?? errorProgram ?? errorOwnerOrgUnit ?? errorOrgUnit, + [errorEnrollment, errorProgram, errorOwnerOrgUnit, errorOrgUnit], ); const useContainsAutoGeneratedEvent = (program: any) => @@ -73,10 +73,10 @@ export const WidgetEnrollment = ({ const { error: errorOrgUnit, displayName } = useOrgUnitNameWithAncestors( typeof ownerOrgUnit === 'string' ? ownerOrgUnit : undefined, ); - const { error: errorLocale, locale } = useUserLocale(); + const locale = CurrentUser.get().uiLocale; const canAddNew = useCanAddNew(enrollments, programId, program?.trackedEntityType.access); const containsAutoGeneratedEvent = useContainsAutoGeneratedEvent(program); - const error = useError(errorEnrollment, errorProgram, errorOwnerOrgUnit, errorOrgUnit, errorLocale); + const error = useError(errorEnrollment, errorProgram, errorOwnerOrgUnit, errorOrgUnit); const events = useEnrollmentEvents(externalData); if (error) { diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts index 6af28b50a2..82b9f81957 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts @@ -1,8 +1,9 @@ import uuid from 'd2-utilizr/lib/uuid'; import { ofType } from 'redux-observable'; -import { switchMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import moment from 'moment'; import { ApiUtils, ReduxStore } from 'capture-core-utils/types'; +import { CurrentUser } from '../../../../utils/userInfo/CurrentUser'; import { newEventWidgetDataEntryActionTypes, } from '../actions/dataEntry.actions'; @@ -14,33 +15,25 @@ import { export const addNoteForNewEnrollmentEventEpic = ( action$: any, store: ReduxStore, - { querySingleResource, fromClientDate }: ApiUtils, + { fromClientDate }: ApiUtils, ) => action$.pipe( ofType(newEventWidgetDataEntryActionTypes.EVENT_NOTE_ADD), - switchMap((action: any) => { + map((action: any) => { const payload = action.payload; - - return querySingleResource({ - resource: 'me', - params: { - fields: 'firstName,surname,username', + const { firstName, surname, username } = CurrentUser.get(); + const clientId = uuid(); + const note = { + value: payload.note, + createdBy: { + firstName, + surname, + uid: clientId, }, - }).then((user: any) => { - const { userName, firstName, surname } = user; - const clientId = uuid(); - const note = { - value: payload.note, - createdBy: { - firstName, - surname, - uid: clientId, - }, - storedBy: userName, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - clientId: uuid(), - }; + storedBy: username, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + clientId: uuid(), + }; - return addNote(payload.dataEntryId, payload.itemId, note); - }); + return addNote(payload.dataEntryId, payload.itemId, note); })); diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts b/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts index 99eaf0c22f..609f3a200e 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts @@ -1,10 +1,11 @@ import { batchActions } from 'redux-batched-actions'; import { ofType } from 'redux-observable'; import { featureAvailable, FEATURES } from 'capture-core-utils'; -import { switchMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import uuid from 'd2-utilizr/lib/uuid'; import moment from 'moment'; import type { ReduxStore, ApiUtils, EpicAction } from 'capture-core-utils/types/global'; +import { CurrentUser } from '../../utils/userInfo/CurrentUser'; import { actionTypes, batchActionTypes, startAddNoteForEnrollment, addEnrollmentNote } from './WidgetEnrollmentNote.actions'; import type { ClientNote, SaveContext } from './WidgetEnrollmentNote.types'; @@ -24,44 +25,37 @@ const createServerData = (note: string, useNewEndpoint: boolean): Record, store: ReduxStore, - { querySingleResource, fromClientDate }: ApiUtils, + { fromClientDate }: ApiUtils, ) => action$.pipe( ofType(actionTypes.REQUEST_ADD_NOTE_FOR_ENROLLMENT), - switchMap((action: { payload: AddNoteActionPayload }) => { + map((action: { payload: AddNoteActionPayload }) => { const state = store.value; const { enrollmentId, note } = action.payload; const useNewEndpoint = featureAvailable(FEATURES.newNoteEndpoint); - return querySingleResource({ - resource: 'me', - params: { - fields: 'firstName, surname, userName', - }, - }).then((user) => { - const { firstName, surname, userName } = user; - const clientId = uuid(); + const { firstName, surname, username } = CurrentUser.get(); + const clientId = uuid(); - const serverData = createServerData(note, useNewEndpoint); + const serverData = createServerData(note, useNewEndpoint); - const clientNote: ClientNote = { - value: note, - createdBy: { - firstName, - surname, - uid: clientId, - }, - storedBy: userName, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - }; + const clientNote: ClientNote = { + value: note, + createdBy: { + firstName, + surname, + uid: clientId, + }, + storedBy: username, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + }; - const saveContext: SaveContext = { - enrollmentId, - noteClientId: clientId, - }; + const saveContext: SaveContext = { + enrollmentId, + noteClientId: clientId, + }; - return batchActions([ - startAddNoteForEnrollment(enrollmentId, serverData, state.currentSelections, saveContext), - addEnrollmentNote(enrollmentId, clientNote), - ], batchActionTypes.ADD_NOTE_BATCH_FOR_ENROLLMENT); - }); + return batchActions([ + startAddNoteForEnrollment(enrollmentId, serverData, state.currentSelections, saveContext), + addEnrollmentNote(enrollmentId, clientNote), + ], batchActionTypes.ADD_NOTE_BATCH_FOR_ENROLLMENT); })); diff --git a/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts b/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts index e2c191e142..9fca9c4e59 100644 --- a/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts @@ -1,10 +1,11 @@ import { batchActions } from 'redux-batched-actions'; import { ofType } from 'redux-observable'; import { featureAvailable, FEATURES } from 'capture-core-utils'; -import { map, switchMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import uuid from 'd2-utilizr/lib/uuid'; import moment from 'moment'; import type { ReduxStore, ApiUtils, EpicAction } from 'capture-core-utils/types'; +import { CurrentUser } from '../../utils/userInfo/CurrentUser'; import { actionTypes, batchActionTypes, startAddNoteForEvent } from './WidgetEventNote.actions'; import type { ClientNote, FormNote, SaveContext } from './WidgetEventNote.types'; @@ -38,58 +39,51 @@ const createServerData = (eventId: string, note: string, useNewEndpoint: boolean export const addNoteForEventEpic = ( action$: EpicAction, store: ReduxStore, - { querySingleResource, fromClientDate }: ApiUtils, + { fromClientDate }: ApiUtils, ) => action$.pipe( ofType(actionTypes.REQUEST_ADD_NOTE_FOR_EVENT), - switchMap((action: { payload: AddNoteActionPayload }) => { + map((action: { payload: AddNoteActionPayload }) => { const state = store.value; const payload = action.payload; const eventId = state.dataEntries[payload.dataEntryId].eventId; const useNewEndpoint = featureAvailable(FEATURES.newNoteEndpoint); - return querySingleResource({ - resource: 'me', - params: { - fields: 'firstName, surname, userName', - }, - }).then((user) => { - const { firstName, surname, userName } = user; - const clientId = uuid(); + const { firstName, surname, username } = CurrentUser.get(); + const clientId = uuid(); - const serverData = createServerData(eventId, payload.note, useNewEndpoint); + const serverData = createServerData(eventId, payload.note, useNewEndpoint); - const clientNote: ClientNote = { - value: payload.note, - lastUpdatedBy: { - firstName, - surname, - uid: clientId, - }, - storedBy: userName, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - }; - const formNote: FormNote = { - ...clientNote, - storedAt: clientNote.storedAt, - createdBy: { - firstName, - surname, - uid: clientId, - }, - }; - const saveContext: SaveContext = { - dataEntryId: payload.dataEntryId, - itemId: payload.itemId, - eventId, - noteClientId: clientId, - }; + const clientNote: ClientNote = { + value: payload.note, + lastUpdatedBy: { + firstName, + surname, + uid: clientId, + }, + storedBy: username, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + }; + const formNote: FormNote = { + ...clientNote, + storedAt: clientNote.storedAt, + createdBy: { + firstName, + surname, + uid: clientId, + }, + }; + const saveContext: SaveContext = { + dataEntryId: payload.dataEntryId, + itemId: payload.itemId, + eventId, + noteClientId: clientId, + }; - return batchActions([ - startAddNoteForEvent(eventId, serverData, state.currentSelections, saveContext), - addNote(payload.dataEntryId, payload.itemId, formNote), - addEventNote(eventId, clientNote), - ], batchActionTypes.ADD_NOTE_BATCH_FOR_EVENT); - }); + return batchActions([ + startAddNoteForEvent(eventId, serverData, state.currentSelections, saveContext), + addNote(payload.dataEntryId, payload.itemId, formNote), + addEventNote(eventId, clientNote), + ], batchActionTypes.ADD_NOTE_BATCH_FOR_EVENT); })); export const removeNoteForEventEpic = (action$: EpicAction) => diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx index 4f46356c5d..8632883180 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx @@ -7,6 +7,8 @@ import { pipe } from 'capture-core-utils'; import { getProgramAndStageForProgram, TrackerProgram, getProgramEventAccess, dataElementTypes } from '../../metaData'; import { getCachedOrgUnitName } from '../../metadataRetrieval/orgUnitName'; import { useLocationQuery } from '../../utils/routing'; +import { CurrentUser } from '../../utils/userInfo/CurrentUser'; +import { generateUID } from '../../utils/uid/generateUID'; import type { ContainerProps } from './widgetEventSchedule.types'; import { WidgetEventScheduleComponent } from './WidgetEventSchedule.component'; import { @@ -14,7 +16,6 @@ import { useDetermineSuggestedScheduleDate, useEventsInOrgUnit, useScheduleConfigFromProgram, - useNoteDetails, } from './hooks'; import { requestScheduleEvent } from './WidgetEventSchedule.actions'; import { NoAccess } from './AccessVerification'; @@ -52,7 +53,6 @@ export const WidgetEventSchedule = ({ }); const { fromClientDate } = useTimeZoneConversion(); const orgUnitName = getCachedOrgUnitName(initialOrgUnitId); - const { currentUser, noteId }: { currentUser: any, noteId: string } = useNoteDetails(); const [scheduleDate, setScheduleDate] = useState(''); const [scheduledOrgUnit, setScheduledOrgUnit] = useState(); const [validation, setValidation] = useState(); @@ -148,19 +148,15 @@ export const WidgetEventSchedule = ({ ]); const onAddNote = (note: string) => { - if (currentUser) { - const newNote = { - storedBy: currentUser.userName, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - value: note, - createdBy: { - firstName: currentUser.firstName, - surname: currentUser.surname, - }, - note: noteId, - }; - setNotes([...notes, newNote]); - } + const { username, firstName, surname } = CurrentUser.get(); + const newNote = { + storedBy: username, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + value: note, + createdBy: { firstName, surname }, + note: generateUID(), + }; + setNotes([...notes, newNote]); }; const onSetAssignee = useCallback((user: any) => setAssignee(user), []); diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/index.ts b/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/index.ts index b41c3de724..951d36bf88 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/index.ts +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/index.ts @@ -2,12 +2,10 @@ import { useDetermineSuggestedScheduleDate } from './useDetermineSuggestedSchedu import { useEventsInOrgUnit } from './useEventsInOrgUnit'; import { useScheduleConfigFromProgram } from './useScheduleConfigFromProgram'; import { useScheduleConfigFromProgramStage } from './useScheduleConfigFromProgramStage'; -import { useNoteDetails } from './useNoteDetails'; export { useDetermineSuggestedScheduleDate, useEventsInOrgUnit, useScheduleConfigFromProgram, useScheduleConfigFromProgramStage, - useNoteDetails, }; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts b/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts deleted file mode 100644 index 972ee5ef77..0000000000 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useMemo } from 'react'; -import { useDataQuery } from '@dhis2/app-runtime'; -import { generateUID } from '../../../utils/uid/generateUID'; - -export const useNoteDetails = () => { - const { data, error, loading } = useDataQuery(useMemo(() => ({ - currentUser: { - resource: 'me', - params: { - fields: - ['firstName,surname,username'], - }, - }, - }), [])); - - - return { - error, - currentUser: !loading && data?.currentUser, - noteId: generateUID(), - }; -}; diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts index ada6c9dde0..cedccec3ab 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts @@ -17,11 +17,10 @@ const fields = 'programStageSections[id,displayName,displayDescription,sortOrder,dataElements[id]],' + 'programStageDataElements[compulsory,displayInReports,renderOptionsAsRadio,allowFutureDate,' + 'renderType[*],dataElement[id,displayName,displayShortName,displayFormName,valueType,' + - 'translations[*],description,optionSetValue,style,optionSet[id,displayName,version,valueType,' + - 'options[id,displayName,code,style, translations]]]]],' + + 'translations[*],description,optionSetValue,style,optionSet[id,version]]]],' + 'programTrackedEntityAttributes[trackedEntityAttribute[id,displayName,displayShortName,displayFormName,' + 'displayDescription,valueType,optionSetValue,unique,orgunitScope,pattern,translations[property,locale,value],' + - 'optionSet[id,displayName,version,valueType,options[id,displayName,name,code,style,translations]]],' + + 'optionSet[id,version]],' + 'displayInList,searchable,mandatory,renderOptionsAsRadio,allowFutureDate],' + 'trackedEntityType[id,access,displayName,allowAuditLog,minAttributesRequiredToSearch,featureType,' + 'trackedEntityTypeAttributes[trackedEntityAttribute[id],displayInList,mandatory,searchable],' + diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useOptionSets.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useOptionSets.ts new file mode 100644 index 0000000000..a432e03365 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useOptionSets.ts @@ -0,0 +1,75 @@ +import { useMemo } from 'react'; +import { useApiMetadataQuery } from 'capture-core/utils/reactQueryHelpers'; + +type ApiOptionSet = { + id: string; + displayName?: string; + version?: number; + valueType?: string; + options?: Array<{ + id: string; + displayName?: string; + name?: string; + code?: string; + style?: any; + translations?: any; + }>; +}; + +type OptionSetDict = { [optionSetId: string]: ApiOptionSet }; + +const collectOptionSetIds = (program: any): string[] => { + const ids = new Set(); + + program.programTrackedEntityAttributes?.forEach((attribute: any) => { + const id = attribute?.trackedEntityAttribute?.optionSet?.id; + if (id) ids.add(id); + }); + + program.programStages?.forEach((stage: any) => { + stage.programStageDataElements?.forEach((psde: any) => { + const id = psde?.dataElement?.optionSet?.id; + if (id) ids.add(id); + }); + }); + + return [...ids]; +}; + +const toDict = ({ optionSets = [] }: { optionSets?: ApiOptionSet[] }): OptionSetDict => + optionSets.reduce((acc: OptionSetDict, optionSet) => { + acc[optionSet.id] = optionSet; + return acc; + }, {}); + +export const useOptionSets = (program: any) => { + const optionSetIds = useMemo(() => (program ? collectOptionSetIds(program) : []), [program]); + + const queryKey = ['optionSets', 'programOptionSets', ...(program?.id ? [program.id] : [])]; + const queryFn = { + resource: 'optionSets', + params: { + fields: 'id,displayName,version,valueType,' + + 'options[id,displayName,name,code,style,translations]', + filter: `id:in:[${optionSetIds.join(',')}]`, + paging: false, + }, + }; + const queryOptions = { + enabled: Boolean(program) && optionSetIds.length > 0, + select: toDict, + }; + + const { data, isInitialLoading, error } = useApiMetadataQuery(queryKey, queryFn, queryOptions); + + let optionSets: OptionSetDict | null = null; + if (program) { + optionSets = optionSetIds.length === 0 ? {} : (data ?? null); + } + + return { + optionSets, + loading: isInitialLoading, + error, + }; +}; diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.ts index 6a3d9ac2d9..b58111821a 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.ts @@ -1,6 +1,7 @@ import { useMemo } from 'react'; import { useApiProgram } from './useApiProgram'; import { useOptionGroups } from './useOptionGroups'; +import { useOptionSets } from './useOptionSets'; type ProgramType = { trackedEntityType?: { @@ -18,36 +19,71 @@ type ProgramType = { }; [key: string]: any; }>; + programStages?: Array<{ + programStageDataElements?: Array<{ + dataElement?: { + optionSet?: { + id: string; + [key: string]: any; + }; + [key: string]: any; + }; + [key: string]: any; + }>; + [key: string]: any; + }>; [key: string]: any; }; +const hydrateOptionSet = ( + optionSet: { id: string; [key: string]: any } | undefined, + fullOptionSets: { [id: string]: any }, +) => { + if (!optionSet) return optionSet; + const full = fullOptionSets[optionSet.id]; + if (!full) return optionSet; + return { ...optionSet, ...full }; +}; + export const useProgram = (programId: string) => { const { error: programError, loading: programLoading, program } = useApiProgram(programId); const { error: optionGroupsError, loading: optionGroupsLoading, optionGroups } = useOptionGroups(program); + const { error: optionSetsError, loading: optionSetsLoading, optionSets } = useOptionSets(program); const programMetadata = useMemo(() => { - if (program && optionGroups) { + if (program && optionGroups && optionSets) { const typedProgram = program as ProgramType; if (typedProgram.trackedEntityType) { typedProgram.trackedEntityType.changelogEnabled = typedProgram.trackedEntityType.allowAuditLog; delete typedProgram.trackedEntityType.allowAuditLog; } + typedProgram.programTrackedEntityAttributes = typedProgram.programTrackedEntityAttributes.map( (attribute: any) => { const tea = attribute.trackedEntityAttribute; if (tea.optionSet) { + tea.optionSet = hydrateOptionSet(tea.optionSet, optionSets); tea.optionSet.optionGroups = optionGroups[tea.optionSet.id]; } return attribute; }); + + typedProgram.programStages?.forEach((stage: any) => { + stage.programStageDataElements?.forEach((psde: any) => { + if (psde.dataElement?.optionSet) { + psde.dataElement.optionSet = hydrateOptionSet(psde.dataElement.optionSet, optionSets); + } + }); + }); + return typedProgram; } return null; - }, [program, optionGroups]); + }, [program, optionGroups, optionSets]); return { program: programMetadata, - loading: programLoading ?? optionGroupsLoading, - error: programError ?? optionGroupsError, + loading: programLoading || optionGroupsLoading || optionSetsLoading, + error: programError ?? optionGroupsError ?? optionSetsError, }; }; diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts index fb484b0087..5349018958 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts @@ -45,7 +45,8 @@ const createApiEventQueryArgs = ( [orgUnitModeQueryParam]: orgUnit ? 'SELECTED' : 'CAPTURE', program, programStage, - fields: '*', + fields: 'event,status,program,programStage,enrollment,trackedEntity,' + + 'occurredAt,scheduledAt,orgUnit,assignedUser,dataValues[dataElement,value]', }; return getScheduledDateQueryArgs(queryArgs); diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts index f9232a8788..9bef03dc81 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts @@ -40,7 +40,7 @@ filtersOnlyMetaForDataFetching: TeiFiltersOnlyMetaForDataFetching, [orgUnitQueryParam]: orgUnitId, [orgUnitModeQueryParam]: orgUnitId ? 'SELECTED' : 'CAPTURE', program, - fields: ':all,!relationships,programOwners[orgUnit,program]', + fields: 'trackedEntity,createdAt,inactive,attributes[attribute,value],programOwners[orgUnit,program]', }; }; diff --git a/src/core_modules/capture-core/components/common/bulkDataEntry/hooks/useBulkDataEntryDatastoreConfigurations.ts b/src/core_modules/capture-core/components/common/bulkDataEntry/hooks/useBulkDataEntryDatastoreConfigurations.ts index 531b843389..c7e3826a8a 100644 --- a/src/core_modules/capture-core/components/common/bulkDataEntry/hooks/useBulkDataEntryDatastoreConfigurations.ts +++ b/src/core_modules/capture-core/components/common/bulkDataEntry/hooks/useBulkDataEntryDatastoreConfigurations.ts @@ -3,7 +3,7 @@ import log from 'loglevel'; import { errorCreator } from 'capture-core-utils'; import { useEffect } from 'react'; import { useApiMetadataQuery } from '../../../../utils/reactQueryHelpers'; -import { useUserLocale } from '../../../../utils/localeData/useUserLocale'; +import { CurrentUser } from '../../../../utils/userInfo/CurrentUser'; import type { DataStoreConfiguration } from '../bulkDataEntry.types'; const bulkDataEntryDatastoreSchema = z.object({ @@ -48,7 +48,7 @@ const getLocalizedString = (field: Record, locale: string): stri }; export const useBulkDataEntryDatastoreConfigurations = (programId: string) => { - const { locale } = useUserLocale(); + const locale = CurrentUser.get().uiLocale; const { data: configExists, isLoading: namespaceIsLoading, diff --git a/src/core_modules/capture-core/utils/localeData/useUserLocale.ts b/src/core_modules/capture-core/utils/localeData/useUserLocale.ts deleted file mode 100644 index 902fcc507a..0000000000 --- a/src/core_modules/capture-core/utils/localeData/useUserLocale.ts +++ /dev/null @@ -1,29 +0,0 @@ -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, - }; -}; diff --git a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts new file mode 100644 index 0000000000..2b50970b19 --- /dev/null +++ b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts @@ -0,0 +1,33 @@ +export type OrgUnitRef = { + id: string; + path: string; +}; + +export type CurrentUserType = { + id: string; + firstName: string; + surname: string; + username: string; + uiLocale: string; + organisationUnits: OrgUnitRef[]; + teiSearchOrganisationUnits: OrgUnitRef[]; +}; + +export class CurrentUser { + private static currentData: CurrentUserType = { + id: '', + firstName: '', + surname: '', + username: '', + uiLocale: '', + organisationUnits: [], + teiSearchOrganisationUnits: [], + }; + + static set(data: CurrentUserType) { + CurrentUser.currentData = data; + } + static get() { + return CurrentUser.currentData; + } +} From b277f8c559eef978353ffd872d5910a22dfd0983 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Tue, 12 May 2026 13:45:32 +0200 Subject: [PATCH 02/18] fix: cypress --- .../helpers/getEventListData/getEventListData.ts | 13 +++++++++---- .../helpers/getTeiListData/getTeiListData.ts | 9 +++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts index 5349018958..bbeb8d4cef 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts @@ -16,6 +16,12 @@ import { addTEIsData } from './addTEIsData'; import { getColumnsQueryArgs } from './getColumnsQueryArgs'; import { getScheduledDateQueryArgs } from './getScheduledDateQueryArgs'; +const LISTING_FIELDS = + 'event,status,program,programStage,enrollment,trackedEntity,' + + 'occurredAt,scheduledAt,orgUnit,assignedUser,dataValues[dataElement,value]'; + +const DOWNLOAD_FIELDS = '*'; + const createApiEventQueryArgs = ( { page, @@ -45,8 +51,7 @@ const createApiEventQueryArgs = ( [orgUnitModeQueryParam]: orgUnit ? 'SELECTED' : 'CAPTURE', program, programStage, - fields: 'event,status,program,programStage,enrollment,trackedEntity,' + - 'occurredAt,scheduledAt,orgUnit,assignedUser,dataValues[dataElement,value]', + fields: LISTING_FIELDS, }; return getScheduledDateQueryArgs(queryArgs); @@ -87,7 +92,7 @@ export const getEventListData = async ( recordContainers: [], request: { url: urlEvents, - queryParams: queryParamsEvents, + queryParams: { ...queryParamsEvents, fields: DOWNLOAD_FIELDS }, }, }; } @@ -119,7 +124,7 @@ export const getEventListData = async ( recordContainers: clientWithSubvalues, request: { url: urlEvents, - queryParams: queryParamsEvents, + queryParams: { ...queryParamsEvents, fields: DOWNLOAD_FIELDS }, }, }; }; diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts index 9bef03dc81..3f1a5f602c 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts @@ -11,6 +11,11 @@ import type { RawQueryArgs } from './types'; import type { InputMeta } from './getTeiListData.types'; import type { TeiColumnsMetaForDataFetching, TeiFiltersOnlyMetaForDataFetching } from '../../../../types'; +const LISTING_FIELDS = + 'trackedEntity,createdAt,inactive,attributes[attribute,value],programOwners[orgUnit,program]'; + +const DOWNLOAD_FIELDS = '*,!relationships,programOwners[orgUnit,program]'; + export const createApiQueryArgs = ({ page, pageSize, @@ -40,7 +45,7 @@ filtersOnlyMetaForDataFetching: TeiFiltersOnlyMetaForDataFetching, [orgUnitQueryParam]: orgUnitId, [orgUnitModeQueryParam]: orgUnitId ? 'SELECTED' : 'CAPTURE', program, - fields: 'trackedEntity,createdAt,inactive,attributes[attribute,value],programOwners[orgUnit,program]', + fields: LISTING_FIELDS, }; }; @@ -73,7 +78,7 @@ export const getTeiListData = async ( recordContainers: clientTeisWithSubvalues, request: { url, - queryParams, + queryParams: { ...queryParams, fields: DOWNLOAD_FIELDS }, }, }; }; From 6bb8de4e3becccfa3e76d7e5e96e64cb6bd8e253 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Tue, 12 May 2026 13:52:35 +0200 Subject: [PATCH 03/18] fix: use all in field for json download --- .../epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts index 3f1a5f602c..20687334bf 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getTeiListData/getTeiListData.ts @@ -14,7 +14,7 @@ import type { TeiColumnsMetaForDataFetching, TeiFiltersOnlyMetaForDataFetching } const LISTING_FIELDS = 'trackedEntity,createdAt,inactive,attributes[attribute,value],programOwners[orgUnit,program]'; -const DOWNLOAD_FIELDS = '*,!relationships,programOwners[orgUnit,program]'; +const DOWNLOAD_FIELDS = ':all,!relationships,programOwners[orgUnit,program]'; export const createApiQueryArgs = ({ page, From 356a3db2cdb361c94ee7390e9934c2810b987a64 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Wed, 13 May 2026 15:09:29 +0200 Subject: [PATCH 04/18] fix: revert optin set changes --- .../WidgetProfile/hooks/useApiProgram.ts | 5 +- .../WidgetProfile/hooks/useOptionSets.ts | 75 ------------------- .../WidgetProfile/hooks/useProgram.ts | 44 +---------- 3 files changed, 7 insertions(+), 117 deletions(-) delete mode 100644 src/core_modules/capture-core/components/WidgetProfile/hooks/useOptionSets.ts diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts index cedccec3ab..ada6c9dde0 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts @@ -17,10 +17,11 @@ const fields = 'programStageSections[id,displayName,displayDescription,sortOrder,dataElements[id]],' + 'programStageDataElements[compulsory,displayInReports,renderOptionsAsRadio,allowFutureDate,' + 'renderType[*],dataElement[id,displayName,displayShortName,displayFormName,valueType,' + - 'translations[*],description,optionSetValue,style,optionSet[id,version]]]],' + + 'translations[*],description,optionSetValue,style,optionSet[id,displayName,version,valueType,' + + 'options[id,displayName,code,style, translations]]]]],' + 'programTrackedEntityAttributes[trackedEntityAttribute[id,displayName,displayShortName,displayFormName,' + 'displayDescription,valueType,optionSetValue,unique,orgunitScope,pattern,translations[property,locale,value],' + - 'optionSet[id,version]],' + + 'optionSet[id,displayName,version,valueType,options[id,displayName,name,code,style,translations]]],' + 'displayInList,searchable,mandatory,renderOptionsAsRadio,allowFutureDate],' + 'trackedEntityType[id,access,displayName,allowAuditLog,minAttributesRequiredToSearch,featureType,' + 'trackedEntityTypeAttributes[trackedEntityAttribute[id],displayInList,mandatory,searchable],' + diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useOptionSets.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useOptionSets.ts deleted file mode 100644 index a432e03365..0000000000 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useOptionSets.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { useMemo } from 'react'; -import { useApiMetadataQuery } from 'capture-core/utils/reactQueryHelpers'; - -type ApiOptionSet = { - id: string; - displayName?: string; - version?: number; - valueType?: string; - options?: Array<{ - id: string; - displayName?: string; - name?: string; - code?: string; - style?: any; - translations?: any; - }>; -}; - -type OptionSetDict = { [optionSetId: string]: ApiOptionSet }; - -const collectOptionSetIds = (program: any): string[] => { - const ids = new Set(); - - program.programTrackedEntityAttributes?.forEach((attribute: any) => { - const id = attribute?.trackedEntityAttribute?.optionSet?.id; - if (id) ids.add(id); - }); - - program.programStages?.forEach((stage: any) => { - stage.programStageDataElements?.forEach((psde: any) => { - const id = psde?.dataElement?.optionSet?.id; - if (id) ids.add(id); - }); - }); - - return [...ids]; -}; - -const toDict = ({ optionSets = [] }: { optionSets?: ApiOptionSet[] }): OptionSetDict => - optionSets.reduce((acc: OptionSetDict, optionSet) => { - acc[optionSet.id] = optionSet; - return acc; - }, {}); - -export const useOptionSets = (program: any) => { - const optionSetIds = useMemo(() => (program ? collectOptionSetIds(program) : []), [program]); - - const queryKey = ['optionSets', 'programOptionSets', ...(program?.id ? [program.id] : [])]; - const queryFn = { - resource: 'optionSets', - params: { - fields: 'id,displayName,version,valueType,' - + 'options[id,displayName,name,code,style,translations]', - filter: `id:in:[${optionSetIds.join(',')}]`, - paging: false, - }, - }; - const queryOptions = { - enabled: Boolean(program) && optionSetIds.length > 0, - select: toDict, - }; - - const { data, isInitialLoading, error } = useApiMetadataQuery(queryKey, queryFn, queryOptions); - - let optionSets: OptionSetDict | null = null; - if (program) { - optionSets = optionSetIds.length === 0 ? {} : (data ?? null); - } - - return { - optionSets, - loading: isInitialLoading, - error, - }; -}; diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.ts index b58111821a..6a3d9ac2d9 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.ts @@ -1,7 +1,6 @@ import { useMemo } from 'react'; import { useApiProgram } from './useApiProgram'; import { useOptionGroups } from './useOptionGroups'; -import { useOptionSets } from './useOptionSets'; type ProgramType = { trackedEntityType?: { @@ -19,71 +18,36 @@ type ProgramType = { }; [key: string]: any; }>; - programStages?: Array<{ - programStageDataElements?: Array<{ - dataElement?: { - optionSet?: { - id: string; - [key: string]: any; - }; - [key: string]: any; - }; - [key: string]: any; - }>; - [key: string]: any; - }>; [key: string]: any; }; -const hydrateOptionSet = ( - optionSet: { id: string; [key: string]: any } | undefined, - fullOptionSets: { [id: string]: any }, -) => { - if (!optionSet) return optionSet; - const full = fullOptionSets[optionSet.id]; - if (!full) return optionSet; - return { ...optionSet, ...full }; -}; - export const useProgram = (programId: string) => { const { error: programError, loading: programLoading, program } = useApiProgram(programId); const { error: optionGroupsError, loading: optionGroupsLoading, optionGroups } = useOptionGroups(program); - const { error: optionSetsError, loading: optionSetsLoading, optionSets } = useOptionSets(program); const programMetadata = useMemo(() => { - if (program && optionGroups && optionSets) { + if (program && optionGroups) { const typedProgram = program as ProgramType; if (typedProgram.trackedEntityType) { typedProgram.trackedEntityType.changelogEnabled = typedProgram.trackedEntityType.allowAuditLog; delete typedProgram.trackedEntityType.allowAuditLog; } - typedProgram.programTrackedEntityAttributes = typedProgram.programTrackedEntityAttributes.map( (attribute: any) => { const tea = attribute.trackedEntityAttribute; if (tea.optionSet) { - tea.optionSet = hydrateOptionSet(tea.optionSet, optionSets); tea.optionSet.optionGroups = optionGroups[tea.optionSet.id]; } return attribute; }); - - typedProgram.programStages?.forEach((stage: any) => { - stage.programStageDataElements?.forEach((psde: any) => { - if (psde.dataElement?.optionSet) { - psde.dataElement.optionSet = hydrateOptionSet(psde.dataElement.optionSet, optionSets); - } - }); - }); - return typedProgram; } return null; - }, [program, optionGroups, optionSets]); + }, [program, optionGroups]); return { program: programMetadata, - loading: programLoading || optionGroupsLoading || optionSetsLoading, - error: programError ?? optionGroupsError ?? optionSetsError, + loading: programLoading ?? optionGroupsLoading, + error: programError ?? optionGroupsError, }; }; From 7271e992f835c5e6a277270edb9317662c515e15 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Wed, 13 May 2026 15:25:54 +0200 Subject: [PATCH 05/18] feat: limit fields in organisation unit queries --- .../Components/OrgUnitField/orgUnitFieldForForms.epics.ts | 3 +-- .../UniqueTEADuplicate/ExistingTEILoader.component.tsx | 2 +- .../New/Fields/OrgUnitField/OrgUnitField.component.tsx | 4 +--- .../withInternalFilterHandler/withInternalFilterHandler.tsx | 5 +---- .../FormFields/OrgUnitTree/OrgUnitTree.component.tsx | 2 +- .../Pages/ViewEvent/epics/getCategoriesDataFromEvent.ts | 2 +- .../SearchOrgUnitSelector/searchOrgUnitSelector.epics.ts | 5 +---- .../SearchOrgUnitSelector/searchOrgUnitSelector.epics.ts | 3 +-- .../metaDataStoreLoaders/categories/loadCategories.ts | 2 +- 9 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/core_modules/capture-core/components/D2Form/field/Components/OrgUnitField/orgUnitFieldForForms.epics.ts b/src/core_modules/capture-core/components/D2Form/field/Components/OrgUnitField/orgUnitFieldForForms.epics.ts index 03decc3ee5..ea7f51151c 100644 --- a/src/core_modules/capture-core/components/D2Form/field/Components/OrgUnitField/orgUnitFieldForForms.epics.ts +++ b/src/core_modules/capture-core/components/D2Form/field/Components/OrgUnitField/orgUnitFieldForForms.epics.ts @@ -40,8 +40,7 @@ export const filterFormFieldOrgUnitsEpic = (action$: any, store: any, { querySin return from(querySingleResource({ resource: 'organisationUnits', params: { - fields: 'id,displayName,path,publicAccess,access,lastUpdated' + - 'children[id,displayName,publicAccess,access,path,children::isNotEmpty]', + fields: 'id,displayName,path', paging: false, withinUserSearchHierarchy: true, query: searchText, diff --git a/src/core_modules/capture-core/components/DataEntries/withErrorMessagePostProcessor/UniqueTEADuplicate/ExistingTEILoader.component.tsx b/src/core_modules/capture-core/components/DataEntries/withErrorMessagePostProcessor/UniqueTEADuplicate/ExistingTEILoader.component.tsx index 2ab7d0be84..f1a7e97e30 100644 --- a/src/core_modules/capture-core/components/DataEntries/withErrorMessagePostProcessor/UniqueTEADuplicate/ExistingTEILoader.component.tsx +++ b/src/core_modules/capture-core/components/DataEntries/withErrorMessagePostProcessor/UniqueTEADuplicate/ExistingTEILoader.component.tsx @@ -85,7 +85,7 @@ class ExistingTEILoaderComponentPlain extends React.Component { resource: `tracker/trackedEntities/${id}`, params: { program: programId, - fields: '*', + fields: 'attributes[attribute,value]', }, }), ); diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx index 3538750c5d..2b8f6b9ce9 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx @@ -72,9 +72,7 @@ const OrgUnitFieldPlain = (props: Props) => { orgUnits: { resource: 'organisationUnits', params: ({ variables: { searchText: currentSearchText } }) => ({ - fields: - 'id,displayName,path,publicAccess,access,lastUpdated' - + ',children[id,displayName,publicAccess,access,path,children::isNotEmpty]', + fields: 'id,path', paging: true, query: currentSearchText, withinUserSearchHierarchy: true, diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx index 5516f7de04..020ac825d9 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx @@ -61,10 +61,7 @@ export const withInternalFilterHandler = () => querySingleResource({ resource: 'organisationUnits', params: { - fields: [ - 'id,displayName,path,publicAccess,access,lastUpdated', - 'children[id,displayName,publicAccess,access,path,children::isNotEmpty]', - ].join(','), + fields: 'id,path', paging: false, query: filterText, ...hierarchyProp, diff --git a/src/core_modules/capture-core/components/FormFields/OrgUnitTree/OrgUnitTree.component.tsx b/src/core_modules/capture-core/components/FormFields/OrgUnitTree/OrgUnitTree.component.tsx index 2c06d1e928..4053168d05 100644 --- a/src/core_modules/capture-core/components/FormFields/OrgUnitTree/OrgUnitTree.component.tsx +++ b/src/core_modules/capture-core/components/FormFields/OrgUnitTree/OrgUnitTree.component.tsx @@ -90,7 +90,7 @@ export class OrgUnitTree extends React.Component { params: { paging: false, filter: `id:in:[${id}]`, - fields: ':all,displayName,path,children[id,displayName,path,children]', + fields: 'children[id,displayName,path,children]', }, }).then((r) => { diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/epics/getCategoriesDataFromEvent.ts b/src/core_modules/capture-core/components/Pages/ViewEvent/epics/getCategoriesDataFromEvent.ts index f89cdc722a..ac1bc44404 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/epics/getCategoriesDataFromEvent.ts +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/epics/getCategoriesDataFromEvent.ts @@ -6,7 +6,7 @@ const getCategoryOptionsAsync = async (optionIds: string, querySingleResource: a querySingleResource({ resource: 'categoryOptions', params: { - fields: 'id,displayName,categories~pluck,access', + fields: 'id,displayName,categories~pluck,access[data[write]]', filter: `id:in:[${optionIds}]`, }, }).then((response: any) => response?.categoryOptions); diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TeiSearch/SearchOrgUnitSelector/searchOrgUnitSelector.epics.ts b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TeiSearch/SearchOrgUnitSelector/searchOrgUnitSelector.epics.ts index 58eb6f8ba1..0937f99a66 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TeiSearch/SearchOrgUnitSelector/searchOrgUnitSelector.epics.ts +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TeiSearch/SearchOrgUnitSelector/searchOrgUnitSelector.epics.ts @@ -47,10 +47,7 @@ export const teiSearchFilterOrgUnitsEpic = ( return from(querySingleResource({ resource: 'organisationUnits', params: { - fields: [ - 'id,displayName,path,publicAccess,access,lastUpdated', - 'children[id,displayName,publicAccess,access,path,children::isNotEmpty]', - ].join(','), + fields: 'id,displayName,path', paging: true, withinUserSearchHierarchy: true, query: searchText, diff --git a/src/core_modules/capture-core/components/TeiSearch/SearchOrgUnitSelector/searchOrgUnitSelector.epics.ts b/src/core_modules/capture-core/components/TeiSearch/SearchOrgUnitSelector/searchOrgUnitSelector.epics.ts index 710e537e14..6419705b96 100644 --- a/src/core_modules/capture-core/components/TeiSearch/SearchOrgUnitSelector/searchOrgUnitSelector.epics.ts +++ b/src/core_modules/capture-core/components/TeiSearch/SearchOrgUnitSelector/searchOrgUnitSelector.epics.ts @@ -44,8 +44,7 @@ export const teiSearchFilterOrgUnitsEpic = (action$: any, store: any, { querySin return from(querySingleResource({ resource: 'organisationUnits', params: { - fields: 'id,displayName,path,publicAccess,access,lastUpdated' - + ',children[id,displayName,publicAccess,access,path,children::isNotEmpty]', + fields: 'id,displayName,path', paging: true, withinUserSearchHierarchy: true, query: searchText, diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/categories/loadCategories.ts b/src/core_modules/capture-core/metaDataStoreLoaders/categories/loadCategories.ts index 54acbb0af5..0682e7428b 100644 --- a/src/core_modules/capture-core/metaDataStoreLoaders/categories/loadCategories.ts +++ b/src/core_modules/capture-core/metaDataStoreLoaders/categories/loadCategories.ts @@ -53,7 +53,7 @@ function getCategoryOptionQuery(categoryIds: Array) { return { resource: 'categoryOptions', params: (variables: QueryVariables) => ({ - fields: 'id,displayName,categories~pluck, organisationUnits~pluck, access[*]', + fields: 'id,displayName,categories~pluck,organisationUnits~pluck,access[data[write]]', filter: [ `categories.id:in:[${categoryIds.join(',')}]`, 'access.data.read:in:[true]', From 8df561b91168e3943c2e4526d6fde96baf09e85b Mon Sep 17 00:00:00 2001 From: henrikmv Date: Wed, 13 May 2026 16:31:11 +0200 Subject: [PATCH 06/18] feat: temp --- src/components/AppLoader/init.ts | 7 +- .../withInternalFilterHandler/scopes.const.ts | 6 - ...withImplicitRootsInternalFilterHandler.tsx | 42 ------ .../withInternalFilterHandler.tsx | 122 ------------------ .../components/FormFields/New/index.ts | 6 - .../ComposedRegUnitSelector.component.tsx | 10 +- .../ComposedRegUnitSelector.component.tsx | 10 +- .../WidgetProfile/WidgetProfile.component.tsx | 18 +-- .../components/WidgetProfile/hooks/index.ts | 1 - .../WidgetProfile/hooks/useUserRoles.ts | 36 ------ .../epics/getEventListData.ts | 14 +- .../epics/initEventWorkingList.ts | 1 - .../epics/updateEventWorkingList.ts | 1 - .../utils/userInfo/CurrentUser.ts | 2 + 14 files changed, 28 insertions(+), 248 deletions(-) delete mode 100644 src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/scopes.const.ts delete mode 100644 src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withImplicitRootsInternalFilterHandler.tsx delete mode 100644 src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx delete mode 100644 src/core_modules/capture-core/components/WidgetProfile/hooks/useUserRoles.ts diff --git a/src/components/AppLoader/init.ts b/src/components/AppLoader/init.ts index 6760ba7ab3..b674c76827 100644 --- a/src/components/AppLoader/init.ts +++ b/src/components/AppLoader/init.ts @@ -149,7 +149,7 @@ export async function initializeAsync({ const meResponse = await querySingleResource({ resource: 'me', params: { - fields: 'id,firstName,surname,username,userRoles,userGroups,' + + fields: 'id,firstName,surname,username,userRoles[id],userGroups[id],' + 'organisationUnits[id,path],teiSearchOrganisationUnits[id,path],settings', }, }); @@ -159,8 +159,8 @@ export async function initializeAsync({ firstName = '', surname = '', username = '', - userRoles, - userGroups, + userRoles = [], + userGroups = [], organisationUnits: captureScope = [], teiSearchOrganisationUnits: searchScope = [], settings: userSettings, @@ -172,6 +172,7 @@ export async function initializeAsync({ surname, username, uiLocale: userSettings?.keyUiLocale ?? '', + userRoles: userRoles.map((role: { id: string }) => role.id), organisationUnits: captureScope, teiSearchOrganisationUnits: searchScope, }); diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/scopes.const.ts b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/scopes.const.ts deleted file mode 100644 index 0f04908e4f..0000000000 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/scopes.const.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const orgUnitFieldScopes = { - USER_CAPTURE: 'USER_CAPTURE', - USER_SEARCH: 'USER_SEARCH', -} as const; - -export type OrgUnitFieldScope = typeof orgUnitFieldScopes[keyof typeof orgUnitFieldScopes]; diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withImplicitRootsInternalFilterHandler.tsx b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withImplicitRootsInternalFilterHandler.tsx deleted file mode 100644 index 12ed72a645..0000000000 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withImplicitRootsInternalFilterHandler.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import * as React from 'react'; -import { withInternalFilterHandler } from './withInternalFilterHandler'; -import { get as getOrgUnitRoots } from '../orgUnitRoots.store'; -import { orgUnitFieldScopes } from './scopes.const'; -import { withApiUtils } from '../../../../../../HOC'; -import type { OrgUnitFieldScope } from './scopes.const'; - -type Props = { - scope: OrgUnitFieldScope; -}; - -// Wraps withInternalFilterHandler. Passes on defaultRoots from the organisation unit store based on the input scope. -export const withOrgUnitFieldImplicitRootsFilterHandler = () => -

>(InnerComponent: React.ComponentType

) => { - const InternalFilterHandlerHOC = withApiUtils(withInternalFilterHandler()(InnerComponent)); - - class OrgUnitImplicitInternalFilterHandlerHOC extends React.Component { - defaultRoots: Array; - constructor(props: Props & P) { - super(props); - const { scope } = this.props; - this.defaultRoots = - getOrgUnitRoots(OrgUnitImplicitInternalFilterHandlerHOC.DEFAULT_ROOTS_DATA[scope]) || []; - } - - static DEFAULT_ROOTS_DATA = { - [orgUnitFieldScopes.USER_CAPTURE]: 'captureRoots', - [orgUnitFieldScopes.USER_SEARCH]: 'search', - }; - - render() { - const { ...passOnProps } = this.props; - return ( - - ); - } - } - return OrgUnitImplicitInternalFilterHandlerHOC; - }; diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx deleted file mode 100644 index 020ac825d9..0000000000 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import * as React from 'react'; -import log from 'loglevel'; -import { makeCancelablePromise, errorCreator } from 'capture-core-utils'; -import { orgUnitFieldScopes } from './scopes.const'; -import type { QuerySingleResource } from '../../../../../../utils/api/api.types'; -import type { OrgUnitFieldScope } from './scopes.const'; - -type Props = { - defaultRoots: Array; - scope: OrgUnitFieldScope; - onSearchError?: (message: string) => void; - onSelect: (value: any) => void; - querySingleResource: QuerySingleResource; -}; - -type State = { - filteredRoots: Array | null; - filterText: string | null; - inProgress: boolean; - treeKey: string; -}; - -// Handles organisation unit filtering internally in this component. -export const withInternalFilterHandler = () => - (InnerComponent: React.ComponentType) => - class OrgUnitInternalFilterHandlerHOC extends React.Component { - cancelablePromise: any; - constructor(props: Props) { - super(props); - this.state = { - filteredRoots: null, - filterText: null, - inProgress: false, - treeKey: OrgUnitInternalFilterHandlerHOC.INITIAL_TREE_KEY, - }; - this.cancelablePromise = null; - } - - componentWillUnmount() { - if (this.cancelablePromise) { - this.cancelablePromise.cancel(); - } - } - - static INITIAL_TREE_KEY = 'initial'; - - filterOrgUnits(filterText: string) { - const { scope, onSearchError, querySingleResource } = this.props; - const hierarchyProp = scope === orgUnitFieldScopes.USER_CAPTURE - ? { withinUserHierarchy: true } - : { withinUserSearchHierarchy: true }; - this.setState({ - inProgress: true, - }); - - if (this.cancelablePromise) { - this.cancelablePromise.cancel(); - } - - const cancelablePromise = makeCancelablePromise( - querySingleResource({ - resource: 'organisationUnits', - params: { - fields: 'id,path', - paging: false, - query: filterText, - ...hierarchyProp, - }, - }), - ); - - cancelablePromise - .promise - .then((orgUnitCollection: Record) => { - this.setState({ - filteredRoots: orgUnitCollection.toArray(), - filterText, - inProgress: false, - treeKey: filterText, - }); - this.cancelablePromise = null; - }) - .catch((error) => { - log.error(errorCreator('There was an error retrieving organisation unit roots')({ error })); - onSearchError && onSearchError(error); - }); - - this.cancelablePromise = cancelablePromise; - } - - resetOrgUnits() { - this.setState({ - filteredRoots: null, - filterText: null, - treeKey: OrgUnitInternalFilterHandlerHOC.INITIAL_TREE_KEY, - }); - } - - handleFilterChange = (searchText: string) => { - if (searchText) { - this.filterOrgUnits(searchText); - } else { - this.resetOrgUnits(); - } - } - - render() { - const { defaultRoots, onSearchError, onSelect, scope, ...passOnProps } = this.props; - const { filteredRoots, filterText, treeKey, inProgress } = this.state; - return ( - - ); - } - }; diff --git a/src/core_modules/capture-core/components/FormFields/New/index.ts b/src/core_modules/capture-core/components/FormFields/New/index.ts index fac9466ce5..3c4037971f 100644 --- a/src/core_modules/capture-core/components/FormFields/New/index.ts +++ b/src/core_modules/capture-core/components/FormFields/New/index.ts @@ -35,11 +35,5 @@ export { withFocusSaver, withInternalChangeHandler } from 'capture-ui'; export { withCenterPoint } from './HOC/withCenterPoint'; export { withConditionalTooltip } from './HOC/withConditionalTooltip'; -// OrgUnit HOCs -export { - withOrgUnitFieldImplicitRootsFilterHandler, -} from './Fields/OrgUnitField/withInternalFilterHandler/withImplicitRootsInternalFilterHandler'; -export { orgUnitFieldScopes } from './Fields/OrgUnitField/withInternalFilterHandler/scopes.const'; - // Constants export { orientations } from 'capture-ui'; diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx index 4710e6d265..f5d98e2948 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx @@ -6,13 +6,8 @@ import { withInternalChangeHandler, withFilterProps, SingleOrgUnitSelectField, - withOrgUnitFieldImplicitRootsFilterHandler, - orgUnitFieldScopes, } from '../../../../../FormFields/New'; -const OrgUnitFieldImplicitRootsFilterHandlerHOC = - withOrgUnitFieldImplicitRootsFilterHandler()(SingleOrgUnitSelectField); - type Props = { onUpdateSelectedOrgUnit: (orgUnit?: OrgUnit | null) => void; }; @@ -21,9 +16,8 @@ class OrgUnitFieldWrapper extends React.Component { render() { const { onUpdateSelectedOrgUnit, ...passOnProps } = this.props; return ( - diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx index fef577ec7a..ba6a4e35d2 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx @@ -5,21 +5,15 @@ import { withInternalChangeHandler, withFilterProps, SingleOrgUnitSelectField, - withOrgUnitFieldImplicitRootsFilterHandler, - orgUnitFieldScopes, } from '../../../../../../FormFields/New'; import type { ComposedRegUnitSelectorProps } from './RegUnitSelector.types'; -const OrgUnitFieldImplicitRootsFilterHandlerHOC = - withOrgUnitFieldImplicitRootsFilterHandler()(SingleOrgUnitSelectField); - class OrgUnitFieldWrapper extends React.Component { render() { const { onUpdateSelectedOrgUnit, ...passOnProps } = this.props; return ( - 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 c55a864f36..dde661bf74 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'; @@ -56,15 +56,13 @@ const showEditModal = (loading: boolean, error: any, showEdit: boolean, modalSta const computeLoadingState = ( programsLoading: boolean, trackedEntityInstancesLoading: boolean, - userRolesLoading: boolean, configIsFetched: boolean, -) => programsLoading || trackedEntityInstancesLoading || userRolesLoading || !configIsFetched; +) => programsLoading || trackedEntityInstancesLoading || !configIsFetched; const computeError = ( programsError: any, trackedEntityInstancesError: any, - userRolesError: any, -) => programsError || trackedEntityInstancesError || userRolesError; +) => programsError || trackedEntityInstancesError; const WidgetProfilePlain = ({ teiId, @@ -95,11 +93,7 @@ const WidgetProfilePlain = ({ trackedEntityTypeAccess, geometry, } = useTrackedEntityInstances(teiId, programId, storedAttributeValues, storedGeometry); - const { - loading: userRolesLoading, - error: userRolesError, - userRoles, - } = useUserRoles(); + const userRoles = CurrentUser.get().userRoles; const hasNoAttributes = !program?.programTrackedEntityAttributes?.length; @@ -109,8 +103,8 @@ const WidgetProfilePlain = ({ !readOnlyMode, [hasNoAttributes, readOnlyMode, trackedEntityTypeAccess]); - const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, userRolesLoading, configIsFetched); - const error = computeError(programsError, trackedEntityInstancesError, userRolesError); + const loading = computeLoadingState(programsLoading, trackedEntityInstancesLoading, configIsFetched); + const error = computeError(programsError, trackedEntityInstancesError); 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 9796657555..d5fc613397 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/index.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/index.ts @@ -1,5 +1,4 @@ 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 deleted file mode 100644 index 4a5e9d10f3..0000000000 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useUserRoles.ts +++ /dev/null @@ -1,36 +0,0 @@ -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 }; -}; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts index dd0d380642..032b03b9ce 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts @@ -7,6 +7,12 @@ type InputQueryArgs = { [key: string]: any, }; +const LISTING_FIELDS = + 'event,status,program,programType,occurredAt,orgUnit,assignedUser,' + + 'updatedAt,createdAt,dataValues[dataElement,value]'; + +const DOWNLOAD_FIELDS = 'dataValues,occurredAt,event,status,orgUnit,program,programType,updatedAt,createdAt,assignedUser'; + const mapArgumentNameFromClientToServer = { programId: 'program', programStageId: 'programStage', @@ -195,8 +201,9 @@ export const getEventListData = async ({ querySingleResource: QuerySingleResource, }) => { const mainColumns = getMainColumns(columnsMetaForDataFetching); + const apiQueryArgs = createApiQueryArgs(queryArgs, mainColumns, categoryCombinationId); const { eventContainers, pagingData, request } = await getEvents( - createApiQueryArgs(queryArgs, mainColumns, categoryCombinationId), + { ...apiQueryArgs, fields: LISTING_FIELDS }, absoluteApiPath, querySingleResource, ); @@ -214,6 +221,9 @@ export const getEventListData = async ({ return { eventContainers: columnFilteredEventContainers, pagingData, - request, + request: { + ...request, + queryParams: { ...request.queryParams, fields: DOWNLOAD_FIELDS }, + }, }; }; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/initEventWorkingList.ts b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/initEventWorkingList.ts index a94c7e5c60..0aa3e9d96c 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/initEventWorkingList.ts +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/initEventWorkingList.ts @@ -40,7 +40,6 @@ export const initEventWorkingListAsync = async ( sortById, sortByDirection, filters: buildFilterQueryArgs(filters, { columns: columnsMetaForDataFetching, storeId, isInit: true }), - fields: 'dataValues,occurredAt,event,status,orgUnit,program,programType,updatedAt,createdAt,assignedUser,', ...commonQueryData, }; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/updateEventWorkingList.ts b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/updateEventWorkingList.ts index d156913a69..4ab8e7f0f1 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/updateEventWorkingList.ts +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/updateEventWorkingList.ts @@ -31,7 +31,6 @@ export const updateEventWorkingListAsync = ( ): Promise => { const rawQueryArgs = { ...queryArgsSource, - fields: 'dataValues,occurredAt,event,status,orgUnit,program,programType,updatedAt,createdAt,assignedUser', filters: buildFilterQueryArgs(queryArgsSource.filters, { columns: columnsMetaForDataFetching, storeId, diff --git a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts index 2b50970b19..c59e249ae7 100644 --- a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts +++ b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts @@ -9,6 +9,7 @@ export type CurrentUserType = { surname: string; username: string; uiLocale: string; + userRoles: string[]; organisationUnits: OrgUnitRef[]; teiSearchOrganisationUnits: OrgUnitRef[]; }; @@ -20,6 +21,7 @@ export class CurrentUser { surname: '', username: '', uiLocale: '', + userRoles: [], organisationUnits: [], teiSearchOrganisationUnits: [], }; From 7a8491c9b8b3927578784a3d8ddfc2b9c39174e6 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Wed, 13 May 2026 17:09:32 +0200 Subject: [PATCH 07/18] feat: update fields in various queries to limit request --- .../components/WidgetEnrollment/hooks/useEnrollment.ts | 2 +- .../common/RelationshipsWidget/useRelationshipTypes.ts | 2 +- .../WorkingLists/EventWorkingLists/epics/getEventListData.ts | 3 ++- .../teiViewEpics/helpers/getEventListData/getEventListData.ts | 4 +++- .../programs/quickStoreOperations/storeProgramIndicators.ts | 4 ++-- .../programs/quickStoreOperations/storeProgramRules.ts | 2 +- .../programs/quickStoreOperations/storePrograms.ts | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useEnrollment.ts b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useEnrollment.ts index b495988ec4..121b44d133 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useEnrollment.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useEnrollment.ts @@ -27,7 +27,7 @@ export const useEnrollment = ({ id: ({ variables: { enrollmentId: updatedEnrollmentId } }: any) => updatedEnrollmentId, params: { fields: 'enrollment,trackedEntity,program,status,orgUnit,enrolledAt,' + - 'occurredAt,followUp,deleted,createdBy,updatedBy,updatedAt,geometry', + 'occurredAt,followUp,deleted,updatedAt,geometry', }, }, }), diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/useRelationshipTypes.ts b/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/useRelationshipTypes.ts index 11eff52045..c984024875 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/useRelationshipTypes.ts +++ b/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/useRelationshipTypes.ts @@ -19,7 +19,7 @@ const relationshipTypesQuery = { resource: 'relationshipTypes', params: { filter: 'access.data.read:eq:true', - fields: 'id,displayName,fromToName,toFromName,access[data[read,write],read,write],' + + fields: 'id,displayName,fromToName,toFromName,access[data[read,write]],' + 'fromConstraint[relationshipEntity,trackerDataView,trackedEntityType[id,name],' + 'program[id,name],programStage[id,name]],' + 'toConstraint[relationshipEntity,trackerDataView,trackedEntityType[id,name],' + diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts index 032b03b9ce..722e5f0d19 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts @@ -8,7 +8,8 @@ type InputQueryArgs = { }; const LISTING_FIELDS = - 'event,status,program,programType,occurredAt,orgUnit,assignedUser,' + 'event,status,program,programType,occurredAt,orgUnit,' + + 'assignedUser[uid,username,firstName,surname],' + 'updatedAt,createdAt,dataValues[dataElement,value]'; const DOWNLOAD_FIELDS = 'dataValues,occurredAt,event,status,orgUnit,program,programType,updatedAt,createdAt,assignedUser'; diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts index bbeb8d4cef..beadf02b15 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts @@ -18,7 +18,9 @@ import { getScheduledDateQueryArgs } from './getScheduledDateQueryArgs'; const LISTING_FIELDS = 'event,status,program,programStage,enrollment,trackedEntity,' - + 'occurredAt,scheduledAt,orgUnit,assignedUser,dataValues[dataElement,value]'; + + 'occurredAt,scheduledAt,orgUnit,' + + 'assignedUser[uid,username,firstName,surname],' + + 'dataValues[dataElement,value]'; const DOWNLOAD_FIELDS = '*'; diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storeProgramIndicators.ts b/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storeProgramIndicators.ts index 7aa6909185..33d24e977b 100644 --- a/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storeProgramIndicators.ts +++ b/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storeProgramIndicators.ts @@ -12,8 +12,8 @@ const convert = (response) => { })); }; -const fieldsParam = 'id,name,displayName,code,shortName,style,expression,' + -'displayDescription,description,filter,program[id]'; +const fieldsParam = 'id,name,displayName,style,expression,' + +'description,filter,program[id]'; export const storeProgramIndicators = async (programIds: Array) => { const query = { diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storeProgramRules.ts b/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storeProgramRules.ts index b86f4ee37b..1c6adb9608 100644 --- a/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storeProgramRules.ts +++ b/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storeProgramRules.ts @@ -62,7 +62,7 @@ const convert = (() => { }; })(); -const fieldsParam = 'id,displayName,condition,description,program[id],programStage[id],priority,' + +const fieldsParam = 'id,displayName,condition,program[id],programStage[id],priority,' + 'programRuleActions[id,content,displayContent,location,data,priority,programRuleActionType,programStageSection[id],' + 'dataElement[id],trackedEntityAttribute[id],programStage[id],optionGroup[id],option[id],legendSet[id]]'; diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts b/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts index d64b3ab8f6..58874777ef 100644 --- a/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts +++ b/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts @@ -146,7 +146,7 @@ const fieldsParam = [ 'expiryDays', 'dataEntryForm[id,htmlCode]', 'displayIncidentDate', - 'access[*]', + 'access[data[read,write]]', 'trackedEntityType[id]', 'categoryCombo[id,displayName,isDefault,categories[id,displayName]]', 'userRoles[id,displayName]', From 016ef046fb908f606bd44721cc36fce894c904ab Mon Sep 17 00:00:00 2001 From: henrikmv Date: Thu, 14 May 2026 09:47:17 +0200 Subject: [PATCH 08/18] fix: limit fields --- src/components/AppLoader/init.ts | 1 - .../FormFields/OrgUnitTree/OrgUnitTree.component.tsx | 2 +- .../WorkingLists/EventWorkingLists/epics/getEventListData.ts | 4 ++-- .../teiViewEpics/helpers/getEventListData/getEventListData.ts | 2 +- src/core_modules/capture-core/utils/userInfo/CurrentUser.ts | 2 -- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/AppLoader/init.ts b/src/components/AppLoader/init.ts index b674c76827..197a8e5b33 100644 --- a/src/components/AppLoader/init.ts +++ b/src/components/AppLoader/init.ts @@ -167,7 +167,6 @@ export async function initializeAsync({ } = meResponse; CurrentUser.set({ - id: currentUserId, firstName, surname, username, diff --git a/src/core_modules/capture-core/components/FormFields/OrgUnitTree/OrgUnitTree.component.tsx b/src/core_modules/capture-core/components/FormFields/OrgUnitTree/OrgUnitTree.component.tsx index 4053168d05..8aa0d37ad8 100644 --- a/src/core_modules/capture-core/components/FormFields/OrgUnitTree/OrgUnitTree.component.tsx +++ b/src/core_modules/capture-core/components/FormFields/OrgUnitTree/OrgUnitTree.component.tsx @@ -90,7 +90,7 @@ export class OrgUnitTree extends React.Component { params: { paging: false, filter: `id:in:[${id}]`, - fields: 'children[id,displayName,path,children]', + fields: 'children[displayName,path,children]', }, }).then((r) => { diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts index 722e5f0d19..70273972e6 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts @@ -8,9 +8,9 @@ type InputQueryArgs = { }; const LISTING_FIELDS = - 'event,status,program,programType,occurredAt,orgUnit,' + 'event,status,occurredAt,orgUnit,' + 'assignedUser[uid,username,firstName,surname],' - + 'updatedAt,createdAt,dataValues[dataElement,value]'; + + 'dataValues[dataElement,value]'; const DOWNLOAD_FIELDS = 'dataValues,occurredAt,event,status,orgUnit,program,programType,updatedAt,createdAt,assignedUser'; diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts index beadf02b15..b06c6f3fde 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts @@ -17,7 +17,7 @@ import { getColumnsQueryArgs } from './getColumnsQueryArgs'; import { getScheduledDateQueryArgs } from './getScheduledDateQueryArgs'; const LISTING_FIELDS = - 'event,status,program,programStage,enrollment,trackedEntity,' + 'event,status,program,enrollment,trackedEntity,' + 'occurredAt,scheduledAt,orgUnit,' + 'assignedUser[uid,username,firstName,surname],' + 'dataValues[dataElement,value]'; diff --git a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts index c59e249ae7..0e0e9be6ae 100644 --- a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts +++ b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts @@ -4,7 +4,6 @@ export type OrgUnitRef = { }; export type CurrentUserType = { - id: string; firstName: string; surname: string; username: string; @@ -16,7 +15,6 @@ export type CurrentUserType = { export class CurrentUser { private static currentData: CurrentUserType = { - id: '', firstName: '', surname: '', username: '', From 37bad8b38d761c45fadb2d09d19c67e3cf5236d6 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Thu, 14 May 2026 10:02:07 +0200 Subject: [PATCH 09/18] fix: limit params --- .../components/SearchBox/SearchForm/SearchForm.epics.ts | 4 ++-- .../WidgetTwoEventWorkspace/hooks/useLinkedEventByOriginId.ts | 2 +- .../teiViewEpics/helpers/getEventListData/getEventListData.ts | 2 +- .../baseLoader/quickStoreOperations/storeRelationshipTypes.ts | 2 +- .../programs/quickStoreOperations/storePrograms.ts | 2 -- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/core_modules/capture-core/components/SearchBox/SearchForm/SearchForm.epics.ts b/src/core_modules/capture-core/components/SearchBox/SearchForm/SearchForm.epics.ts index fd6a176934..d4cb70f304 100644 --- a/src/core_modules/capture-core/components/SearchBox/SearchForm/SearchForm.epics.ts +++ b/src/core_modules/capture-core/components/SearchBox/SearchForm/SearchForm.epics.ts @@ -206,7 +206,7 @@ export const searchViaUniqueIdOnScopeTrackedEntityTypeEpic = ( trackedEntityType: trackedEntityTypeId, pageNumber: 1, [orgUnitModeQueryParam]: 'ACCESSIBLE', - fields: 'trackedEntity,trackedEntityType,orgUnit,attributes,enrollments', + fields: 'trackedEntity,orgUnit,attributes,enrollments', }; const attributes = getTrackedEntityTypeThrowIfNotFound(trackedEntityTypeId).attributes; @@ -373,7 +373,7 @@ export const fallbackSearchEpic = ( page, pageSize, [orgUnitModeQueryParam]: 'ACCESSIBLE', - fields: 'trackedEntity,trackedEntityType,orgUnit,attributes,enrollments,', + fields: 'trackedEntity,orgUnit,attributes,enrollments', }; return from(getTrackedEntityInstances(queryArgs, attributes, absoluteApiPath, querySingleResource)).pipe( diff --git a/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/hooks/useLinkedEventByOriginId.ts b/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/hooks/useLinkedEventByOriginId.ts index 887fef5369..2a9fac2dba 100644 --- a/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/hooks/useLinkedEventByOriginId.ts +++ b/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/hooks/useLinkedEventByOriginId.ts @@ -45,7 +45,7 @@ export const useLinkedEventByOriginId = ({ originEventId, skipBidirectionalCheck params: { fields: 'event,relationships[relationship,relationshipType,relationshipName,bidirectional,' + 'from[event[event,dataValues,occurredAt,scheduledAt,status,orgUnit,programStage,program]],' + - 'to[event[event,dataValues,*,occurredAt,scheduledAt,status,orgUnit,programStage,program]]' + + 'to[event[event,dataValues,occurredAt,scheduledAt,status,orgUnit,programStage,program]]' + ']', }, }), [originEventId]); diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts index b06c6f3fde..e23e5acae3 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/epics/teiViewEpics/helpers/getEventListData/getEventListData.ts @@ -71,7 +71,7 @@ const createApiTEIsQueryArgs = [filterQueryParam]: trackedEntityIds, fields: 'trackedEntity,createdAt,attributes[attribute,value],' + - 'programOwners[orgUnit],enrollments[enrollment,status,orgUnit,enrolledAt]', + 'programOwners[orgUnit],enrollments[enrollment]', }; }; diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/baseLoader/quickStoreOperations/storeRelationshipTypes.ts b/src/core_modules/capture-core/metaDataStoreLoaders/baseLoader/quickStoreOperations/storeRelationshipTypes.ts index 259e8aa8d1..82a83675c6 100644 --- a/src/core_modules/capture-core/metaDataStoreLoaders/baseLoader/quickStoreOperations/storeRelationshipTypes.ts +++ b/src/core_modules/capture-core/metaDataStoreLoaders/baseLoader/quickStoreOperations/storeRelationshipTypes.ts @@ -9,7 +9,7 @@ export const storeRelationshipTypes = () => { 'fromConstraint[relationshipEntity,trackerDataView,trackedEntityType[id,name],' + 'program[id,name],programStage[id,name,program[id]]],' + 'toConstraint[relationshipEntity,trackerDataView,trackedEntityType[id,name],' + - 'program[id,name],programStage[id,name,program[id]]],access[*],referral', + 'program[id,name],programStage[id,name,program[id]]],access[*]', }, }; diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts b/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts index 58874777ef..f36ad26c90 100644 --- a/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts +++ b/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts @@ -129,7 +129,6 @@ const fieldsParam = [ 'id', 'displayName', 'displayShortName', - 'description', 'programType', 'style', 'displayFrontPageList', @@ -149,7 +148,6 @@ const fieldsParam = [ 'access[data[read,write]]', 'trackedEntityType[id]', 'categoryCombo[id,displayName,isDefault,categories[id,displayName]]', - 'userRoles[id,displayName]', `programStages[${programStageFields}]`, 'programSections[id, displayDescription, displayFormName, sortOrder, trackedEntityAttributes]', `programTrackedEntityAttributes[${programTrackedEntityAttributeFields}]`, From cc2a951be07b0c2ef4cea46b0dd084082d18d709 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Thu, 14 May 2026 10:16:40 +0200 Subject: [PATCH 10/18] fix: ensure CurrentUser is initialized before access --- .../capture-core/utils/userInfo/CurrentUser.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts index 0e0e9be6ae..eb8be00328 100644 --- a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts +++ b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts @@ -14,20 +14,15 @@ export type CurrentUserType = { }; export class CurrentUser { - private static currentData: CurrentUserType = { - firstName: '', - surname: '', - username: '', - uiLocale: '', - userRoles: [], - organisationUnits: [], - teiSearchOrganisationUnits: [], - }; + private static currentData: CurrentUserType | null = null; static set(data: CurrentUserType) { CurrentUser.currentData = data; } - static get() { + static get(): CurrentUserType { + if (!CurrentUser.currentData) { + throw new Error('CurrentUser accessed before initialization'); + } return CurrentUser.currentData; } } From 14ec26876d59c6482c449f55a0a8f51fa85679df Mon Sep 17 00:00:00 2001 From: henrikmv Date: Thu, 14 May 2026 10:30:04 +0200 Subject: [PATCH 11/18] feat: revert changes for me resource --- src/components/AppLoader/init.ts | 33 ++------ .../epics/addNoteForNewSingleEvent.epics.ts | 41 ++++++---- .../useMetadataForProgramStage.ts | 4 +- .../useMetadataForRegistrationForm.ts | 4 +- .../OrgUnitField/OrgUnitField.component.tsx | 20 ++++- .../ViewEvent/Notes/viewEventNotes.epics.ts | 49 +++++++----- .../SearchBox/hooks/useSearchOption.ts | 4 +- .../useSearchScopeWithFallback.ts | 28 +++++-- .../WidgetEnrollment.container.tsx | 12 +-- .../DataEntry/epics/dataEntryNote.epics.ts | 41 ++++++---- .../WidgetEnrollmentNote.epics.ts | 56 +++++++------ .../WidgetEventNote/WidgetEventNote.epics.ts | 80 ++++++++++--------- .../WidgetEventSchedule.container.tsx | 26 +++--- .../WidgetEventSchedule/hooks/index.ts | 2 + .../hooks/useNoteDetails.ts | 22 +++++ .../WidgetProfile/WidgetProfile.component.tsx | 18 +++-- .../components/WidgetProfile/hooks/index.ts | 1 + .../WidgetProfile/hooks/useUserRoles.ts | 36 +++++++++ ...useBulkDataEntryDatastoreConfigurations.ts | 4 +- .../utils/localeData/useUserLocale.ts | 29 +++++++ .../utils/userInfo/CurrentUser.ts | 28 ------- 21 files changed, 325 insertions(+), 213 deletions(-) create mode 100644 src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts create mode 100644 src/core_modules/capture-core/components/WidgetProfile/hooks/useUserRoles.ts create mode 100644 src/core_modules/capture-core/utils/localeData/useUserLocale.ts delete mode 100644 src/core_modules/capture-core/utils/userInfo/CurrentUser.ts diff --git a/src/components/AppLoader/init.ts b/src/components/AppLoader/init.ts index 197a8e5b33..f8cdaefc13 100644 --- a/src/components/AppLoader/init.ts +++ b/src/components/AppLoader/init.ts @@ -3,7 +3,6 @@ import { environments } from 'capture-core/constants/environments'; import moment from 'moment'; import { CurrentLocaleData } from 'capture-core/utils/localeData/CurrentLocaleData'; import type { LocaleDataType } from 'capture-core/utils/localeData/CurrentLocaleData'; -import { CurrentUser } from 'capture-core/utils/userInfo/CurrentUser'; import i18n from '@dhis2/d2-i18n'; import { loadMetaData, cacheSystemSettings } from 'capture-core/metaDataStoreLoaders'; import { buildMetaDataAsync, buildSystemSettingsAsync } from 'capture-core/metaDataMemoryStoreBuilders'; @@ -146,34 +145,18 @@ export async function initializeAsync({ }) { setLogLevel(); - const meResponse = await querySingleResource({ - resource: 'me', - params: { - fields: 'id,firstName,surname,username,userRoles[id],userGroups[id],' + - 'organisationUnits[id,path],teiSearchOrganisationUnits[id,path],settings', - }, - }); - const { id: currentUserId, - firstName = '', - surname = '', - username = '', - userRoles = [], - userGroups = [], - organisationUnits: captureScope = [], - teiSearchOrganisationUnits: searchScope = [], - settings: userSettings, - } = meResponse; - - CurrentUser.set({ - firstName, - surname, - username, - uiLocale: userSettings?.keyUiLocale ?? '', - userRoles: userRoles.map((role: { id: string }) => role.id), + userRoles, + userGroups, organisationUnits: captureScope, teiSearchOrganisationUnits: searchScope, + settings: userSettings, + } = await querySingleResource({ + resource: 'me', + params: { + fields: 'id,userRoles,userGroups,organisationUnits,teiSearchOrganisationUnits,settings', + }, }); const systemSettings = await querySingleResource({ diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts index 9ee62255b6..6b8e93764b 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts @@ -1,9 +1,8 @@ import uuid from 'd2-utilizr/lib/uuid'; import { ofType } from 'redux-observable'; -import { map } from 'rxjs/operators'; +import { switchMap } from 'rxjs/operators'; import moment from 'moment'; import type { ReduxStore, ApiUtils, EpicAction } from 'capture-core-utils/types'; -import { CurrentUser } from '../../../../../../utils/userInfo/CurrentUser'; import { actionTypes as newEventDataEntryActionTypes, } from '../actions/dataEntry.actions'; @@ -21,25 +20,33 @@ type AddNotePayload = { export const addNoteForNewSingleEventEpic = ( action$: EpicAction, store: ReduxStore, - { fromClientDate }: ApiUtils, + { querySingleResource, fromClientDate }: ApiUtils, ) => action$.pipe( ofType(newEventDataEntryActionTypes.ADD_NEW_EVENT_NOTE), - map((action) => { + switchMap((action) => { const payload = action.payload; - const { firstName, surname, username } = CurrentUser.get(); - const clientId = uuid(); - const note = { - value: payload.note, - createdBy: { - firstName, - surname, - uid: clientId, + + return querySingleResource({ + resource: 'me', + params: { + fields: 'firstName,surname,userName', }, - storedBy: username, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - clientId: uuid(), - }; + }).then((user) => { + const { userName, firstName, surname } = user; + const clientId = uuid(); + const note = { + value: payload.note, + createdBy: { + firstName, + surname, + uid: clientId, + }, + storedBy: userName, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + clientId: uuid(), + }; - return addNote(payload.dataEntryId, payload.itemId, note); + return addNote(payload.dataEntryId, payload.itemId, note); + }); })); diff --git a/src/core_modules/capture-core/components/DataEntries/common/ProgramStage/useMetadataForProgramStage.ts b/src/core_modules/capture-core/components/DataEntries/common/ProgramStage/useMetadataForProgramStage.ts index 2b5b52b77b..c9b7468ba6 100644 --- a/src/core_modules/capture-core/components/DataEntries/common/ProgramStage/useMetadataForProgramStage.ts +++ b/src/core_modules/capture-core/components/DataEntries/common/ProgramStage/useMetadataForProgramStage.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { useConfig } from '@dhis2/app-runtime'; import { useProgramFromIndexedDB } from '../../../../utils/cachedDataHooks/useProgramFromIndexedDB'; -import { CurrentUser } from '../../../../utils/userInfo/CurrentUser'; +import { useUserLocale } from '../../../../utils/localeData/useUserLocale'; import { useDataEntryFormConfig, useOptionSetsForAttributes } from '../TEIAndEnrollment'; import { useDataElementsForStage } from './useDataElementsForStage'; import { useIndexedDBQuery } from '../../../../utils/reactQueryHelpers'; @@ -42,7 +42,7 @@ export const useMetadataForProgramStage = ({ }: Props): ReturnType => { const scopeId = stageId || programId; const { program } = useProgramFromIndexedDB(programId, { enabled: !!programId }); - const locale = CurrentUser.get().uiLocale; + const { locale } = useUserLocale(); const { serverVersion } = useConfig(); const minor = serverVersion?.minor; const { diff --git a/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/useMetadataForRegistrationForm.ts b/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/useMetadataForRegistrationForm.ts index 4a1eeb3b1c..6ce2a8a009 100644 --- a/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/useMetadataForRegistrationForm.ts +++ b/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/useMetadataForRegistrationForm.ts @@ -6,7 +6,7 @@ import { useScopeInfo } from '../../../../../hooks/useScopeInfo'; import { useTrackedEntityTypeCollection } from './hooks/useTrackedEntityTypeCollection'; import { useEnrollmentFormFoundation } from './hooks/useEnrollmentFormFoundation'; import { useTrackedEntityTypeFromIndexedDB } from '../../../../../utils/cachedDataHooks/useTrackedEntityTypeFromIndexedDB'; -import { CurrentUser } from '../../../../../utils/userInfo/CurrentUser'; +import { useUserLocale } from '../../../../../utils/localeData/useUserLocale'; import { useTrackedEntityAttributes } from './hooks/useTrackedEntityAttributes'; import { useDataEntryFormConfig } from './hooks/useDataEntryFormConfig'; @@ -25,7 +25,7 @@ export const useMetadataForRegistrationForm = ({ selectedScopeId }: Props) => { const { program } = useProgramFromIndexedDB(selectedScopeId, { enabled: !!(scopeType === scopeTypes.TRACKER_PROGRAM && selectedScopeId), }); - const locale = CurrentUser.get().uiLocale; + const { locale } = useUserLocale(); const { trackedEntityType } = useTrackedEntityTypeFromIndexedDB(tetId, { enabled: !!tetId }); const cachedTrackedEntityAttributeIds = useMemo(() => { diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx index 2b8f6b9ce9..8e81696f28 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/OrgUnitField.component.tsx @@ -4,7 +4,6 @@ import i18n from '@dhis2/d2-i18n'; import { useDataQuery } from '@dhis2/app-runtime'; import { withStyles, type WithStyles } from 'capture-core-utils/styles'; import { DebounceField } from 'capture-ui'; -import { CurrentUser } from '../../../../../utils/userInfo/CurrentUser'; import { OrgUnitTree } from './OrgUnitTree.component'; const getStyles = () => ({ @@ -64,7 +63,20 @@ const OrgUnitFieldPlain = (props: Props) => { const [searchText, setSearchText] = React.useState(undefined); const [key, setKey] = React.useState(undefined); - const initialRoots = React.useMemo(() => CurrentUser.get().organisationUnits, []); + const { loading, data } = useDataQuery( + React.useMemo( + () => ({ + orgUnits: { + resource: 'me', + params: { + fields: ['organisationUnits[id,path]'], + }, + + }, + }), + [], + ), + ); const { loading: searchLoading, data: searchData, refetch: refetchOrg } = useDataQuery( React.useMemo( @@ -85,7 +97,7 @@ const OrgUnitFieldPlain = (props: Props) => { { lazy: true }, ); - const ready = searchText?.length ? !searchLoading : true; + const ready = searchText?.length ? !searchLoading : !loading; React.useEffect(() => { if (searchText?.length) { @@ -107,7 +119,7 @@ const OrgUnitFieldPlain = (props: Props) => { />); } return ( ], viewEventNotesBatchActionTypes.LOAD_EVENT_NOTES_BATCH); })); -export const addNoteForViewEventEpic = (action$: any, store: any, { fromClientDate }: any) => +export const addNoteForViewEventEpic = (action$: any, store: any, { querySingleResource, fromClientDate }: any) => action$.pipe( ofType(viewEventNotesActionTypes.REQUEST_SAVE_EVENT_NOTE), - map((action: any) => { + switchMap((action: any) => { const state = store.value; const payload = action.payload; const eventId = state.viewEventPage.eventId; const useNewEndpoint = featureAvailable(FEATURES.newNoteEndpoint); - const { firstName, surname, username } = CurrentUser.get(); - const clientId = uuid(); - const serverData = createServerData(eventId, payload.note, useNewEndpoint); - const clientNote = { - value: payload.note, - createdBy: { - firstName, - surname, - uid: clientId, + return querySingleResource({ + resource: 'me', + params: { + fields: 'userName,firstName,surname', }, - storedBy: username, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - clientId: uuid(), - }; - return batchActions([ - startSaveEventNote(eventId, serverData, state.currentSelections, clientNote.clientId), - addNote(noteKey, clientNote), - ], viewEventNotesBatchActionTypes.SAVE_EVENT_NOTE_BATCH); + }).then((user: any) => { + const { userName, firstName, surname } = user; + const clientId = uuid(); + const serverData = createServerData(eventId, payload.note, useNewEndpoint); + + const clientNote = { + value: payload.note, + createdBy: { + firstName, + surname, + uid: clientId, + }, + storedBy: userName, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + clientId: uuid(), + }; + return batchActions([ + startSaveEventNote(eventId, serverData, state.currentSelections, clientNote.clientId), + addNote(noteKey, clientNote), + ], viewEventNotesBatchActionTypes.SAVE_EVENT_NOTE_BATCH); + }); })); export const saveNoteForViewEventFailedEpic = (action$: any) => diff --git a/src/core_modules/capture-core/components/SearchBox/hooks/useSearchOption.ts b/src/core_modules/capture-core/components/SearchBox/hooks/useSearchOption.ts index 638789a392..b24fa2c09d 100644 --- a/src/core_modules/capture-core/components/SearchBox/hooks/useSearchOption.ts +++ b/src/core_modules/capture-core/components/SearchBox/hooks/useSearchOption.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useProgramFromIndexedDB } from '../../../utils/cachedDataHooks/useProgramFromIndexedDB'; import { buildSearchOption } from '../../../hooks/useSearchOptions'; import { useTrackedEntityTypeFromIndexedDB } from '../../../utils/cachedDataHooks/useTrackedEntityTypeFromIndexedDB'; -import { CurrentUser } from '../../../utils/userInfo/CurrentUser'; +import { useUserLocale } from '../../../utils/localeData/useUserLocale'; import type { AvailableSearchOption } from '../SearchBox.types'; import type { SearchGroup } from '../../../metaData/SearchGroup/SearchGroup'; import { useIndexedDBQuery } from '../../../utils/reactQueryHelpers'; @@ -49,7 +49,7 @@ export const useSearchOption = ({ programId, trackedEntityTypeId, }: Props): { searchOption?: AvailableSearchOption; isLoading: boolean; isError: boolean } => { - const locale = CurrentUser.get().uiLocale; + const { locale } = useUserLocale(); const searchScope = useMemo(() => { if (programId) { 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 8c51462aaa..c8acf44885 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/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts index 82b9f81957..6af28b50a2 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts @@ -1,9 +1,8 @@ import uuid from 'd2-utilizr/lib/uuid'; import { ofType } from 'redux-observable'; -import { map } from 'rxjs/operators'; +import { switchMap } from 'rxjs/operators'; import moment from 'moment'; import { ApiUtils, ReduxStore } from 'capture-core-utils/types'; -import { CurrentUser } from '../../../../utils/userInfo/CurrentUser'; import { newEventWidgetDataEntryActionTypes, } from '../actions/dataEntry.actions'; @@ -15,25 +14,33 @@ import { export const addNoteForNewEnrollmentEventEpic = ( action$: any, store: ReduxStore, - { fromClientDate }: ApiUtils, + { querySingleResource, fromClientDate }: ApiUtils, ) => action$.pipe( ofType(newEventWidgetDataEntryActionTypes.EVENT_NOTE_ADD), - map((action: any) => { + switchMap((action: any) => { const payload = action.payload; - const { firstName, surname, username } = CurrentUser.get(); - const clientId = uuid(); - const note = { - value: payload.note, - createdBy: { - firstName, - surname, - uid: clientId, + + return querySingleResource({ + resource: 'me', + params: { + fields: 'firstName,surname,username', }, - storedBy: username, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - clientId: uuid(), - }; + }).then((user: any) => { + const { userName, firstName, surname } = user; + const clientId = uuid(); + const note = { + value: payload.note, + createdBy: { + firstName, + surname, + uid: clientId, + }, + storedBy: userName, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + clientId: uuid(), + }; - return addNote(payload.dataEntryId, payload.itemId, note); + return addNote(payload.dataEntryId, payload.itemId, note); + }); })); diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts b/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts index 609f3a200e..99eaf0c22f 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts @@ -1,11 +1,10 @@ import { batchActions } from 'redux-batched-actions'; import { ofType } from 'redux-observable'; import { featureAvailable, FEATURES } from 'capture-core-utils'; -import { map } from 'rxjs/operators'; +import { switchMap } from 'rxjs/operators'; import uuid from 'd2-utilizr/lib/uuid'; import moment from 'moment'; import type { ReduxStore, ApiUtils, EpicAction } from 'capture-core-utils/types/global'; -import { CurrentUser } from '../../utils/userInfo/CurrentUser'; import { actionTypes, batchActionTypes, startAddNoteForEnrollment, addEnrollmentNote } from './WidgetEnrollmentNote.actions'; import type { ClientNote, SaveContext } from './WidgetEnrollmentNote.types'; @@ -25,37 +24,44 @@ const createServerData = (note: string, useNewEndpoint: boolean): Record, store: ReduxStore, - { fromClientDate }: ApiUtils, + { querySingleResource, fromClientDate }: ApiUtils, ) => action$.pipe( ofType(actionTypes.REQUEST_ADD_NOTE_FOR_ENROLLMENT), - map((action: { payload: AddNoteActionPayload }) => { + switchMap((action: { payload: AddNoteActionPayload }) => { const state = store.value; const { enrollmentId, note } = action.payload; const useNewEndpoint = featureAvailable(FEATURES.newNoteEndpoint); - const { firstName, surname, username } = CurrentUser.get(); - const clientId = uuid(); + return querySingleResource({ + resource: 'me', + params: { + fields: 'firstName, surname, userName', + }, + }).then((user) => { + const { firstName, surname, userName } = user; + const clientId = uuid(); - const serverData = createServerData(note, useNewEndpoint); + const serverData = createServerData(note, useNewEndpoint); - const clientNote: ClientNote = { - value: note, - createdBy: { - firstName, - surname, - uid: clientId, - }, - storedBy: username, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - }; + const clientNote: ClientNote = { + value: note, + createdBy: { + firstName, + surname, + uid: clientId, + }, + storedBy: userName, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + }; - const saveContext: SaveContext = { - enrollmentId, - noteClientId: clientId, - }; + const saveContext: SaveContext = { + enrollmentId, + noteClientId: clientId, + }; - return batchActions([ - startAddNoteForEnrollment(enrollmentId, serverData, state.currentSelections, saveContext), - addEnrollmentNote(enrollmentId, clientNote), - ], batchActionTypes.ADD_NOTE_BATCH_FOR_ENROLLMENT); + return batchActions([ + startAddNoteForEnrollment(enrollmentId, serverData, state.currentSelections, saveContext), + addEnrollmentNote(enrollmentId, clientNote), + ], batchActionTypes.ADD_NOTE_BATCH_FOR_ENROLLMENT); + }); })); diff --git a/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts b/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts index 9fca9c4e59..e2c191e142 100644 --- a/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts @@ -1,11 +1,10 @@ import { batchActions } from 'redux-batched-actions'; import { ofType } from 'redux-observable'; import { featureAvailable, FEATURES } from 'capture-core-utils'; -import { map } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import uuid from 'd2-utilizr/lib/uuid'; import moment from 'moment'; import type { ReduxStore, ApiUtils, EpicAction } from 'capture-core-utils/types'; -import { CurrentUser } from '../../utils/userInfo/CurrentUser'; import { actionTypes, batchActionTypes, startAddNoteForEvent } from './WidgetEventNote.actions'; import type { ClientNote, FormNote, SaveContext } from './WidgetEventNote.types'; @@ -39,51 +38,58 @@ const createServerData = (eventId: string, note: string, useNewEndpoint: boolean export const addNoteForEventEpic = ( action$: EpicAction, store: ReduxStore, - { fromClientDate }: ApiUtils, + { querySingleResource, fromClientDate }: ApiUtils, ) => action$.pipe( ofType(actionTypes.REQUEST_ADD_NOTE_FOR_EVENT), - map((action: { payload: AddNoteActionPayload }) => { + switchMap((action: { payload: AddNoteActionPayload }) => { const state = store.value; const payload = action.payload; const eventId = state.dataEntries[payload.dataEntryId].eventId; const useNewEndpoint = featureAvailable(FEATURES.newNoteEndpoint); - const { firstName, surname, username } = CurrentUser.get(); - const clientId = uuid(); + return querySingleResource({ + resource: 'me', + params: { + fields: 'firstName, surname, userName', + }, + }).then((user) => { + const { firstName, surname, userName } = user; + const clientId = uuid(); - const serverData = createServerData(eventId, payload.note, useNewEndpoint); + const serverData = createServerData(eventId, payload.note, useNewEndpoint); - const clientNote: ClientNote = { - value: payload.note, - lastUpdatedBy: { - firstName, - surname, - uid: clientId, - }, - storedBy: username, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - }; - const formNote: FormNote = { - ...clientNote, - storedAt: clientNote.storedAt, - createdBy: { - firstName, - surname, - uid: clientId, - }, - }; - const saveContext: SaveContext = { - dataEntryId: payload.dataEntryId, - itemId: payload.itemId, - eventId, - noteClientId: clientId, - }; + const clientNote: ClientNote = { + value: payload.note, + lastUpdatedBy: { + firstName, + surname, + uid: clientId, + }, + storedBy: userName, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + }; + const formNote: FormNote = { + ...clientNote, + storedAt: clientNote.storedAt, + createdBy: { + firstName, + surname, + uid: clientId, + }, + }; + const saveContext: SaveContext = { + dataEntryId: payload.dataEntryId, + itemId: payload.itemId, + eventId, + noteClientId: clientId, + }; - return batchActions([ - startAddNoteForEvent(eventId, serverData, state.currentSelections, saveContext), - addNote(payload.dataEntryId, payload.itemId, formNote), - addEventNote(eventId, clientNote), - ], batchActionTypes.ADD_NOTE_BATCH_FOR_EVENT); + return batchActions([ + startAddNoteForEvent(eventId, serverData, state.currentSelections, saveContext), + addNote(payload.dataEntryId, payload.itemId, formNote), + addEventNote(eventId, clientNote), + ], batchActionTypes.ADD_NOTE_BATCH_FOR_EVENT); + }); })); export const removeNoteForEventEpic = (action$: EpicAction) => diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx index 8632883180..4f46356c5d 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx @@ -7,8 +7,6 @@ import { pipe } from 'capture-core-utils'; import { getProgramAndStageForProgram, TrackerProgram, getProgramEventAccess, dataElementTypes } from '../../metaData'; import { getCachedOrgUnitName } from '../../metadataRetrieval/orgUnitName'; import { useLocationQuery } from '../../utils/routing'; -import { CurrentUser } from '../../utils/userInfo/CurrentUser'; -import { generateUID } from '../../utils/uid/generateUID'; import type { ContainerProps } from './widgetEventSchedule.types'; import { WidgetEventScheduleComponent } from './WidgetEventSchedule.component'; import { @@ -16,6 +14,7 @@ import { useDetermineSuggestedScheduleDate, useEventsInOrgUnit, useScheduleConfigFromProgram, + useNoteDetails, } from './hooks'; import { requestScheduleEvent } from './WidgetEventSchedule.actions'; import { NoAccess } from './AccessVerification'; @@ -53,6 +52,7 @@ export const WidgetEventSchedule = ({ }); const { fromClientDate } = useTimeZoneConversion(); const orgUnitName = getCachedOrgUnitName(initialOrgUnitId); + const { currentUser, noteId }: { currentUser: any, noteId: string } = useNoteDetails(); const [scheduleDate, setScheduleDate] = useState(''); const [scheduledOrgUnit, setScheduledOrgUnit] = useState(); const [validation, setValidation] = useState(); @@ -148,15 +148,19 @@ export const WidgetEventSchedule = ({ ]); const onAddNote = (note: string) => { - const { username, firstName, surname } = CurrentUser.get(); - const newNote = { - storedBy: username, - storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), - value: note, - createdBy: { firstName, surname }, - note: generateUID(), - }; - setNotes([...notes, newNote]); + if (currentUser) { + const newNote = { + storedBy: currentUser.userName, + storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), + value: note, + createdBy: { + firstName: currentUser.firstName, + surname: currentUser.surname, + }, + note: noteId, + }; + setNotes([...notes, newNote]); + } }; const onSetAssignee = useCallback((user: any) => setAssignee(user), []); diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/index.ts b/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/index.ts index 951d36bf88..b41c3de724 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/index.ts +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/index.ts @@ -2,10 +2,12 @@ import { useDetermineSuggestedScheduleDate } from './useDetermineSuggestedSchedu import { useEventsInOrgUnit } from './useEventsInOrgUnit'; import { useScheduleConfigFromProgram } from './useScheduleConfigFromProgram'; import { useScheduleConfigFromProgramStage } from './useScheduleConfigFromProgramStage'; +import { useNoteDetails } from './useNoteDetails'; export { useDetermineSuggestedScheduleDate, useEventsInOrgUnit, useScheduleConfigFromProgram, useScheduleConfigFromProgramStage, + useNoteDetails, }; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts b/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts new file mode 100644 index 0000000000..972ee5ef77 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts @@ -0,0 +1,22 @@ +import { useMemo } from 'react'; +import { useDataQuery } from '@dhis2/app-runtime'; +import { generateUID } from '../../../utils/uid/generateUID'; + +export const useNoteDetails = () => { + const { data, error, loading } = useDataQuery(useMemo(() => ({ + currentUser: { + resource: 'me', + params: { + fields: + ['firstName,surname,username'], + }, + }, + }), [])); + + + return { + error, + currentUser: !loading && data?.currentUser, + noteId: generateUID(), + }; +}; 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 dde661bf74..c55a864f36 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'; @@ -56,13 +56,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, @@ -93,7 +95,11 @@ const WidgetProfilePlain = ({ trackedEntityTypeAccess, geometry, } = useTrackedEntityInstances(teiId, programId, storedAttributeValues, storedGeometry); - const userRoles = CurrentUser.get().userRoles; + const { + loading: userRolesLoading, + error: userRolesError, + userRoles, + } = useUserRoles(); const hasNoAttributes = !program?.programTrackedEntityAttributes?.length; @@ -103,8 +109,8 @@ const WidgetProfilePlain = ({ !readOnlyMode, [hasNoAttributes, readOnlyMode, trackedEntityTypeAccess]); - 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 }; +}; diff --git a/src/core_modules/capture-core/components/common/bulkDataEntry/hooks/useBulkDataEntryDatastoreConfigurations.ts b/src/core_modules/capture-core/components/common/bulkDataEntry/hooks/useBulkDataEntryDatastoreConfigurations.ts index c7e3826a8a..531b843389 100644 --- a/src/core_modules/capture-core/components/common/bulkDataEntry/hooks/useBulkDataEntryDatastoreConfigurations.ts +++ b/src/core_modules/capture-core/components/common/bulkDataEntry/hooks/useBulkDataEntryDatastoreConfigurations.ts @@ -3,7 +3,7 @@ import log from 'loglevel'; import { errorCreator } from 'capture-core-utils'; import { useEffect } from 'react'; import { useApiMetadataQuery } from '../../../../utils/reactQueryHelpers'; -import { CurrentUser } from '../../../../utils/userInfo/CurrentUser'; +import { useUserLocale } from '../../../../utils/localeData/useUserLocale'; import type { DataStoreConfiguration } from '../bulkDataEntry.types'; const bulkDataEntryDatastoreSchema = z.object({ @@ -48,7 +48,7 @@ const getLocalizedString = (field: Record, locale: string): stri }; export const useBulkDataEntryDatastoreConfigurations = (programId: string) => { - const locale = CurrentUser.get().uiLocale; + const { locale } = useUserLocale(); const { data: configExists, isLoading: namespaceIsLoading, 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, + }; +}; diff --git a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts deleted file mode 100644 index eb8be00328..0000000000 --- a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts +++ /dev/null @@ -1,28 +0,0 @@ -export type OrgUnitRef = { - id: string; - path: string; -}; - -export type CurrentUserType = { - firstName: string; - surname: string; - username: string; - uiLocale: string; - userRoles: string[]; - organisationUnits: OrgUnitRef[]; - teiSearchOrganisationUnits: OrgUnitRef[]; -}; - -export class CurrentUser { - private static currentData: CurrentUserType | null = null; - - static set(data: CurrentUserType) { - CurrentUser.currentData = data; - } - static get(): CurrentUserType { - if (!CurrentUser.currentData) { - throw new Error('CurrentUser accessed before initialization'); - } - return CurrentUser.currentData; - } -} From aea868e2e0a240b546566a5e685d6678d2a45d13 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Thu, 14 May 2026 11:29:43 +0200 Subject: [PATCH 12/18] fix: revert hoc removal --- .../withInternalFilterHandler/scopes.const.ts | 6 + ...withImplicitRootsInternalFilterHandler.tsx | 42 ++++++ .../withInternalFilterHandler.tsx | 125 ++++++++++++++++++ .../components/FormFields/New/index.ts | 6 + .../ComposedRegUnitSelector.component.tsx | 10 +- .../ComposedRegUnitSelector.component.tsx | 10 +- 6 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/scopes.const.ts create mode 100644 src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withImplicitRootsInternalFilterHandler.tsx create mode 100644 src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/scopes.const.ts b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/scopes.const.ts new file mode 100644 index 0000000000..0f04908e4f --- /dev/null +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/scopes.const.ts @@ -0,0 +1,6 @@ +export const orgUnitFieldScopes = { + USER_CAPTURE: 'USER_CAPTURE', + USER_SEARCH: 'USER_SEARCH', +} as const; + +export type OrgUnitFieldScope = typeof orgUnitFieldScopes[keyof typeof orgUnitFieldScopes]; diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withImplicitRootsInternalFilterHandler.tsx b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withImplicitRootsInternalFilterHandler.tsx new file mode 100644 index 0000000000..12ed72a645 --- /dev/null +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withImplicitRootsInternalFilterHandler.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { withInternalFilterHandler } from './withInternalFilterHandler'; +import { get as getOrgUnitRoots } from '../orgUnitRoots.store'; +import { orgUnitFieldScopes } from './scopes.const'; +import { withApiUtils } from '../../../../../../HOC'; +import type { OrgUnitFieldScope } from './scopes.const'; + +type Props = { + scope: OrgUnitFieldScope; +}; + +// Wraps withInternalFilterHandler. Passes on defaultRoots from the organisation unit store based on the input scope. +export const withOrgUnitFieldImplicitRootsFilterHandler = () => +

>(InnerComponent: React.ComponentType

) => { + const InternalFilterHandlerHOC = withApiUtils(withInternalFilterHandler()(InnerComponent)); + + class OrgUnitImplicitInternalFilterHandlerHOC extends React.Component { + defaultRoots: Array; + constructor(props: Props & P) { + super(props); + const { scope } = this.props; + this.defaultRoots = + getOrgUnitRoots(OrgUnitImplicitInternalFilterHandlerHOC.DEFAULT_ROOTS_DATA[scope]) || []; + } + + static DEFAULT_ROOTS_DATA = { + [orgUnitFieldScopes.USER_CAPTURE]: 'captureRoots', + [orgUnitFieldScopes.USER_SEARCH]: 'search', + }; + + render() { + const { ...passOnProps } = this.props; + return ( + + ); + } + } + return OrgUnitImplicitInternalFilterHandlerHOC; + }; diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx new file mode 100644 index 0000000000..5516f7de04 --- /dev/null +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/withInternalFilterHandler/withInternalFilterHandler.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; +import log from 'loglevel'; +import { makeCancelablePromise, errorCreator } from 'capture-core-utils'; +import { orgUnitFieldScopes } from './scopes.const'; +import type { QuerySingleResource } from '../../../../../../utils/api/api.types'; +import type { OrgUnitFieldScope } from './scopes.const'; + +type Props = { + defaultRoots: Array; + scope: OrgUnitFieldScope; + onSearchError?: (message: string) => void; + onSelect: (value: any) => void; + querySingleResource: QuerySingleResource; +}; + +type State = { + filteredRoots: Array | null; + filterText: string | null; + inProgress: boolean; + treeKey: string; +}; + +// Handles organisation unit filtering internally in this component. +export const withInternalFilterHandler = () => + (InnerComponent: React.ComponentType) => + class OrgUnitInternalFilterHandlerHOC extends React.Component { + cancelablePromise: any; + constructor(props: Props) { + super(props); + this.state = { + filteredRoots: null, + filterText: null, + inProgress: false, + treeKey: OrgUnitInternalFilterHandlerHOC.INITIAL_TREE_KEY, + }; + this.cancelablePromise = null; + } + + componentWillUnmount() { + if (this.cancelablePromise) { + this.cancelablePromise.cancel(); + } + } + + static INITIAL_TREE_KEY = 'initial'; + + filterOrgUnits(filterText: string) { + const { scope, onSearchError, querySingleResource } = this.props; + const hierarchyProp = scope === orgUnitFieldScopes.USER_CAPTURE + ? { withinUserHierarchy: true } + : { withinUserSearchHierarchy: true }; + this.setState({ + inProgress: true, + }); + + if (this.cancelablePromise) { + this.cancelablePromise.cancel(); + } + + const cancelablePromise = makeCancelablePromise( + querySingleResource({ + resource: 'organisationUnits', + params: { + fields: [ + 'id,displayName,path,publicAccess,access,lastUpdated', + 'children[id,displayName,publicAccess,access,path,children::isNotEmpty]', + ].join(','), + paging: false, + query: filterText, + ...hierarchyProp, + }, + }), + ); + + cancelablePromise + .promise + .then((orgUnitCollection: Record) => { + this.setState({ + filteredRoots: orgUnitCollection.toArray(), + filterText, + inProgress: false, + treeKey: filterText, + }); + this.cancelablePromise = null; + }) + .catch((error) => { + log.error(errorCreator('There was an error retrieving organisation unit roots')({ error })); + onSearchError && onSearchError(error); + }); + + this.cancelablePromise = cancelablePromise; + } + + resetOrgUnits() { + this.setState({ + filteredRoots: null, + filterText: null, + treeKey: OrgUnitInternalFilterHandlerHOC.INITIAL_TREE_KEY, + }); + } + + handleFilterChange = (searchText: string) => { + if (searchText) { + this.filterOrgUnits(searchText); + } else { + this.resetOrgUnits(); + } + } + + render() { + const { defaultRoots, onSearchError, onSelect, scope, ...passOnProps } = this.props; + const { filteredRoots, filterText, treeKey, inProgress } = this.state; + return ( + + ); + } + }; diff --git a/src/core_modules/capture-core/components/FormFields/New/index.ts b/src/core_modules/capture-core/components/FormFields/New/index.ts index 3c4037971f..fac9466ce5 100644 --- a/src/core_modules/capture-core/components/FormFields/New/index.ts +++ b/src/core_modules/capture-core/components/FormFields/New/index.ts @@ -35,5 +35,11 @@ export { withFocusSaver, withInternalChangeHandler } from 'capture-ui'; export { withCenterPoint } from './HOC/withCenterPoint'; export { withConditionalTooltip } from './HOC/withConditionalTooltip'; +// OrgUnit HOCs +export { + withOrgUnitFieldImplicitRootsFilterHandler, +} from './Fields/OrgUnitField/withInternalFilterHandler/withImplicitRootsInternalFilterHandler'; +export { orgUnitFieldScopes } from './Fields/OrgUnitField/withInternalFilterHandler/scopes.const'; + // Constants export { orientations } from 'capture-ui'; diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx index f5d98e2948..4710e6d265 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx @@ -6,8 +6,13 @@ import { withInternalChangeHandler, withFilterProps, SingleOrgUnitSelectField, + withOrgUnitFieldImplicitRootsFilterHandler, + orgUnitFieldScopes, } from '../../../../../FormFields/New'; +const OrgUnitFieldImplicitRootsFilterHandlerHOC = + withOrgUnitFieldImplicitRootsFilterHandler()(SingleOrgUnitSelectField); + type Props = { onUpdateSelectedOrgUnit: (orgUnit?: OrgUnit | null) => void; }; @@ -16,8 +21,9 @@ class OrgUnitFieldWrapper extends React.Component { render() { const { onUpdateSelectedOrgUnit, ...passOnProps } = this.props; return ( - diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx index ba6a4e35d2..fef577ec7a 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/ComposedRegUnitSelector.component.tsx @@ -5,15 +5,21 @@ import { withInternalChangeHandler, withFilterProps, SingleOrgUnitSelectField, + withOrgUnitFieldImplicitRootsFilterHandler, + orgUnitFieldScopes, } from '../../../../../../FormFields/New'; import type { ComposedRegUnitSelectorProps } from './RegUnitSelector.types'; +const OrgUnitFieldImplicitRootsFilterHandlerHOC = + withOrgUnitFieldImplicitRootsFilterHandler()(SingleOrgUnitSelectField); + class OrgUnitFieldWrapper extends React.Component { render() { const { onUpdateSelectedOrgUnit, ...passOnProps } = this.props; return ( - From 56cda939fcddd6d59c8be7696192f362d089617b Mon Sep 17 00:00:00 2001 From: henrikmv Date: Thu, 14 May 2026 14:48:09 +0200 Subject: [PATCH 13/18] fix: add program and programStage to event listing fields --- .../WorkingLists/EventWorkingLists/epics/getEventListData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts index 70273972e6..12679eca59 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/getEventListData.ts @@ -8,7 +8,7 @@ type InputQueryArgs = { }; const LISTING_FIELDS = - 'event,status,occurredAt,orgUnit,' + 'event,program,programStage,status,occurredAt,orgUnit,' + 'assignedUser[uid,username,firstName,surname],' + 'dataValues[dataElement,value]'; From fa1e08d6060ed5fedf5462570895a2967548d57b Mon Sep 17 00:00:00 2001 From: henrikmv Date: Mon, 18 May 2026 12:34:16 +0200 Subject: [PATCH 14/18] fix: (review) adjust storeRelationshipTypes query for access --- .../metaData/RelationshipType/RelationshipType.ts | 14 ++++++++++---- .../quickStoreOperations/storeRelationshipTypes.ts | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/core_modules/capture-core/metaData/RelationshipType/RelationshipType.ts b/src/core_modules/capture-core/metaData/RelationshipType/RelationshipType.ts index 85e776197b..1b606b79aa 100644 --- a/src/core_modules/capture-core/metaData/RelationshipType/RelationshipType.ts +++ b/src/core_modules/capture-core/metaData/RelationshipType/RelationshipType.ts @@ -1,6 +1,12 @@ /* eslint-disable no-underscore-dangle */ import isFunction from 'd2-utilizr/lib/isFunction'; -import type { Access } from '../Access'; + +type RelationshipTypeAccess = { + data: { + read: boolean, + write: boolean, + }, +}; type RelationshipConstraint = { entity: string, @@ -15,7 +21,7 @@ export class RelationshipType { _referral!: boolean; _fromToName!: string; _toFromName!: string; - _access!: Access; + _access!: RelationshipTypeAccess; _from!: RelationshipConstraint; _to!: RelationshipConstraint; @@ -39,11 +45,11 @@ export class RelationshipType { return this._name; } - set access(access: Access) { + set access(access: RelationshipTypeAccess) { this._access = access; } - get access(): Access { + get access(): RelationshipTypeAccess { return this._access; } diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/baseLoader/quickStoreOperations/storeRelationshipTypes.ts b/src/core_modules/capture-core/metaDataStoreLoaders/baseLoader/quickStoreOperations/storeRelationshipTypes.ts index 82a83675c6..ce4f21e53c 100644 --- a/src/core_modules/capture-core/metaDataStoreLoaders/baseLoader/quickStoreOperations/storeRelationshipTypes.ts +++ b/src/core_modules/capture-core/metaDataStoreLoaders/baseLoader/quickStoreOperations/storeRelationshipTypes.ts @@ -9,7 +9,7 @@ export const storeRelationshipTypes = () => { 'fromConstraint[relationshipEntity,trackerDataView,trackedEntityType[id,name],' + 'program[id,name],programStage[id,name,program[id]]],' + 'toConstraint[relationshipEntity,trackerDataView,trackedEntityType[id,name],' + - 'program[id,name],programStage[id,name,program[id]]],access[*]', + 'program[id,name],programStage[id,name,program[id]]],access[data[read,write]]', }, }; From c3d76d816fdaad8049e83a5295b6ba804f7f1da3 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Thu, 21 May 2026 19:55:27 +0200 Subject: [PATCH 15/18] fix: remove storedBy fields from note related components and epics --- .../DataEntry/epics/addNoteForNewSingleEvent.epics.ts | 5 ++--- .../capture-core/components/Notes/Notes.component.tsx | 4 +--- .../capture-core/components/Notes/notes.types.ts | 1 - .../components/Pages/ViewEvent/Notes/viewEventNotes.epics.ts | 5 ++--- .../useCommonEnrollmentDomainData.types.ts | 1 - .../DataEntry/epics/dataEntryNote.epics.ts | 5 ++--- .../WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts | 5 ++--- .../WidgetEnrollmentNote/WidgetEnrollmentNote.types.ts | 1 - .../components/WidgetEventNote/WidgetEventNote.epics.ts | 5 ++--- .../components/WidgetEventNote/WidgetEventNote.types.ts | 1 - .../WidgetEventSchedule/WidgetEventSchedule.actions.ts | 1 - .../WidgetEventSchedule/WidgetEventSchedule.container.tsx | 2 -- .../WidgetEventSchedule/widgetEventSchedule.types.ts | 1 - 13 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts index 6b8e93764b..11ac525f12 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addNoteForNewSingleEvent.epics.ts @@ -30,10 +30,10 @@ export const addNoteForNewSingleEventEpic = ( return querySingleResource({ resource: 'me', params: { - fields: 'firstName,surname,userName', + fields: 'firstName,surname', }, }).then((user) => { - const { userName, firstName, surname } = user; + const { firstName, surname } = user; const clientId = uuid(); const note = { value: payload.note, @@ -42,7 +42,6 @@ export const addNoteForNewSingleEventEpic = ( surname, uid: clientId, }, - storedBy: userName, storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), clientId: uuid(), }; diff --git a/src/core_modules/capture-core/components/Notes/Notes.component.tsx b/src/core_modules/capture-core/components/Notes/Notes.component.tsx index dfd4bfc064..da815c2a53 100644 --- a/src/core_modules/capture-core/components/Notes/Notes.component.tsx +++ b/src/core_modules/capture-core/components/Notes/Notes.component.tsx @@ -180,9 +180,7 @@ const NotesPlain = ({ label={<>

- {n.createdBy ? - `${n.createdBy.firstName} ${n.createdBy.surname}` - : `${n.storedBy}` } + {n.createdBy && `${n.createdBy.firstName} ${n.createdBy.surname}`}
diff --git a/src/core_modules/capture-core/components/Notes/notes.types.ts b/src/core_modules/capture-core/components/Notes/notes.types.ts index 4aa088a5a0..4fcb075d6f 100644 --- a/src/core_modules/capture-core/components/Notes/notes.types.ts +++ b/src/core_modules/capture-core/components/Notes/notes.types.ts @@ -1,6 +1,5 @@ export type Note = { clientId: string; - storedBy: string; createdBy?: { firstName: string; surname: string; diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/Notes/viewEventNotes.epics.ts b/src/core_modules/capture-core/components/Pages/ViewEvent/Notes/viewEventNotes.epics.ts index 77a9a7d2cb..8d0e404a6b 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/Notes/viewEventNotes.epics.ts +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/Notes/viewEventNotes.epics.ts @@ -57,10 +57,10 @@ export const addNoteForViewEventEpic = (action$: any, store: any, { querySingleR return querySingleResource({ resource: 'me', params: { - fields: 'userName,firstName,surname', + fields: 'firstName,surname', }, }).then((user: any) => { - const { userName, firstName, surname } = user; + const { firstName, surname } = user; const clientId = uuid(); const serverData = createServerData(eventId, payload.note, useNewEndpoint); @@ -71,7 +71,6 @@ export const addNoteForViewEventEpic = (action$: any, store: any, { querySingleR surname, uid: clientId, }, - storedBy: userName, storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), clientId: uuid(), }; 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 20cdf66a7d..195acc194a 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 @@ -20,7 +20,6 @@ export type EnrollmentData = { orgUnit?: string; program?: string; status?: string; - storedBy?: string; scheduledAt?: string; trackedEntity?: string; trackedEntityType?: string; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts index 6af28b50a2..c7a8aef123 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/epics/dataEntryNote.epics.ts @@ -24,10 +24,10 @@ export const addNoteForNewEnrollmentEventEpic = ( return querySingleResource({ resource: 'me', params: { - fields: 'firstName,surname,username', + fields: 'firstName,surname', }, }).then((user: any) => { - const { userName, firstName, surname } = user; + const { firstName, surname } = user; const clientId = uuid(); const note = { value: payload.note, @@ -36,7 +36,6 @@ export const addNoteForNewEnrollmentEventEpic = ( surname, uid: clientId, }, - storedBy: userName, storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), clientId: uuid(), }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts b/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts index 99eaf0c22f..fa0793c688 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts @@ -35,10 +35,10 @@ export const addNoteForEnrollmentEpic = ( return querySingleResource({ resource: 'me', params: { - fields: 'firstName, surname, userName', + fields: 'firstName, surname', }, }).then((user) => { - const { firstName, surname, userName } = user; + const { firstName, surname } = user; const clientId = uuid(); const serverData = createServerData(note, useNewEndpoint); @@ -50,7 +50,6 @@ export const addNoteForEnrollmentEpic = ( surname, uid: clientId, }, - storedBy: userName, storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.types.ts b/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.types.ts index 4f3470de6e..9a2fc3a167 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.types.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.types.ts @@ -5,7 +5,6 @@ export type ClientNote = { surname: string; uid: string; }; - storedBy: string; storedAt: string; }; diff --git a/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts b/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts index e2c191e142..08dc4d54a7 100644 --- a/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts @@ -50,10 +50,10 @@ export const addNoteForEventEpic = ( return querySingleResource({ resource: 'me', params: { - fields: 'firstName, surname, userName', + fields: 'firstName, surname', }, }).then((user) => { - const { firstName, surname, userName } = user; + const { firstName, surname } = user; const clientId = uuid(); const serverData = createServerData(eventId, payload.note, useNewEndpoint); @@ -65,7 +65,6 @@ export const addNoteForEventEpic = ( surname, uid: clientId, }, - storedBy: userName, storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), }; const formNote: FormNote = { diff --git a/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.types.ts b/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.types.ts index 87957e6627..021ea17a7f 100644 --- a/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.types.ts +++ b/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.types.ts @@ -10,7 +10,6 @@ export type ClientNote = { surname: string; uid: string; }; - storedBy: string; storedAt: string; }; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.ts b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.ts index 193dd59454..5375284527 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.ts +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.ts @@ -27,7 +27,6 @@ export const requestScheduleEvent = ({ notes: Array<{ value: string; storedAt?: string; - storedBy?: string; createdBy?: any; note?: string; }>; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx index 4f46356c5d..3267c36d95 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx @@ -75,7 +75,6 @@ export const WidgetEventSchedule = ({ const [notes, setNotes] = useState>([]); @@ -150,7 +149,6 @@ export const WidgetEventSchedule = ({ const onAddNote = (note: string) => { if (currentUser) { const newNote = { - storedBy: currentUser.userName, storedAt: fromClientDate(moment().toISOString()).getServerZonedISOString(), value: note, createdBy: { diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.ts b/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.ts index ed62d7f988..1e3efe8dc9 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.ts +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.ts @@ -58,7 +58,6 @@ export type Props = { notes: Array<{ value: string; storedAt: string; - storedBy?: string; createdBy?: any; note?: string; }>; From bb746bd27c16ef0b0d1a1a02c37d65ca6e452ae5 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Thu, 21 May 2026 21:09:46 +0200 Subject: [PATCH 16/18] fix: remove space in fields parameter --- .../WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts | 2 +- .../components/WidgetEventNote/WidgetEventNote.epics.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts b/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts index fa0793c688..8756df4ac1 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEnrollmentNote/WidgetEnrollmentNote.epics.ts @@ -35,7 +35,7 @@ export const addNoteForEnrollmentEpic = ( return querySingleResource({ resource: 'me', params: { - fields: 'firstName, surname', + fields: 'firstName,surname', }, }).then((user) => { const { firstName, surname } = user; diff --git a/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts b/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts index 08dc4d54a7..58a69b8cd8 100644 --- a/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts +++ b/src/core_modules/capture-core/components/WidgetEventNote/WidgetEventNote.epics.ts @@ -50,7 +50,7 @@ export const addNoteForEventEpic = ( return querySingleResource({ resource: 'me', params: { - fields: 'firstName, surname', + fields: 'firstName,surname', }, }).then((user) => { const { firstName, surname } = user; From f5fe6a5059ebb1616abcba28eeb104fc9306380c Mon Sep 17 00:00:00 2001 From: henrikmv Date: Fri, 22 May 2026 12:41:44 +0200 Subject: [PATCH 17/18] fix: (review) remove username from fields parameter in useNoteDetails hook --- .../components/WidgetEventSchedule/hooks/useNoteDetails.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts b/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts index 972ee5ef77..f4fc14be51 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/hooks/useNoteDetails.ts @@ -8,7 +8,7 @@ export const useNoteDetails = () => { resource: 'me', params: { fields: - ['firstName,surname,username'], + ['firstName,surname'], }, }, }), [])); From 9124167c4e8b4a9e74189759b6700ca359b579a6 Mon Sep 17 00:00:00 2001 From: henrikmv Date: Wed, 27 May 2026 12:09:53 +0200 Subject: [PATCH 18/18] fix: remove username field from CurrentUser type and API response --- src/components/AppLoader/init.ts | 4 +--- src/core_modules/capture-core/utils/userInfo/CurrentUser.ts | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/AppLoader/init.ts b/src/components/AppLoader/init.ts index 197a8e5b33..4418f77ba7 100644 --- a/src/components/AppLoader/init.ts +++ b/src/components/AppLoader/init.ts @@ -149,7 +149,7 @@ export async function initializeAsync({ const meResponse = await querySingleResource({ resource: 'me', params: { - fields: 'id,firstName,surname,username,userRoles[id],userGroups[id],' + + fields: 'id,firstName,surname,userRoles[id],userGroups[id],' + 'organisationUnits[id,path],teiSearchOrganisationUnits[id,path],settings', }, }); @@ -158,7 +158,6 @@ export async function initializeAsync({ id: currentUserId, firstName = '', surname = '', - username = '', userRoles = [], userGroups = [], organisationUnits: captureScope = [], @@ -169,7 +168,6 @@ export async function initializeAsync({ CurrentUser.set({ firstName, surname, - username, uiLocale: userSettings?.keyUiLocale ?? '', userRoles: userRoles.map((role: { id: string }) => role.id), organisationUnits: captureScope, diff --git a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts index eb8be00328..61edda9b95 100644 --- a/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts +++ b/src/core_modules/capture-core/utils/userInfo/CurrentUser.ts @@ -6,7 +6,6 @@ export type OrgUnitRef = { export type CurrentUserType = { firstName: string; surname: string; - username: string; uiLocale: string; userRoles: string[]; organisationUnits: OrgUnitRef[];