diff --git a/cypress/e2e/EnrollmentPage/StagesAndEventsWidget.feature b/cypress/e2e/EnrollmentPage/StagesAndEventsWidget.feature index 480c827c0a..f50fe17c60 100644 --- a/cypress/e2e/EnrollmentPage/StagesAndEventsWidget.feature +++ b/cypress/e2e/EnrollmentPage/StagesAndEventsWidget.feature @@ -21,15 +21,11 @@ Feature: User interacts with Stages and Events Widget And you see the first 5 events in the table And you see buttons in the footer list - Scenario: User can view more events + Scenario: User can view more events and then view less Given you open the enrollment page which has multiples events and stages When you click show more button in stages&event list Then more events should be displayed And reset button should be displayed - - Scenario: User can reset events - Given you open the enrollment page which has multiples events and stages - When you click show more button in stages&event list And you click reset button Then there should be 5 rows in the table diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js b/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js index 676a73b3bb..be1a87296a 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js @@ -15,7 +15,7 @@ Then('the enrollment widget should be closed', () => { Then('the enrollment widget should be opened', () => { cy.get('[data-test="widget-enrollment"]').within(() => { - cy.get('[data-test="widget-contents"]').children().should('exist'); + cy.get('[data-test="widget-enrollment-contents"]').children().should('exist'); }); }); diff --git a/packages/rules-engine/src/services/VariableService/variableService.types.js b/packages/rules-engine/src/services/VariableService/variableService.types.js index 5529c58a43..0a0b7e65d2 100644 --- a/packages/rules-engine/src/services/VariableService/variableService.types.js +++ b/packages/rules-engine/src/services/VariableService/variableService.types.js @@ -20,7 +20,6 @@ type EventMain = { +programStageId?: string, +programStageName?: string, +orgUnitId?: string, - +orgUnitName?: string, +trackedEntityInstanceId?: string, +enrollmentId?: string, +enrollmentStatus?: string, diff --git a/src/core_modules/capture-core/components/CardList/CardListItem.component.js b/src/core_modules/capture-core/components/CardList/CardListItem.component.js index d2542d3c1b..328926bdb8 100644 --- a/src/core_modules/capture-core/components/CardList/CardListItem.component.js +++ b/src/core_modules/capture-core/components/CardList/CardListItem.component.js @@ -13,6 +13,7 @@ import { searchScopes } from '../SearchBox'; import { enrollmentTypes } from './CardList.constants'; import { ListEntry } from './ListEntry.component'; import { dataElementTypes, getTrackerProgramThrowIfNotFound } from '../../metaData'; +import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; import type { ListItem, RenderCustomCardActions } from './CardList.types'; @@ -96,24 +97,24 @@ const deriveEnrollmentType = return enrollmentTypes.DONT_SHOW_TAG; }; -const deriveEnrollmentOrgUnitAndDate = (enrollments, enrollmentType, currentProgramId): {orgUnitName?: string, enrolledAt?: string} => { +const deriveEnrollmentOrgUnitIdAndDate = (enrollments, enrollmentType, currentProgramId): {orgUnitId?: string, enrolledAt?: string} => { if (!enrollments?.length) { return {}; } if (!currentProgramId && enrollments.length) { - const { orgUnitName, enrolledAt } = enrollments[0]; + const { orgUnit: orgUnitId, enrolledAt } = enrollments[0]; return { - orgUnitName, + orgUnitId, enrolledAt, }; } - const { orgUnitName, enrolledAt } = + const { orgUnit: orgUnitId, enrolledAt } = enrollments .filter(({ program }) => program === currentProgramId) .filter(({ status }) => status === enrollmentType) .sort((a, b) => moment.utc(a.lastUpdated).diff(moment.utc(b.lastUpdated)))[0] || {}; - return { orgUnitName, enrolledAt }; + return { orgUnitId, enrolledAt }; }; const deriveProgramFromEnrollment = (enrollments, currentSearchScopeType) => { @@ -137,7 +138,8 @@ const CardListItemIndex = ({ }: Props) => { const enrollments = item.tei ? item.tei.enrollments : []; const enrollmentType = deriveEnrollmentType(enrollments, currentProgramId); - const { orgUnitName, enrolledAt } = deriveEnrollmentOrgUnitAndDate(enrollments, enrollmentType, currentProgramId); + const { orgUnitId, enrolledAt } = deriveEnrollmentOrgUnitIdAndDate(enrollments, enrollmentType, currentProgramId); + const { displayName: orgUnitName } = useOrgUnitName(orgUnitId); const program = enrollments && enrollments.length ? deriveProgramFromEnrollment(enrollments, currentSearchScopeType) : undefined; diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js index 7b61054364..3e2ea0d8a9 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js @@ -9,7 +9,7 @@ import { useScopeInfo } from '../../../hooks/useScopeInfo'; import { scopeTypes } from '../../../metaData'; import { TrackedEntityInstanceDataEntry } from '../TrackedEntityInstance'; import { useCurrentOrgUnitId } from '../../../hooks/useCurrentOrgUnitId'; -import { useCoreOrgUnit } from '../../../metadataRetrieval/coreOrgUnit'; +import { useOrgUnitName } from '../../../metadataRetrieval/orgUnitName'; import type { Props, PlainProps } from './TeiRegistrationEntry.types'; import { DiscardDialog } from '../../Dialogs/DiscardDialog.component'; import { withSaveHandler } from '../../DataEntry'; @@ -56,8 +56,7 @@ const TeiRegistrationEntryPlain = const { scopeType } = useScopeInfo(selectedScopeId); const { formId, formFoundation } = useMetadataForRegistrationForm({ selectedScopeId }); const orgUnitId = useCurrentOrgUnitId(); - const { orgUnit } = useCoreOrgUnit(orgUnitId); // Tony: [DHIS2-15814] Change this to new hook - const orgUnitName = orgUnit ? orgUnit.name : ''; + const { displayName: orgUnitName } = useOrgUnitName(orgUnitId); const handleOnCancel = () => { if (!isUserInteractionInProgress) { diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.component.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.component.js index 05be58a16e..b59ad5a3a5 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.component.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.component.js @@ -75,7 +75,7 @@ export const EnrollmentPageDefaultPlain = ({ }: PlainProps) => { const [mainContentVisible, setMainContentVisibility] = useState(true); const [addRelationShipContainerElement, setAddRelationshipContainerElement] = - useState(undefined); + useState(undefined); const toggleVisibility = useCallback(() => setMainContentVisibility(current => !current), []); diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useProgramStages.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useProgramStages.js index 3c8b50285a..7d692e9867 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useProgramStages.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useProgramStages.js @@ -1,11 +1,12 @@ // @flow +import { useMemo } from 'react'; import log from 'loglevel'; import { errorCreator } from 'capture-core-utils'; import i18n from '@dhis2/d2-i18n'; import type { apiProgramStage } from 'capture-core/metaDataStoreLoaders/programs/quickStoreOperations/types'; import { Program } from '../../../../../metaData'; -export const useProgramStages = (program: Program, programStages?: Array) => { +export const useProgramStages = (program: Program, programStages?: Array) => useMemo(() => { const stages = []; if (program && programStages) { program.stages.forEach((item) => { @@ -48,4 +49,4 @@ export const useProgramStages = (program: Program, programStages?: Array { programId: event.program, programStageId: event.programStage, orgUnitId: event.orgUnit, - orgUnitName: event.orgUnitName, trackedEntityInstanceId: event.trackedEntityInstance, enrollmentId: event.enrollment, enrollmentStatus: event.enrollmentStatus, diff --git a/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.container.js b/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.container.js index 18557db76b..0aba4ccd71 100644 --- a/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.container.js +++ b/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.container.js @@ -3,15 +3,15 @@ import React, { type ComponentType, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { ScopeSelectorComponent } from './ScopeSelector.component'; import type { OwnProps } from './ScopeSelector.types'; -import { useOrganizationUnit } from './hooks'; +import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; import { resetOrgUnitIdFromScopeSelector } from './ScopeSelector.actions'; -const deriveReadiness = (lockedSelectorLoads, selectedOrgUnitId, selectedOrgUnitName) => { +const deriveReadiness = (lockedSelectorLoads, selectedOrgUnitId, selectedOrgUnitName, displayName) => { // because we want the orgUnit to be fetched and stored // before allowing the user to view the locked selector - if (selectedOrgUnitId && selectedOrgUnitName) { - return true; + if (selectedOrgUnitId && (!selectedOrgUnitName || selectedOrgUnitName !== displayName)) { + return false; } return !lockedSelectorLoads; }; @@ -32,21 +32,20 @@ export const ScopeSelector: ComponentType = ({ children, }) => { const dispatch = useDispatch(); - const { refetch: refetchOrganisationUnit, displayName } = useOrganizationUnit(); - const [selectedOrgUnit, setSelectedOrgUnit] = useState({ name: displayName, id: selectedOrgUnitId }); + const [selectedOrgUnit, setSelectedOrgUnit] = useState({ name: undefined, id: selectedOrgUnitId }); + const { displayName } = useOrgUnitName(selectedOrgUnit.id); useEffect(() => { - const missName = !selectedOrgUnit.name; - const hasDifferentId = selectedOrgUnit.id !== selectedOrgUnitId; - - selectedOrgUnitId && - (hasDifferentId || missName) && - refetchOrganisationUnit({ variables: { selectedOrgUnitId } }); - }, [selectedOrgUnitId]); // eslint-disable-line react-hooks/exhaustive-deps + if (displayName && selectedOrgUnit.name !== displayName) { + setSelectedOrgUnit(prevSelectedOrgUnit => ({ ...prevSelectedOrgUnit, name: displayName })); + } + }, [displayName, selectedOrgUnit, setSelectedOrgUnit]); useEffect(() => { - displayName && setSelectedOrgUnit(prevSelectedOrgUnit => ({ ...prevSelectedOrgUnit, name: displayName })); - }, [displayName, setSelectedOrgUnit]); + if (selectedOrgUnitId && !selectedOrgUnit.id) { + selectedOrgUnitId && setSelectedOrgUnit(prevSelectedOrgUnit => ({ ...prevSelectedOrgUnit, id: selectedOrgUnitId })); + } + }, [selectedOrgUnitId, selectedOrgUnit, setSelectedOrgUnit]); const handleSetOrgUnit = (orgUnitId, orgUnitObject) => { setSelectedOrgUnit(orgUnitObject); @@ -59,7 +58,7 @@ export const ScopeSelector: ComponentType = ({ previousOrgUnitId: app.previousOrgUnitId, } )); - const ready = deriveReadiness(lockedSelectorLoads, selectedOrgUnitId, selectedOrgUnit.name); + const ready = deriveReadiness(lockedSelectorLoads, selectedOrgUnit.id, selectedOrgUnit.name, displayName); return ( { - const { data, refetch } = useDataQuery( - useMemo( - () => ({ - organisationUnits: { - resource: 'organisationUnits', - id: ({ variables: { selectedOrgUnitId: id } }) => id, - params: { - fields: ['displayName'], - }, - }, - }), - [], - ), - { - lazy: true, - }, - ); - - return { - displayName: data?.organisationUnits?.displayName, - refetch, - }; -}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js index 78c7f1cb08..d9d400d044 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js @@ -19,6 +19,7 @@ import { Status } from './Status'; import { convertValue as convertValueServerToClient } from '../../converters/serverToClient'; import { convertValue as convertValueClientToView } from '../../converters/clientToView'; import { dataElementTypes } from '../../metaData'; +import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; import { Date } from './Date'; import { Actions } from './Actions'; @@ -68,6 +69,7 @@ export const WidgetEnrollmentPlain = ({ const [open, setOpenStatus] = useState(true); const { fromServerDate } = useTimeZoneConversion(); const geometryType = getGeometryType(enrollment?.geometry?.type); + const { displayName: orgUnitName } = useOrgUnitName(enrollment.orgUnit); return (
@@ -84,7 +86,7 @@ export const WidgetEnrollmentPlain = ({ )} {loading && } {!initError && !loading && ( -
+
{enrollment.followUp && ( @@ -125,7 +127,7 @@ export const WidgetEnrollmentPlain = ({ {i18n.t('Started at {{orgUnitName}}', { - orgUnitName: enrollment.orgUnitName, + orgUnitName, interpolation: { escapeValue: false }, })}
diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js index 71a1c43728..c0a388d46a 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js @@ -3,7 +3,7 @@ import React from 'react'; import { errorCreator } from 'capture-core-utils'; import log from 'loglevel'; import { WidgetEnrollment as WidgetEnrollmentComponent } from './WidgetEnrollment.component'; -import { useOrganizationUnit } from './hooks/useOrganizationUnit'; +import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; import { useTrackedEntityInstances } from './hooks/useTrackedEntityInstances'; import { useEnrollment } from './hooks/useEnrollment'; import { useProgram } from './hooks/useProgram'; @@ -42,7 +42,7 @@ export const WidgetEnrollment = ({ enrollments, refetch: refetchTEI, } = useTrackedEntityInstances(teiId, programId); - const { error: errorOrgUnit, displayName } = useOrganizationUnit(ownerOrgUnit); + const { error: errorOrgUnit, displayName } = useOrgUnitName(typeof ownerOrgUnit === 'string' ? ownerOrgUnit : undefined); const { error: errorLocale, locale } = useUserLocale(); const canAddNew = enrollments .filter(item => item.program === programId) diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useOrganizationUnit.js b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useOrganizationUnit.js deleted file mode 100644 index 875e86796b..0000000000 --- a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useOrganizationUnit.js +++ /dev/null @@ -1,32 +0,0 @@ -// @flow -import { useMemo } from 'react'; -import { useDataQuery } from '@dhis2/app-runtime'; - -export const useOrganizationUnit = (ownerOrgUnit: string | boolean) => { - const { error, loading, data, refetch, called } = useDataQuery( - useMemo( - () => ({ - organisationUnits: { - resource: 'organisationUnits', - id: ({ variables: { ownerOrgUnit: id } }) => id, - params: { - fields: ['displayName'], - }, - }, - }), - [], - ), - { - lazy: true, - }, - ); - - if (ownerOrgUnit && !called) { - refetch({ variables: { ownerOrgUnit } }); - } - - return { - error, - displayName: !loading && data?.organisationUnits?.displayName, - }; -}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/common.types.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/common.types.js index ccdcea6c45..791ff8fa85 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/common.types.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/common.types.js @@ -25,7 +25,6 @@ export type EnrollmentEvent = {| programId: string, programStageId: string, orgUnitId: string, - orgUnitName: string, trackedEntityInstanceId: string, enrollmentId: string, enrollmentStatus: string, diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/ScheduleDate/scheduleDate.types.js b/src/core_modules/capture-core/components/WidgetEventSchedule/ScheduleDate/scheduleDate.types.js index 84d5f037e3..e181165e6f 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/ScheduleDate/scheduleDate.types.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/ScheduleDate/scheduleDate.types.js @@ -12,6 +12,6 @@ export type Props = {| eventCountInOrgUnit: number, suggestedScheduleDate?: ?string, hideDueDate?: boolean, - orgUnit: Object, + orgUnit: { id: string, name: string }, ...CssClasses, |}; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js index 367ab395bb..489ce42dbc 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js @@ -4,7 +4,7 @@ import i18n from '@dhis2/d2-i18n'; import { useDispatch } from 'react-redux'; import moment from 'moment'; import { getProgramAndStageForProgram, TrackerProgram, getProgramEventAccess } from '../../metaData'; -import { useOrganisationUnit } from '../../dataQueries'; +import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; import { useLocationQuery } from '../../utils/routing'; import type { ContainerProps } from './widgetEventSchedule.types'; import { WidgetEventScheduleComponent } from './WidgetEventSchedule.component'; @@ -35,7 +35,7 @@ export const WidgetEventSchedule = ({ }: ContainerProps) => { const { program, stage } = useMemo(() => getProgramAndStageForProgram(programId, stageId), [programId, stageId]); const dispatch = useDispatch(); - const { orgUnit } = useOrganisationUnit(orgUnitId, 'displayName'); + const orgUnit = { id: orgUnitId, name: useOrgUnitName(orgUnitId).displayName }; const { programStageScheduleConfig } = useScheduleConfigFromProgramStage(stageId); const { programConfig } = useScheduleConfigFromProgram(programId); const suggestedScheduleDate = useDetermineSuggestedScheduleDate({ diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js index 868e1c5889..0c900af9a4 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import { convertValue } from '../../../../converters/serverToClient'; import { dataElementTypes } from '../../../../metaData'; +import { useOrgUnitNames } from '../../../../metadataRetrieval/orgUnitName'; const convertDate = date => convertValue(date, dataElementTypes.DATE); @@ -14,16 +15,26 @@ const getClientFormattedDataValuesAsObject = (dataValues, elementsById) => return acc; }, {}); -export const useEvents = (enrollment: any, elementsById: Array) => - useMemo( +const getOrgUnitIds = (enrollment: any): Array => + (enrollment ? enrollment.events.reduce((acc, event) => { + if (event.orgUnit) { + acc.push(event.orgUnit); + } + return acc; + }, []) : []); + +export const useEvents = (enrollment: any, elementsById: Array) => { + const orgUnitIds = useMemo(() => getOrgUnitIds(enrollment), [enrollment]); + const { orgUnitNames } = useOrgUnitNames(orgUnitIds); + return useMemo( () => - enrollment && + enrollment && orgUnitNames && enrollment.events.map(event => ({ eventId: event.event, programId: event.program, programStageId: event.programStage, orgUnitId: event.orgUnit, - orgUnitName: event.orgUnitName, + orgUnitName: orgUnitNames[event.orgUnit], trackedEntityInstanceId: event.trackedEntityInstance, enrollmentId: event.enrollment, enrollmentStatus: event.enrollmentStatus, @@ -32,5 +43,6 @@ export const useEvents = (enrollment: any, elementsById: Array) => dueDate: convertDate(event.dueDate), ...getClientFormattedDataValuesAsObject(event.dataValues, elementsById), })), - [elementsById, enrollment], + [elementsById, enrollment, orgUnitNames], ); +}; diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageDetail/StageDetail.component.js b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageDetail/StageDetail.component.js index 6d5da181fa..0cc28bc98c 100644 --- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageDetail/StageDetail.component.js +++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageDetail/StageDetail.component.js @@ -103,6 +103,11 @@ const StageDetailPlain = (props: Props) => { onCreateNew(stageId); }, [onCreateNew, stageId]); + const handleShowMore = useCallback(() => { + const nextRowIndex = Math.min(events.length, displayedRowNumber + DEFAULT_NUMBER_OF_ROW); + setDisplayedRowNumber(nextRowIndex); + }, [events, displayedRowNumber, setDisplayedRowNumber]); + function renderHeader() { const headerCells = headerColumns .map(column => ( @@ -181,16 +186,14 @@ const StageDetailPlain = (props: Props) => { } function renderFooter() { - const renderShowMoreButton = () => (events.length > DEFAULT_NUMBER_OF_ROW + const renderShowMoreButton = () => (dataSource && !loading + && events.length > DEFAULT_NUMBER_OF_ROW && displayedRowNumber < events.length ?