From 53902817ac1b34688a76e30459a3c9c6c9ddf5d8 Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Thu, 16 Nov 2023 10:44:54 +0100 Subject: [PATCH 1/5] feat: add inheritable TEAs --- .../hooks/useFormValues.js | 7 +-- .../SingleOrgUnitSelectField.component.js | 6 +-- .../DataEntryEnrollment.component.js | 4 ++ .../Enrollment/dataEntryEnrollment.types.js | 3 ++ .../RegisterTeiDataEntry.component.js | 8 +++- .../RegisterTei/RegisterTei.component.js | 4 ++ .../RegisterTei/RegisterTei.container.js | 15 ++++++ .../RegisterTei/RegisterTei.types.js | 5 ++ ...kedEntityRelationshipsWrapper.component.js | 1 + .../useInheritedAttributeValues.js | 47 +++++++++++++++++++ .../metaData/DataElement/DataElement.js | 9 ++++ .../factory/enrollment/DataElementFactory.js | 1 + .../TrackedEntityType/DataElementFactory.js | 1 + .../storeTrackedEntityAttributes.js | 2 +- .../storageControllers/cache.types.js | 1 + 15 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js index 1431a10e23..d84c5d278d 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js @@ -50,7 +50,7 @@ type StaticPatternValues = { const useClientAttributesWithSubvalues = (program: InputProgramData, attributes: Array) => { const dataEngine = useDataEngine(); - const [listAttributes, setListAttributes] = useState([]); + const [listAttributes, setListAttributes] = useState(null); const getListAttributes = useCallback(async () => { if (program && attributes) { @@ -139,8 +139,6 @@ export const useFormValues = ({ program, trackedEntityInstanceAttributes, orgUni const formValuesReadyRef = useRef(false); const [formValues, setFormValues] = useState({}); const [clientValues, setClientValues] = useState({}); - const areAttributesWithSubvaluesReady = - (teiId && clientAttributesWithSubvalues.length > 0) || (!teiId && clientAttributesWithSubvalues.length === 0); useEffect(() => { formValuesReadyRef.current = false; @@ -152,7 +150,7 @@ export const useFormValues = ({ program, trackedEntityInstanceAttributes, orgUni formFoundation && Object.entries(formFoundation).length > 0 && formValuesReadyRef.current === false && - areAttributesWithSubvaluesReady + !!clientAttributesWithSubvalues ) { const staticPatternValues = { orgUnitCode: orgUnit.code }; const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); @@ -172,7 +170,6 @@ export const useFormValues = ({ program, trackedEntityInstanceAttributes, orgUni clientAttributesWithSubvalues, formValuesReadyRef, orgUnit, - areAttributesWithSubvaluesReady, searchTerms, dataEngine, ]); diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/SingleOrgUnitSelectField.component.js b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/SingleOrgUnitSelectField.component.js index 6c252f087b..7946993a79 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/SingleOrgUnitSelectField.component.js +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/OrgUnitField/SingleOrgUnitSelectField.component.js @@ -13,7 +13,7 @@ const getStyles = () => ({ type OrgUnitValue = { id: string, - name: string, + displayName: string, path: string, } @@ -44,7 +44,7 @@ class SingleOrgUnitSelectFieldPlain extends React.Component { const { classes } = this.props; return (
- {selectedOrgUnit.name} + {selectedOrgUnit.displayName}
); } @@ -52,7 +52,7 @@ class SingleOrgUnitSelectFieldPlain extends React.Component { onSelectOrgUnit = (orgUnit: Object) => { this.props.onBlur({ id: orgUnit.id, - name: orgUnit.displayName, + displayName: orgUnit.displayName, path: orgUnit.path, }); } diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js index 2dbf0c7a20..02b607df90 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js @@ -12,6 +12,8 @@ const NewEnrollmentRelationshipPlain = theme, onSave, programId, + originTeiId, + inheritedAttributes, orgUnitId, duplicatesReviewPageSize, renderDuplicatesDialogActions, @@ -35,6 +37,8 @@ const NewEnrollmentRelationshipPlain = renderDuplicatesDialogActions={renderDuplicatesDialogActions} renderDuplicatesCardActions={renderDuplicatesCardActions} ExistingUniqueValueDialogActions={ExistingUniqueValueDialogActions} + teiId={originTeiId} + trackedEntityInstanceAttributes={inheritedAttributes} /> ); }; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js index de3f6cee6f..745929f7e3 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js @@ -6,11 +6,14 @@ import type { SaveForEnrollmentAndTeiRegistration, ExistingUniqueValueDialogActionsComponent, } from '../../../../../../DataEntries'; +import type { InputAttribute } from '../../../../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; export type Props = {| theme: Theme, programId: string, orgUnitId: string, + originTeiId: ?string, + inheritedAttributes: Array, enrollmentMetadata?: Enrollment, onSave: SaveForEnrollmentAndTeiRegistration, duplicatesReviewPageSize: number, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js index 292a1d1530..940aa904fd 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js @@ -12,7 +12,13 @@ type Props = { export class RegisterTeiDataEntryComponent extends React.Component { render() { - const { showDataEntry, programId, onSaveWithoutEnrollment, onSaveWithEnrollment, ...passOnProps } = this.props; + const { + showDataEntry, + programId, + onSaveWithoutEnrollment, + onSaveWithEnrollment, + ...passOnProps + } = this.props; if (!showDataEntry) { return null; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js index 9a9a7070af..f04efc61c2 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js @@ -58,12 +58,14 @@ const DialogButtons = ({ onCancel, onSave, trackedEntityName }) => ( const RegisterTeiPlain = ({ dataEntryId, onLink, + originTeiId, onSaveWithoutEnrollment, onSaveWithEnrollment, onGetUnsavedAttributeValues, trackedEntityName, trackedEntityTypeId, selectedScopeId, + inheritedAttributes, classes, }: ComponentProps) => { const { resultsPageSize } = useContext(ResultsPageSizeContext); @@ -110,6 +112,8 @@ const RegisterTeiPlain = ({ renderDuplicatesDialogActions={renderDuplicatesDialogActions} renderDuplicatesCardActions={renderDuplicatesCardActions} ExistingUniqueValueDialogActions={ExistingUniqueValueDialogActions} + originTeiId={originTeiId} + inheritedAttributes={inheritedAttributes} /> { @@ -16,6 +18,17 @@ export const RegisterTei = ({ const error = useSelector(({ newRelationshipRegisterTei }) => (newRelationshipRegisterTei.error)); const selectedScopeId = suggestedProgramId || trackedEntityTypeId; const { trackedEntityName } = useScopeInfo(selectedScopeId); + const { + inheritedAttributes, + originTeiId, + isLoading: isLoadingAttributes } = useInheritedAttributeValues({ + teiId, + trackedEntityTypeId, + }); + + if (isLoadingAttributes) { + return null; + } return ( ); }; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js index 0de2b570ba..2d19530459 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js @@ -1,4 +1,6 @@ // @flow +import type { InputAttribute } from '../../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; + export type SharedProps = {| onLink: (teiId: string, values: Object) => void, onGetUnsavedAttributeValues?: ?Function, @@ -7,6 +9,7 @@ export type SharedProps = {| export type ContainerProps = {| suggestedProgramId: string, + teiId: string, onSave: (teiPayload: Object) => void, ...SharedProps, |}; @@ -16,6 +19,8 @@ export type ComponentProps = {| error: string, dataEntryId: string, trackedEntityName: ?string, + originTeiId: ?string, + inheritedAttributes: Array, onSaveWithEnrollment: () => void, onSaveWithoutEnrollment: () => void, ...SharedProps, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js index df27fb1125..135dafe53f 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js @@ -76,6 +76,7 @@ export const TrackedEntityRelationshipsWrapper = ({ suggestedProgramId={suggestedProgramId} onLink={onLinkToTrackedEntityFromSearch} onSave={onLinkToTrackedEntityFromRegistration} + teiId={teiId} onGetUnsavedAttributeValues={() => console.log('get unsaved')} trackedEntityTypeId={selectedTrackedEntityTypeId} /> diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js new file mode 100644 index 0000000000..b8fd418738 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js @@ -0,0 +1,47 @@ +// @flow +import type { InputAttribute } from '../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; +import { useApiDataQuery } from '../../../../utils/reactQueryHelpers'; +import { getTrackedEntityTypeThrowIfNotFound } from '../../../../metaData'; + +type Props = { + teiId: string, + trackedEntityTypeId: string, +}; + +type Return = { + inheritedAttributes: Array, + originTeiId: ?string, + isLoading: boolean, +}; +export const useInheritedAttributeValues = ({ teiId, trackedEntityTypeId }: Props): Return => { + const trackedEntityType = getTrackedEntityTypeThrowIfNotFound(trackedEntityTypeId); + const inheritedAttributeIds = trackedEntityType.attributes.reduce((acc, attribute) => { + if (attribute.inherit) { + acc.push(attribute.id); + } + return acc; + }, []); + + const { data, isLoading } = useApiDataQuery( + ['inheritedAttributeValues', teiId], + { + resource: 'tracker/trackedEntities', + id: teiId, + params: { + fields: ['attributes'], + }, + }, { + enabled: !!teiId, + select: (response) => { + const attributes = response.attributes || []; + return attributes + .filter(attribute => inheritedAttributeIds.includes(attribute.attribute)); + }, + }); + + return { + inheritedAttributes: data ?? [], + originTeiId: null, + isLoading, + }; +}; diff --git a/src/core_modules/capture-core/metaData/DataElement/DataElement.js b/src/core_modules/capture-core/metaData/DataElement/DataElement.js index 5555d4842e..78fe1d959a 100644 --- a/src/core_modules/capture-core/metaData/DataElement/DataElement.js +++ b/src/core_modules/capture-core/metaData/DataElement/DataElement.js @@ -35,6 +35,7 @@ export class DataElement { _displayInReports: boolean; _icon: Icon | void; _unique: ?Unique; + _inherit: boolean; _searchable: ?boolean; _url: ?string; _attributeValues: Array @@ -157,6 +158,14 @@ export class DataElement { return this._unique; } + get inherit(): boolean { + return this._inherit; + } + + set inherit(value: boolean) { + this._inherit = value; + } + set searchable(searchable: boolean) { this._searchable = searchable; } diff --git a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js index 18e56e1869..27d1222735 100644 --- a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js +++ b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js @@ -164,6 +164,7 @@ export class DataElementFactory { dataElement.compulsory = cachedProgramTrackedEntityAttribute.mandatory; dataElement.code = cachedTrackedEntityAttribute.code; dataElement.attributeValues = cachedTrackedEntityAttribute.attributeValues; + dataElement.inherit = cachedTrackedEntityAttribute.inherit; dataElement.name = this._getAttributeTranslation( cachedTrackedEntityAttribute.translations, diff --git a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js index bc27da80ed..723d148d3c 100644 --- a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js +++ b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js @@ -112,6 +112,7 @@ export class DataElementFactory { cachedAttribute.translations, DataElementFactory.translationPropertyNames.DESCRIPTION) || cachedAttribute.description; o.displayInForms = true; + o.inherit = cachedAttribute.inherit; o.displayInReports = cachedTrackedEntityTypeAttribute.displayInList; o.disabled = false; o.type = cachedAttribute.valueType; diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js b/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js index 8624bb577e..7658ff4a8b 100644 --- a/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js +++ b/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js @@ -7,7 +7,7 @@ export const storeTrackedEntityAttributes = (ids: Array) => { resource: 'trackedEntityAttributes', params: { fields: 'id,displayName,displayShortName,displayFormName,description,valueType,optionSetValue,unique,orgunitScope,' + - 'pattern,code,attributeValues,translations[property,locale,value],optionSet[id]', + 'pattern,code,attributeValues,inherit,translations[property,locale,value],optionSet[id]', filter: `id:in:[${ids.join(',')}]`, pageSize: ids.length, }, diff --git a/src/core_modules/capture-core/storageControllers/cache.types.js b/src/core_modules/capture-core/storageControllers/cache.types.js index 49ce4a64ca..01ba461309 100644 --- a/src/core_modules/capture-core/storageControllers/cache.types.js +++ b/src/core_modules/capture-core/storageControllers/cache.types.js @@ -26,6 +26,7 @@ export type CachedTrackedEntityAttribute = { translations: Array, valueType: string, optionSetValue: boolean, + inherit: boolean, optionSet: { id: string }, unique: ?boolean, orgunitScope: ?boolean, From 2b785d02ca328b703e9426e14bf067f257e84aa2 Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Thu, 16 Nov 2023 12:47:59 +0100 Subject: [PATCH 2/5] feat: tea without enrollment --- .../hooks/useFormValues.js | 3 +- .../TeiRegistrationEntry.container.js | 40 ++++++++++++++++--- .../TeiRegistrationEntry.types.js | 2 + .../hooks/useFormValuesFromSearchTerms.js | 26 ++++++++---- .../components/Pages/New/NewPage.types.js | 3 +- .../RegistrationDataEntry.types.js | 4 +- .../DataEntryTrackedEntityInstance.js | 2 + .../dataEntryTrackedEntityInstance.types.js | 2 + 8 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js index d84c5d278d..679d973ecb 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js @@ -8,6 +8,7 @@ import type { RenderFoundation } from '../../../../metaData'; import { convertClientToForm, convertServerToClient } from '../../../../converters'; import { subValueGetterByElementType } from './getSubValueForTei'; import type { QuerySingleResource } from '../../../../utils/api/api.types'; +import { dataElementTypes } from '../../../../metaData'; type InputProgramData = { attributes: Array<{ @@ -32,7 +33,7 @@ export type InputAttribute = { displayName: string, lastUpdated: string, value: string, - valueType: string, + valueType: $Keys, }; type InputForm = { diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.container.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.container.js index 4c3b8badab..7d017181b9 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.container.js @@ -11,19 +11,37 @@ import { useFormValuesFromSearchTerms } from './hooks/useFormValuesFromSearchTer import { dataEntryHasChanges } from '../../DataEntry/common/dataEntryHasChanges'; import { useMetadataForRegistrationForm } from '../common/TEIAndEnrollment/useMetadataForRegistrationForm'; import { useBuildTeiPayload } from './hooks/useBuildTeiPayload'; +import type { InputAttribute } from '../EnrollmentRegistrationEntry/hooks/useFormValues'; -const useInitialiseTeiRegistration = (selectedScopeId, dataEntryId, orgUnitId) => { +type Props = { + selectedScopeId: string, + dataEntryId: string, + orgUnitId: string, + inheritedAttributes: ?Array, +} +const useInitialiseTeiRegistration = ({ + selectedScopeId, + dataEntryId, + orgUnitId, + inheritedAttributes, +}: Props) => { const dispatch = useDispatch(); const { scopeType, trackedEntityName } = useScopeInfo(selectedScopeId); const { formId, formFoundation } = useMetadataForRegistrationForm({ selectedScopeId }); - const formValues = useFormValuesFromSearchTerms(); + const formValues = useFormValuesFromSearchTerms({ inheritedAttributes }); const registrationFormReady = !!formId; useEffect(() => { if (registrationFormReady && scopeType === scopeTypes.TRACKED_ENTITY_TYPE) { dispatch( startNewTeiDataEntryInitialisation( - { selectedOrgUnitId: orgUnitId, selectedScopeId, dataEntryId, formFoundation, formValues }, + { + selectedOrgUnitId: orgUnitId, + selectedScopeId, + dataEntryId, + formFoundation, + formValues, + }, )); } }, [ @@ -43,8 +61,20 @@ const useInitialiseTeiRegistration = (selectedScopeId, dataEntryId, orgUnitId) = }; -export const TeiRegistrationEntry: ComponentType = ({ selectedScopeId, id, orgUnitId, onSave, ...rest }) => { - const { trackedEntityName } = useInitialiseTeiRegistration(selectedScopeId, id, orgUnitId); +export const TeiRegistrationEntry: ComponentType = ({ + selectedScopeId, + id, + orgUnitId, + onSave, + inheritedAttributes, + ...rest +}) => { + const { trackedEntityName } = useInitialiseTeiRegistration({ + selectedScopeId, + dataEntryId: id, + orgUnitId, + inheritedAttributes, + }); const ready = useSelector(({ dataEntries }) => (!!dataEntries[id])); const dataEntry = useSelector(({ dataEntries }) => (dataEntries[id])); const { diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.types.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.types.js index 43afd9adc8..33f32eabf8 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.types.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.types.js @@ -6,6 +6,7 @@ import type { ExistingUniqueValueDialogActionsComponent } from '../withErrorMess import type { TeiPayload, } from '../../Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types'; +import type { InputAttribute } from '../EnrollmentRegistrationEntry/hooks/useFormValues'; export type OwnProps = $ReadOnly<{| id: string, @@ -16,6 +17,7 @@ export type OwnProps = $ReadOnly<{| onSave: (TeiPayload) => void, duplicatesReviewPageSize: number, isSavingInProgress?: boolean, + inheritedAttributes?: Array, renderDuplicatesCardActions?: RenderCustomCardActions, renderDuplicatesDialogActions?: (onCancel: () => void, onSave: (TeiPayload) => void) => Node, ExistingUniqueValueDialogActions: ExistingUniqueValueDialogActionsComponent, diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js index 87bdf7fa98..f32af84d01 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js @@ -1,19 +1,29 @@ // @flow -import { useEffect, useState } from 'react'; +import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { convertClientToForm } from '../../../../converters'; +import type { InputAttribute } from '../../EnrollmentRegistrationEntry/hooks/useFormValues'; +type Props = { + inheritedAttributes: ?Array, +}; -export const useFormValuesFromSearchTerms = () => { +export const useFormValuesFromSearchTerms = ({ inheritedAttributes }: Props) => { const searchTerms = useSelector(({ searchDomain }) => searchDomain.currentSearchInfo.currentSearchTerms); - const [formValues, setFormValues] = useState(); - useEffect(() => { + + return useMemo(() => { + if (inheritedAttributes) { + return inheritedAttributes + ?.reduce((acc, item) => ({ + ...acc, + [item.attribute]: convertClientToForm(item.value, item.valueType), + }), {}); + } if (searchTerms) { - const searchFormValues = searchTerms + return searchTerms ?.reduce((acc, item) => ({ ...acc, [item.id]: convertClientToForm(item.value, item.type) }), {}); - setFormValues(searchFormValues); } - }, [searchTerms]); - return formValues; + return null; + }, [inheritedAttributes, searchTerms]); }; diff --git a/src/core_modules/capture-core/components/Pages/New/NewPage.types.js b/src/core_modules/capture-core/components/Pages/New/NewPage.types.js index 899367152f..f9357422d7 100644 --- a/src/core_modules/capture-core/components/Pages/New/NewPage.types.js +++ b/src/core_modules/capture-core/components/Pages/New/NewPage.types.js @@ -1,5 +1,6 @@ // @flow import { typeof newPageStatuses } from './NewPage.constants'; +import { dataElementTypes } from '../../../metaData'; type ProgramCategories = Array<{|name: string, id: string|}> @@ -10,7 +11,7 @@ type InputAttribute = { displayName: string, lastUpdated: string, value: string, - valueType: string, + valueType: $Keys, }; export type ContainerProps = $ReadOnly<{| diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.types.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.types.js index 79a07ea96e..22e908fb66 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.types.js +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.types.js @@ -1,5 +1,7 @@ // @flow +import { dataElementTypes } from '../../../../metaData'; + type InputAttribute = { attribute: string, code: string, @@ -7,7 +9,7 @@ type InputAttribute = { displayName: string, lastUpdated: string, value: string, - valueType: string, + valueType: $Keys, }; export type OwnProps = $ReadOnly<{| diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.js index c455e3616d..20579fc2db 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.js @@ -14,6 +14,7 @@ const RelationshipTrackedEntityInstancePlain = theme, onSave, trackedEntityTypeId, + inheritedAttributes, duplicatesReviewPageSize, renderDuplicatesDialogActions, renderDuplicatesCardActions, @@ -36,6 +37,7 @@ const RelationshipTrackedEntityInstancePlain = orgUnitId={orgUnitId} teiRegistrationMetadata={teiRegistrationMetadata} selectedScopeId={teiRegistrationMetadata.form.id} + inheritedAttributes={inheritedAttributes} saveButtonText={i18n.t('Save new {{trackedEntityTypeName}} and link', { trackedEntityTypeName: trackedEntityTypeNameLC, interpolation: { escapeValue: false }, })} diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js index b4f9617052..4fcec10567 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js @@ -5,6 +5,7 @@ import type { RenderCustomCardActions } from '../../../../../../CardList'; import type { ExistingUniqueValueDialogActionsComponent, } from '../../../../../../DataEntries'; +import type { InputAttribute } from '../../../../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; export type TeiPayload = {| trackedEntity: string, @@ -23,6 +24,7 @@ export type Props = {| trackedEntityTypeId: string, onSave: TeiPayload => void, teiRegistrationMetadata?: TeiRegistration, + inheritedAttributes: Array, duplicatesReviewPageSize: number, renderDuplicatesCardActions?: RenderCustomCardActions, renderDuplicatesDialogActions?: (onCancel: () => void, onSave: (TeiPayload) => void) => Node, From e5013c3c3eedea2e6afc7937a1e1940ad5b43e59 Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Thu, 16 Nov 2023 19:58:25 +0100 Subject: [PATCH 3/5] fix: review --- .../hooks/useFormValuesFromSearchTerms.js | 13 ++++++++----- .../Enrollment/DataEntryEnrollment.component.js | 2 -- .../RegisterTei/RegisterTei.component.js | 2 -- .../RegisterTei/RegisterTei.container.js | 5 ++--- .../RegisterTei/RegisterTei.types.js | 1 - .../useInheritedAttributeValues.js | 15 ++++++--------- 6 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js index f32af84d01..42c6fbff54 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js @@ -14,14 +14,17 @@ export const useFormValuesFromSearchTerms = ({ inheritedAttributes }: Props) => return useMemo(() => { if (inheritedAttributes) { return inheritedAttributes - ?.reduce((acc, item) => ({ - ...acc, - [item.attribute]: convertClientToForm(item.value, item.valueType), - }), {}); + ?.reduce((acc, item) => { + acc[item.attribute] = convertClientToForm(item.value, item.valueType); + return acc; + }, {}); } if (searchTerms) { return searchTerms - ?.reduce((acc, item) => ({ ...acc, [item.id]: convertClientToForm(item.value, item.type) }), {}); + ?.reduce((acc, item) => { + acc[item.id] = convertClientToForm(item.value, item.type); + return acc; + }, {}); } return null; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js index 02b607df90..25b215eed7 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js @@ -12,7 +12,6 @@ const NewEnrollmentRelationshipPlain = theme, onSave, programId, - originTeiId, inheritedAttributes, orgUnitId, duplicatesReviewPageSize, @@ -37,7 +36,6 @@ const NewEnrollmentRelationshipPlain = renderDuplicatesDialogActions={renderDuplicatesDialogActions} renderDuplicatesCardActions={renderDuplicatesCardActions} ExistingUniqueValueDialogActions={ExistingUniqueValueDialogActions} - teiId={originTeiId} trackedEntityInstanceAttributes={inheritedAttributes} /> ); diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js index f04efc61c2..c22abb0c0e 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js @@ -58,7 +58,6 @@ const DialogButtons = ({ onCancel, onSave, trackedEntityName }) => ( const RegisterTeiPlain = ({ dataEntryId, onLink, - originTeiId, onSaveWithoutEnrollment, onSaveWithEnrollment, onGetUnsavedAttributeValues, @@ -112,7 +111,6 @@ const RegisterTeiPlain = ({ renderDuplicatesDialogActions={renderDuplicatesDialogActions} renderDuplicatesCardActions={renderDuplicatesCardActions} ExistingUniqueValueDialogActions={ExistingUniqueValueDialogActions} - originTeiId={originTeiId} inheritedAttributes={inheritedAttributes} /> diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.container.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.container.js index a15e97ce2c..fe9ef129b6 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.container.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.container.js @@ -20,8 +20,8 @@ export const RegisterTei = ({ const { trackedEntityName } = useScopeInfo(selectedScopeId); const { inheritedAttributes, - originTeiId, - isLoading: isLoadingAttributes } = useInheritedAttributeValues({ + isLoading: isLoadingAttributes, + } = useInheritedAttributeValues({ teiId, trackedEntityTypeId, }); @@ -41,7 +41,6 @@ export const RegisterTei = ({ selectedScopeId={selectedScopeId} error={error} trackedEntityTypeId={trackedEntityTypeId} - originTeiId={originTeiId} inheritedAttributes={inheritedAttributes} /> ); diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js index 2d19530459..dea706722b 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js @@ -19,7 +19,6 @@ export type ComponentProps = {| error: string, dataEntryId: string, trackedEntityName: ?string, - originTeiId: ?string, inheritedAttributes: Array, onSaveWithEnrollment: () => void, onSaveWithoutEnrollment: () => void, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js index b8fd418738..1c798eeaaf 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js @@ -10,17 +10,15 @@ type Props = { type Return = { inheritedAttributes: Array, - originTeiId: ?string, isLoading: boolean, }; export const useInheritedAttributeValues = ({ teiId, trackedEntityTypeId }: Props): Return => { const trackedEntityType = getTrackedEntityTypeThrowIfNotFound(trackedEntityTypeId); - const inheritedAttributeIds = trackedEntityType.attributes.reduce((acc, attribute) => { - if (attribute.inherit) { - acc.push(attribute.id); - } - return acc; - }, []); + const inheritedAttributeIds = new Set( + trackedEntityType.attributes + .filter(attribute => attribute.inherit) + .map(attribute => attribute.id), + ); const { data, isLoading } = useApiDataQuery( ['inheritedAttributeValues', teiId], @@ -35,13 +33,12 @@ export const useInheritedAttributeValues = ({ teiId, trackedEntityTypeId }: Prop select: (response) => { const attributes = response.attributes || []; return attributes - .filter(attribute => inheritedAttributeIds.includes(attribute.attribute)); + .filter(attribute => inheritedAttributeIds.has(attribute.attribute)); }, }); return { inheritedAttributes: data ?? [], - originTeiId: null, isLoading, }; }; From df3462e6c8175ae84433024bede59aa6de9e0f8b Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Tue, 21 Nov 2023 11:10:00 +0000 Subject: [PATCH 4/5] chore: review --- .../DataEntry/Enrollment/dataEntryEnrollment.types.js | 1 - .../useInheritedAttributeValues.js | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js index 745929f7e3..3b32b17d35 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js @@ -12,7 +12,6 @@ export type Props = {| theme: Theme, programId: string, orgUnitId: string, - originTeiId: ?string, inheritedAttributes: Array, enrollmentMetadata?: Enrollment, onSave: SaveForEnrollmentAndTeiRegistration, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js index 1c798eeaaf..d65d5a9c86 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js @@ -14,11 +14,12 @@ type Return = { }; export const useInheritedAttributeValues = ({ teiId, trackedEntityTypeId }: Props): Return => { const trackedEntityType = getTrackedEntityTypeThrowIfNotFound(trackedEntityTypeId); - const inheritedAttributeIds = new Set( - trackedEntityType.attributes - .filter(attribute => attribute.inherit) - .map(attribute => attribute.id), - ); + const inheritedAttributeIds = trackedEntityType.attributes?.reduce((acc, attribute) => { + if (attribute.inherit) { + acc.add(attribute.id); + } + return acc; + }, new Set()); const { data, isLoading } = useApiDataQuery( ['inheritedAttributeValues', teiId], From 03f0db9787270e6c4dcf4e033c141161428bcc85 Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Mon, 27 Nov 2023 11:27:19 +0100 Subject: [PATCH 5/5] fix: add pteas --- .../RegisterTei/RegisterTei.container.js | 6 +-- .../useInheritedAttributeValues.js | 41 +++++++++++++++---- .../query/useApiDataQuery.js | 2 +- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.container.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.container.js index fe9ef129b6..5c74a30e44 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.container.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.container.js @@ -18,12 +18,10 @@ export const RegisterTei = ({ const error = useSelector(({ newRelationshipRegisterTei }) => (newRelationshipRegisterTei.error)); const selectedScopeId = suggestedProgramId || trackedEntityTypeId; const { trackedEntityName } = useScopeInfo(selectedScopeId); - const { - inheritedAttributes, - isLoading: isLoadingAttributes, - } = useInheritedAttributeValues({ + const { inheritedAttributes, isLoading: isLoadingAttributes } = useInheritedAttributeValues({ teiId, trackedEntityTypeId, + programId: suggestedProgramId, }); if (isLoadingAttributes) { diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js index d65d5a9c86..8c526525dd 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js @@ -1,7 +1,13 @@ // @flow +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; import type { InputAttribute } from '../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; import { useApiDataQuery } from '../../../../utils/reactQueryHelpers'; -import { getTrackedEntityTypeThrowIfNotFound } from '../../../../metaData'; +import { + getProgramFromProgramIdThrowIfNotFound, + getTrackedEntityTypeThrowIfNotFound, + TrackerProgram, +} from '../../../../metaData'; type Props = { teiId: string, @@ -13,21 +19,40 @@ type Return = { isLoading: boolean, }; export const useInheritedAttributeValues = ({ teiId, trackedEntityTypeId }: Props): Return => { - const trackedEntityType = getTrackedEntityTypeThrowIfNotFound(trackedEntityTypeId); - const inheritedAttributeIds = trackedEntityType.attributes?.reduce((acc, attribute) => { - if (attribute.inherit) { - acc.add(attribute.id); + const programId = useSelector(({ newRelationshipRegisterTei }) => newRelationshipRegisterTei.programId); + const inheritedAttributeIds = useMemo(() => { + const attributeIds = new Set(); + + if (programId) { + const program = getProgramFromProgramIdThrowIfNotFound(programId); + if (program instanceof TrackerProgram) { + program.attributes.forEach((attribute) => { + if (attribute.inherit) { + attributeIds.add(attribute.id); + } + }); + } + return attributeIds; } - return acc; - }, new Set()); + + const trackedEntityType = getTrackedEntityTypeThrowIfNotFound(trackedEntityTypeId); + trackedEntityType.attributes.forEach((attribute) => { + if (attribute.inherit) { + attributeIds.add(attribute.id); + } + }); + return attributeIds; + }, [programId, trackedEntityTypeId]); + const { data, isLoading } = useApiDataQuery( - ['inheritedAttributeValues', teiId], + ['inheritedAttributeValues', teiId, programId], { resource: 'tracker/trackedEntities', id: teiId, params: { fields: ['attributes'], + program: programId, }, }, { enabled: !!teiId, diff --git a/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js b/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js index 8505d4aaff..44b0ba996f 100644 --- a/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js +++ b/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js @@ -6,7 +6,7 @@ import type { Result } from './useMetadataQuery.types'; import { ReactQueryAppNamespace } from '../reactQueryHelpers.const'; export const useApiDataQuery = ( - queryKey: Array, + queryKey: Array, queryObject: ResourceQuery, queryOptions: UseQueryOptions, ): Result => {