From ed21ac81fbbed031a47c1c023d4df9d05fdf1026 Mon Sep 17 00:00:00 2001 From: alaa-yahia Date: Mon, 19 Aug 2024 18:24:09 +0300 Subject: [PATCH 1/8] fix: app crash on invalid programid --- .../Enrollment/EnrollmentPage.component.js | 2 -- .../Enrollment/MissingMessage.component.js | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.component.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.component.js index 9de818c2b2..9b2112b175 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.component.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.component.js @@ -6,7 +6,6 @@ import { compose } from 'redux'; import type { Props } from './EnrollmentPage.types'; import { enrollmentPageStatuses } from './EnrollmentPage.constants'; import { LoadingMaskForPage } from '../../LoadingMasks/LoadingMaskForPage.component'; -import { withErrorMessageHandler } from '../../../HOC'; import { MissingMessage } from './MissingMessage.component'; import { EnrollmentPageDefault } from './EnrollmentPageDefault'; import { TopBar } from './TopBar.container'; @@ -55,6 +54,5 @@ const EnrollmentPagePlain = ({ ); export const EnrollmentPageComponent: ComponentType<$Diff> = compose( - withErrorMessageHandler(), withStyles(getStyles), )(EnrollmentPagePlain); diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.js b/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.js index 17d36faedd..a0f06f6230 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.js @@ -26,14 +26,14 @@ export const missingStatuses = { MISSING_ENROLLMENT_SELECTION_ADD_NEW: 'MISSING_ENROLLMENT_SELECTION_ADD_NEW', MISSING_PROGRAM_CATEGORIES_SELECTION: 'MISSING_PROGRAM_CATEGORIES_SELECTION', MISSING_PROGRAM_SELECTION: 'MISSING_PROGRAM_SELECTION', + INVALID_PROGRAM_ID: 'INVALID_PROGRAM_ID', }; const useMissingStatus = () => { const [missingStatus, setStatus] = useState(null); const { programId, enrollmentId, teiId } = useLocationQuery(); - - const { scopeType, tetId: scopeTetId } = useScopeInfo(programId); + const { scopeType, tetId: scopeTetId, programName } = useScopeInfo(programId); const { programSelectionIsIncomplete } = useMissingCategoriesInProgramSelection(); const { programHasEnrollments, @@ -47,8 +47,9 @@ const useMissingStatus = () => { useEffect(() => { const selectedProgramIsTracker = programId && scopeType === scopeTypes.TRACKER_PROGRAM; const selectedProgramIsEvent = programId && scopeType === scopeTypes.EVENT_PROGRAM; - - if (enrollmentAccessLevel === enrollmentAccessLevels.LIMITED_ACCESS) { + if (programId && !programName) { + setStatus(missingStatuses.INVALID_PROGRAM_ID); + } else if (enrollmentAccessLevel === enrollmentAccessLevels.LIMITED_ACCESS) { setStatus(missingStatuses.PROTECTED_PROGRAM_WITH_BREAKING_THE_GLASS); } else if (enrollmentAccessLevel === enrollmentAccessLevels.NO_ACCESS) { setStatus(missingStatuses.RESTRICTED_PROGRAM_NO_ACCESS); @@ -71,6 +72,7 @@ const useMissingStatus = () => { } }, [ programId, + programName, programSelectionIsIncomplete, programHasEnrollments, programHasActiveEnrollments, @@ -132,6 +134,15 @@ export const MissingMessage = withStyles(getStyles)(({ const { programName, trackedEntityName: selectedTetName } = useScopeInfo(programId); return (<> + { + missingStatus === missingStatuses.INVALID_PROGRAM_ID && + + {i18n.t('Program with id "{{programId}}" does not exist', { + programId, + interpolation: { escapeValue: false }, + })} + + } { missingStatus === missingStatuses.MISSING_PROGRAM_SELECTION && From c8aca0de9e070f660ffb6b7cf8d1bd94b4a1e272 Mon Sep 17 00:00:00 2001 From: alaa-yahia Date: Fri, 23 Aug 2024 01:54:24 +0300 Subject: [PATCH 2/8] chore: display scope selector when there is an error --- .../e2e/ScopeSelector/ScopeSelector.feature | 2 - i18n/en.pot | 4 +- .../components/Pages/EnrollmentAddEvent/x.js | 164 +++++++++++++++++ .../components/Pages/New/NewPage.component.js | 174 +++++++++--------- 4 files changed, 257 insertions(+), 87 deletions(-) create mode 100644 src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/x.js diff --git a/cypress/e2e/ScopeSelector/ScopeSelector.feature b/cypress/e2e/ScopeSelector/ScopeSelector.feature index a53da4790d..ec80a5d2b0 100644 --- a/cypress/e2e/ScopeSelector/ScopeSelector.feature +++ b/cypress/e2e/ScopeSelector/ScopeSelector.feature @@ -28,8 +28,6 @@ Feature: User uses the ScopeSelector to navigate When you select both org unit and program Malaria case registration Then you should see the table - # DHIS2-16010 - App crashes on invalid program id - @skip Scenario: Main page > Url with invalid program id Given you land on a main page with an invalid program id Then you should see error message diff --git a/i18n/en.pot b/i18n/en.pot index d8d607944f..f8051620ca 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-08-08T11:49:13.423Z\n" -"PO-Revision-Date: 2024-08-08T11:49:13.423Z\n" +"POT-Creation-Date: 2024-08-22T21:46:48.644Z\n" +"PO-Revision-Date: 2024-08-22T21:46:48.644Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/x.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/x.js new file mode 100644 index 0000000000..2d2b846df3 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/x.js @@ -0,0 +1,164 @@ +// @flow +import React, { useMemo } from 'react'; +import { compose } from 'redux'; +import i18n from '@dhis2/d2-i18n'; +import { NoticeBox, spacersNum } from '@dhis2/ui'; +import withStyles from '@material-ui/core/styles/withStyles'; +import { useEnrollmentAddEventTopBar, EnrollmentAddEventTopBar } from './TopBar'; +import { EnrollmentAddEventPageDefault } from './EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container'; +import { useLocationQuery } from '../../../utils/routing'; +import { + IdTypes, + useValidatedIDsFromCache, +} from '../../../utils/cachedDataHooks/useValidatedIDsFromCache'; +import { useCommonEnrollmentDomainData } from '../common/EnrollmentOverviewDomain'; +import { EnrollmentAddEventPageStatuses } from './EnrollmentAddEventPage.constants'; +import { LoadingMaskForPage } from '../../LoadingMasks'; +import { type Props } from './EnrollmentAddEventPage.types'; +import { + useEnrollmentPageLayout, +} from '../common/EnrollmentOverviewDomain/EnrollmentPageLayout/hooks/useEnrollmentPageLayout'; +import { DataStoreKeyByPage } from '../common/EnrollmentOverviewDomain/EnrollmentPageLayout'; +import { DefaultPageLayout } from './PageLayout/DefaultPageLayout.constants'; +import { useProgramInfo } from '../../../hooks/useProgramInfo'; + +const getStyles = () => ({ + container: { + padding: '24px 24px 16px 24px', + }, + informativeMessage: { + marginLeft: spacersNum.dp16, + marginTop: spacersNum.dp24, + marginRight: spacersNum.dp16, + }, +}); + +const EnrollmentAddEventPagePlain = ({ classes }: Props) => { + const { teiId, programId, orgUnitId, enrollmentId, stageId } = useLocationQuery(); + const { valid: validIds, loading, error: validatedIdsError } = useValidatedIDsFromCache({ programId, orgUnitId }); + const { + enrollment, + attributeValues, + error: commonDataError, + } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); + const { pageLayout, isLoading } = useEnrollmentPageLayout({ + selectedScopeId: validIds[IdTypes.PROGRAM_ID]?.id, + dataStoreKey: DataStoreKeyByPage.ENROLLMENT_EVENT_NEW, + defaultPageLayout: DefaultPageLayout, + }); + + const { program } = useProgramInfo(programId); + const selectedProgramStage = program?.stages.get(stageId); + const trackedEntityName = program?.trackedEntityType?.name; + + const { + handleSetOrgUnitId, + handleResetOrgUnitId, + handleResetProgramId, + handleResetEnrollmentId, + handleResetTeiId, + handleResetStageId, + handleResetEventId, + teiDisplayName, + enrollmentsAsOptions, + teiSelectorFailure, + userInteractionInProgress, + } = useEnrollmentAddEventTopBar(teiId, programId, enrollment); + + const pageIsInvalid = (!loading && !Object.values(validIds)?.every(Id => Id?.valid)) || commonDataError || validatedIdsError; + const pageStatus = useMemo(() => { + if (!programId || !enrollmentId || !teiId) { + return EnrollmentAddEventPageStatuses.MISSING_REQUIRED_VALUES; + } + if (pageIsInvalid && validIds[IdTypes.PROGRAM_ID]?.valid && !validIds[IdTypes.ORG_UNIT_ID]?.valid) { + return EnrollmentAddEventPageStatuses.ORG_UNIT_INVALID; + } + if (pageIsInvalid && !validIds[IdTypes.PROGRAM_ID]?.valid) { + return EnrollmentAddEventPageStatuses.PROGRAM_INVALID; + } + if (pageIsInvalid) { + return EnrollmentAddEventPageStatuses.PAGE_INVALID; + } + if (loading || isLoading) { + return EnrollmentAddEventPageStatuses.LOADING; + } + return EnrollmentAddEventPageStatuses.DEFAULT; + }, [enrollmentId, isLoading, loading, pageIsInvalid, programId, teiId, validIds]); + + const getErrorMessage = () => { + switch (pageStatus) { + case EnrollmentAddEventPageStatuses.MISSING_REQUIRED_VALUES: + return i18n.t('Page is missing required values from URL'); + case EnrollmentAddEventPageStatuses.PROGRAM_INVALID: + return i18n.t('Program is not valid'); + case EnrollmentAddEventPageStatuses.ORG_UNIT_INVALID: + return i18n.t('Org unit is not valid with current program'); + case EnrollmentAddEventPageStatuses.PAGE_INVALID: + return i18n.t('There was an error opening the Page'); + default: + return ''; + } + }; + + const renderContent = () => { + if (pageStatus === EnrollmentAddEventPageStatuses.LOADING) { + return ; + } + + if (pageStatus === EnrollmentAddEventPageStatuses.DEFAULT) { + return ( + + ); + } + + return ( +
+ + {getErrorMessage()} + +
+ ); + }; + + + return ( + <> + +
+ {renderContent()} +
+ + ); +}; + +export const EnrollmentAddEventPage = compose( + withStyles(getStyles), +)(EnrollmentAddEventPagePlain); diff --git a/src/core_modules/capture-core/components/Pages/New/NewPage.component.js b/src/core_modules/capture-core/components/Pages/New/NewPage.component.js index a7b070193b..a2d3c6f3ca 100644 --- a/src/core_modules/capture-core/components/Pages/New/NewPage.component.js +++ b/src/core_modules/capture-core/components/Pages/New/NewPage.component.js @@ -6,10 +6,10 @@ import type { ComponentType } from 'react'; import withStyles from '@material-ui/core/styles/withStyles'; import { OrgUnitFetcher } from 'capture-core/components/OrgUnitFetcher'; import i18n from '@dhis2/d2-i18n'; -import { Button } from '@dhis2/ui'; +import { Button , NoticeBox} from '@dhis2/ui'; import { TopBar } from './TopBar.container'; import type { ContainerProps, Props } from './NewPage.types'; -import { withErrorMessageHandler, withLoadingIndicator } from '../../../HOC'; +import { withLoadingIndicator } from '../../../HOC'; import { NEW_TEI_DATA_ENTRY_ID, newPageStatuses } from './NewPage.constants'; import { useScopeInfo } from '../../../hooks/useScopeInfo'; import { RegistrationDataEntry } from './RegistrationDataEntry'; @@ -42,6 +42,7 @@ const NewPagePlain = ({ trackedEntityName, teiDisplayName, trackedEntityInstanceAttributes, + error, }: Props) => { const { scopeType } = useScopeInfo(currentScopeId); const [selectedScopeId, setScopeId] = useState(currentScopeId); @@ -60,8 +61,7 @@ const NewPagePlain = ({ } else { showDefaultViewOnNewPage(); } - }, - [ + }, [ programCategorySelectionIncomplete, orgUnitSelectionIncomplete, showMessageToSelectOrgUnitOnNewPage, @@ -70,96 +70,104 @@ const NewPagePlain = ({ categoryOptionIsInvalidForOrgUnit, showMessageThatCategoryOptionIsInvalidForOrgUnit, ]); + const orgUnitId = useSelector(({ currentSelections }) => currentSelections.orgUnitId); - return (<> - -
- { - !writeAccess ? - - : - - { - newPageStatus === newPageStatuses.DEFAULT && - - } + const renderContent = () => { + if (error) { + return ( + + {error} + + ); + } - { - newPageStatus === newPageStatuses.WITHOUT_ORG_UNIT_SELECTED && - <> - - {i18n.t('Choose an organisation unit to start reporting')} - - - - } + if (!writeAccess) { + return ( + + ); + } + + return ( + + {newPageStatus === newPageStatuses.DEFAULT && ( + + )} - { - newPageStatus === newPageStatuses.WITHOUT_PROGRAM_CATEGORY_SELECTED && - (() => { - const missingCategories = missingCategoriesInProgramSelection.reduce((acc, { name }, index) => { - if ((index + 1 === missingCategoriesInProgramSelection.length)) { - return `${acc} ${name} ${missingCategoriesInProgramSelection.length > 1 ? 'categories' : 'category'}`; - } - return `${acc} ${name},`; - }, ''); + {newPageStatus === newPageStatuses.WITHOUT_ORG_UNIT_SELECTED && ( + <> + + {i18n.t('Choose an organisation unit to start reporting')} + + + + )} - return ( - - {i18n.t('Choose the {{missingCategories}} to start reporting', { - missingCategories, interpolation: { escapeValue: false }, - })} - - ); - })() - } + {newPageStatus === newPageStatuses.WITHOUT_PROGRAM_CATEGORY_SELECTED && ( + + {i18n.t('Choose the {{missingCategories}} to start reporting', { + missingCategories: missingCategoriesInProgramSelection.reduce((acc, { name }, index) => { + if ((index + 1 === missingCategoriesInProgramSelection.length)) { + return `${acc} ${name} ${missingCategoriesInProgramSelection.length > 1 ? 'categories' : 'category'}`; + } + return `${acc} ${name},`; + }, ''), + interpolation: { escapeValue: false }, + })} + + )} - { - newPageStatus === newPageStatuses.CATEGORY_OPTION_INVALID_FOR_ORG_UNIT && ( - - {i18n.t( - 'The category option is not valid for the selected organisation unit. Please select a valid combination.', - )} - - ) - } + {newPageStatus === newPageStatuses.CATEGORY_OPTION_INVALID_FOR_ORG_UNIT && ( + + {i18n.t( + 'The category option is not valid for the selected organisation unit. Please select a valid combination.', + )} + + )} + + ); + }; - - } -
- ); + return ( + <> + +
+ {renderContent()} +
+ + ); }; export const NewPageComponent: ComponentType = compose( withLoadingIndicator(), - withErrorMessageHandler(), withStyles(getStyles), )(NewPagePlain); From 92df5eef5b1dc200a4d5883a88643a23ca100dec Mon Sep 17 00:00:00 2001 From: alaa-yahia Date: Mon, 26 Aug 2024 12:42:10 +0300 Subject: [PATCH 3/8] fix: linter issue --- .../components/Pages/EnrollmentAddEvent/x.js | 164 ------------------ .../components/Pages/New/NewPage.component.js | 2 +- 2 files changed, 1 insertion(+), 165 deletions(-) delete mode 100644 src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/x.js diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/x.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/x.js deleted file mode 100644 index 2d2b846df3..0000000000 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/x.js +++ /dev/null @@ -1,164 +0,0 @@ -// @flow -import React, { useMemo } from 'react'; -import { compose } from 'redux'; -import i18n from '@dhis2/d2-i18n'; -import { NoticeBox, spacersNum } from '@dhis2/ui'; -import withStyles from '@material-ui/core/styles/withStyles'; -import { useEnrollmentAddEventTopBar, EnrollmentAddEventTopBar } from './TopBar'; -import { EnrollmentAddEventPageDefault } from './EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container'; -import { useLocationQuery } from '../../../utils/routing'; -import { - IdTypes, - useValidatedIDsFromCache, -} from '../../../utils/cachedDataHooks/useValidatedIDsFromCache'; -import { useCommonEnrollmentDomainData } from '../common/EnrollmentOverviewDomain'; -import { EnrollmentAddEventPageStatuses } from './EnrollmentAddEventPage.constants'; -import { LoadingMaskForPage } from '../../LoadingMasks'; -import { type Props } from './EnrollmentAddEventPage.types'; -import { - useEnrollmentPageLayout, -} from '../common/EnrollmentOverviewDomain/EnrollmentPageLayout/hooks/useEnrollmentPageLayout'; -import { DataStoreKeyByPage } from '../common/EnrollmentOverviewDomain/EnrollmentPageLayout'; -import { DefaultPageLayout } from './PageLayout/DefaultPageLayout.constants'; -import { useProgramInfo } from '../../../hooks/useProgramInfo'; - -const getStyles = () => ({ - container: { - padding: '24px 24px 16px 24px', - }, - informativeMessage: { - marginLeft: spacersNum.dp16, - marginTop: spacersNum.dp24, - marginRight: spacersNum.dp16, - }, -}); - -const EnrollmentAddEventPagePlain = ({ classes }: Props) => { - const { teiId, programId, orgUnitId, enrollmentId, stageId } = useLocationQuery(); - const { valid: validIds, loading, error: validatedIdsError } = useValidatedIDsFromCache({ programId, orgUnitId }); - const { - enrollment, - attributeValues, - error: commonDataError, - } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); - const { pageLayout, isLoading } = useEnrollmentPageLayout({ - selectedScopeId: validIds[IdTypes.PROGRAM_ID]?.id, - dataStoreKey: DataStoreKeyByPage.ENROLLMENT_EVENT_NEW, - defaultPageLayout: DefaultPageLayout, - }); - - const { program } = useProgramInfo(programId); - const selectedProgramStage = program?.stages.get(stageId); - const trackedEntityName = program?.trackedEntityType?.name; - - const { - handleSetOrgUnitId, - handleResetOrgUnitId, - handleResetProgramId, - handleResetEnrollmentId, - handleResetTeiId, - handleResetStageId, - handleResetEventId, - teiDisplayName, - enrollmentsAsOptions, - teiSelectorFailure, - userInteractionInProgress, - } = useEnrollmentAddEventTopBar(teiId, programId, enrollment); - - const pageIsInvalid = (!loading && !Object.values(validIds)?.every(Id => Id?.valid)) || commonDataError || validatedIdsError; - const pageStatus = useMemo(() => { - if (!programId || !enrollmentId || !teiId) { - return EnrollmentAddEventPageStatuses.MISSING_REQUIRED_VALUES; - } - if (pageIsInvalid && validIds[IdTypes.PROGRAM_ID]?.valid && !validIds[IdTypes.ORG_UNIT_ID]?.valid) { - return EnrollmentAddEventPageStatuses.ORG_UNIT_INVALID; - } - if (pageIsInvalid && !validIds[IdTypes.PROGRAM_ID]?.valid) { - return EnrollmentAddEventPageStatuses.PROGRAM_INVALID; - } - if (pageIsInvalid) { - return EnrollmentAddEventPageStatuses.PAGE_INVALID; - } - if (loading || isLoading) { - return EnrollmentAddEventPageStatuses.LOADING; - } - return EnrollmentAddEventPageStatuses.DEFAULT; - }, [enrollmentId, isLoading, loading, pageIsInvalid, programId, teiId, validIds]); - - const getErrorMessage = () => { - switch (pageStatus) { - case EnrollmentAddEventPageStatuses.MISSING_REQUIRED_VALUES: - return i18n.t('Page is missing required values from URL'); - case EnrollmentAddEventPageStatuses.PROGRAM_INVALID: - return i18n.t('Program is not valid'); - case EnrollmentAddEventPageStatuses.ORG_UNIT_INVALID: - return i18n.t('Org unit is not valid with current program'); - case EnrollmentAddEventPageStatuses.PAGE_INVALID: - return i18n.t('There was an error opening the Page'); - default: - return ''; - } - }; - - const renderContent = () => { - if (pageStatus === EnrollmentAddEventPageStatuses.LOADING) { - return ; - } - - if (pageStatus === EnrollmentAddEventPageStatuses.DEFAULT) { - return ( - - ); - } - - return ( -
- - {getErrorMessage()} - -
- ); - }; - - - return ( - <> - -
- {renderContent()} -
- - ); -}; - -export const EnrollmentAddEventPage = compose( - withStyles(getStyles), -)(EnrollmentAddEventPagePlain); diff --git a/src/core_modules/capture-core/components/Pages/New/NewPage.component.js b/src/core_modules/capture-core/components/Pages/New/NewPage.component.js index a2d3c6f3ca..1abb89e77f 100644 --- a/src/core_modules/capture-core/components/Pages/New/NewPage.component.js +++ b/src/core_modules/capture-core/components/Pages/New/NewPage.component.js @@ -6,7 +6,7 @@ import type { ComponentType } from 'react'; import withStyles from '@material-ui/core/styles/withStyles'; import { OrgUnitFetcher } from 'capture-core/components/OrgUnitFetcher'; import i18n from '@dhis2/d2-i18n'; -import { Button , NoticeBox} from '@dhis2/ui'; +import { Button, NoticeBox } from '@dhis2/ui'; import { TopBar } from './TopBar.container'; import type { ContainerProps, Props } from './NewPage.types'; import { withLoadingIndicator } from '../../../HOC'; From 6c36bf60e7e2754f5e9540a61dcbfeb9557be511 Mon Sep 17 00:00:00 2001 From: alaa-yahia Date: Wed, 28 Aug 2024 15:43:27 +0300 Subject: [PATCH 4/8] fix: always display context selector when there is an error --- i18n/en.pot | 7 +- .../HOC/withErrorMessageHandler.js | 13 +- .../Enrollment/EnrollmentPage.component.js | 39 ++-- .../Enrollment/EnrollmentPage.container.js | 32 ++-- .../Enrollment/MissingMessage.component.js | 19 +- .../EnrollmentAddEventPage.container.js | 16 +- .../Pages/MainPage/MainPage.component.js | 109 +++++------ .../Pages/MainPage/MainPage.container.js | 2 + .../components/Pages/New/NewPage.component.js | 169 ++++++++---------- .../components/Pages/New/NewPage.container.js | 55 +++--- .../getProgramFromProgramIdThrowIfNotFound.js | 1 - 11 files changed, 211 insertions(+), 251 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index f8051620ca..55439130c6 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-08-22T21:46:48.644Z\n" -"PO-Revision-Date: 2024-08-22T21:46:48.644Z\n" +"POT-Creation-Date: 2024-08-28T07:21:55.353Z\n" +"PO-Revision-Date: 2024-08-28T07:21:55.354Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -762,9 +762,6 @@ msgstr "View working list in this program." msgid "Page is missing required values from URL" msgstr "Page is missing required values from URL" -msgid "Program is not valid" -msgstr "Program is not valid" - msgid "Org unit is not valid with current program" msgstr "Org unit is not valid with current program" diff --git a/src/core_modules/capture-core/HOC/withErrorMessageHandler.js b/src/core_modules/capture-core/HOC/withErrorMessageHandler.js index 7fc48e201d..8e6f4dcbda 100644 --- a/src/core_modules/capture-core/HOC/withErrorMessageHandler.js +++ b/src/core_modules/capture-core/HOC/withErrorMessageHandler.js @@ -1,11 +1,11 @@ // @flow import * as React from 'react'; import { withStyles } from '@material-ui/core/styles'; +import { NoticeBox } from '@dhis2/ui'; -const getStyles = (theme: Theme) => ({ +const getStyles = () => ({ errorContainer: { margin: 20, - color: theme.palette.error.main, }, }); @@ -23,12 +23,13 @@ export const withErrorMessageHandler = () => if (error) { return ( -
- {error} -
+
{error}
+ ); } diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.component.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.component.js index 9b2112b175..baf588711b 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.component.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.component.js @@ -6,9 +6,10 @@ import { compose } from 'redux'; import type { Props } from './EnrollmentPage.types'; import { enrollmentPageStatuses } from './EnrollmentPage.constants'; import { LoadingMaskForPage } from '../../LoadingMasks/LoadingMaskForPage.component'; +import { withErrorMessageHandler } from '../../../HOC'; import { MissingMessage } from './MissingMessage.component'; import { EnrollmentPageDefault } from './EnrollmentPageDefault'; -import { TopBar } from './TopBar.container'; + const getStyles = ({ typography }) => ({ loadingMask: { @@ -21,38 +22,22 @@ const getStyles = ({ typography }) => ({ const EnrollmentPagePlain = ({ classes, - programId, - orgUnitId, - enrollmentId, - trackedEntityName, - teiDisplayName, enrollmentPageStatus, - enrollmentsAsOptions, }) => ( - <> - - -
- {enrollmentPageStatus === enrollmentPageStatuses.MISSING_SELECTIONS && } +
+ {enrollmentPageStatus === enrollmentPageStatuses.MISSING_SELECTIONS && } - {enrollmentPageStatus === enrollmentPageStatuses.DEFAULT && } + {enrollmentPageStatus === enrollmentPageStatuses.DEFAULT && } - {enrollmentPageStatus === enrollmentPageStatuses.LOADING && ( -
- -
- )} -
- + {enrollmentPageStatus === enrollmentPageStatuses.LOADING && ( +
+ +
+ )} +
); export const EnrollmentPageComponent: ComponentType<$Diff> = compose( + withErrorMessageHandler(), withStyles(getStyles), )(EnrollmentPagePlain); diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.container.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.container.js index bacc367ac1..9de18b5331 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.container.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.container.js @@ -24,6 +24,7 @@ import { useSetEnrollmentId, } from '../../ScopeSelector'; import { useLocationQuery } from '../../../utils/routing'; +import { TopBar } from './TopBar.container'; const useComponentLifecycle = () => { const dispatch = useDispatch(); @@ -114,15 +115,26 @@ export const EnrollmentPage: ComponentType<{||}> = () => { useSelector(({ activePage }) => activePage.selectionsError && activePage.selectionsError.error); return ( - + <> + + + + ); }; diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.js b/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.js index a0f06f6230..17d36faedd 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.js @@ -26,14 +26,14 @@ export const missingStatuses = { MISSING_ENROLLMENT_SELECTION_ADD_NEW: 'MISSING_ENROLLMENT_SELECTION_ADD_NEW', MISSING_PROGRAM_CATEGORIES_SELECTION: 'MISSING_PROGRAM_CATEGORIES_SELECTION', MISSING_PROGRAM_SELECTION: 'MISSING_PROGRAM_SELECTION', - INVALID_PROGRAM_ID: 'INVALID_PROGRAM_ID', }; const useMissingStatus = () => { const [missingStatus, setStatus] = useState(null); const { programId, enrollmentId, teiId } = useLocationQuery(); - const { scopeType, tetId: scopeTetId, programName } = useScopeInfo(programId); + + const { scopeType, tetId: scopeTetId } = useScopeInfo(programId); const { programSelectionIsIncomplete } = useMissingCategoriesInProgramSelection(); const { programHasEnrollments, @@ -47,9 +47,8 @@ const useMissingStatus = () => { useEffect(() => { const selectedProgramIsTracker = programId && scopeType === scopeTypes.TRACKER_PROGRAM; const selectedProgramIsEvent = programId && scopeType === scopeTypes.EVENT_PROGRAM; - if (programId && !programName) { - setStatus(missingStatuses.INVALID_PROGRAM_ID); - } else if (enrollmentAccessLevel === enrollmentAccessLevels.LIMITED_ACCESS) { + + if (enrollmentAccessLevel === enrollmentAccessLevels.LIMITED_ACCESS) { setStatus(missingStatuses.PROTECTED_PROGRAM_WITH_BREAKING_THE_GLASS); } else if (enrollmentAccessLevel === enrollmentAccessLevels.NO_ACCESS) { setStatus(missingStatuses.RESTRICTED_PROGRAM_NO_ACCESS); @@ -72,7 +71,6 @@ const useMissingStatus = () => { } }, [ programId, - programName, programSelectionIsIncomplete, programHasEnrollments, programHasActiveEnrollments, @@ -134,15 +132,6 @@ export const MissingMessage = withStyles(getStyles)(({ const { programName, trackedEntityName: selectedTetName } = useScopeInfo(programId); return (<> - { - missingStatus === missingStatuses.INVALID_PROGRAM_ID && - - {i18n.t('Program with id "{{programId}}" does not exist', { - programId, - interpolation: { escapeValue: false }, - })} - - } { missingStatus === missingStatuses.MISSING_PROGRAM_SELECTION && diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.js index c2a486bad0..3dba5c2521 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.container.js @@ -1,10 +1,11 @@ // @flow -import React, { useMemo } from 'react'; +import React, { useMemo, useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; import i18n from '@dhis2/d2-i18n'; import { NoticeBox, spacersNum } from '@dhis2/ui'; import withStyles from '@material-ui/core/styles/withStyles'; import { EnrollmentAddEventPageDefault } from './EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container'; -import { useLocationQuery } from '../../../utils/routing'; +import { useLocationQuery, buildUrlQueryString } from '../../../utils/routing'; import { IdTypes, useValidatedIDsFromCache, @@ -27,6 +28,7 @@ const styles = { }, }; const EnrollmentAddEventPagePlain = ({ classes }: Props) => { + const history = useHistory(); const { teiId, programId, orgUnitId, enrollmentId } = useLocationQuery(); const { valid: validIds, loading, error: validatedIdsError } = useValidatedIDsFromCache({ programId, orgUnitId }); const { @@ -63,6 +65,12 @@ const EnrollmentAddEventPagePlain = ({ classes }: Props) => { return EnrollmentAddEventPageStatuses.DEFAULT; }, [enrollmentId, isLoading, loading, pageIsInvalid, programId, teiId, validIds]); + useEffect(() => { + if (pageStatus === EnrollmentAddEventPageStatuses.PROGRAM_INVALID) { + history.push(`/enrollment?${buildUrlQueryString({ orgUnitId, teiId, enrollmentId })}`); + } + }, [pageStatus, orgUnitId, teiId, enrollmentId, history]); + if (pageStatus === EnrollmentAddEventPageStatuses.LOADING) { return ; } @@ -89,10 +97,6 @@ const EnrollmentAddEventPagePlain = ({ classes }: Props) => { i18n.t('Page is missing required values from URL') )} - {pageStatus === EnrollmentAddEventPageStatuses.PROGRAM_INVALID && ( - i18n.t('Program is not valid') - )} - {pageStatus === EnrollmentAddEventPageStatuses.ORG_UNIT_INVALID && ( i18n.t('Org unit is not valid with current program') )} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js index 2d29de6e9b..c826fab13a 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js @@ -4,12 +4,12 @@ import { compose } from 'redux'; import { colors, spacers } from '@dhis2/ui'; import { withStyles } from '@material-ui/core/styles'; import { WorkingListsType } from './WorkingListsType'; -import type { Props, PlainProps } from './mainPage.types'; +import type { ComponentType } from 'react'; +import type { Props } from './mainPage.types'; import { MainPageStatuses } from './MainPage.constants'; import { WithoutOrgUnitSelectedMessage } from './WithoutOrgUnitSelectedMessage/WithoutOrgUnitSelectedMessage'; import { WithoutCategorySelectedMessage } from './WithoutCategorySelectedMessage/WithoutCategorySelectedMessage'; import { withErrorMessageHandler, withLoadingIndicator } from '../../../HOC'; -import { TopBar } from './TopBar.container'; import { SearchBox } from '../../SearchBox'; import { TemplateSelector } from '../../TemplateSelector'; import { @@ -41,79 +41,62 @@ const getStyles = () => ({ }, }); -const useShowMainPage = ({ programId, orgUnitId, trackedEntityTypeId, displayFrontPageList, selectedTemplateId }) => - useMemo(() => { - const noProgramSelected = !programId; - const noOrgUnitSelected = !orgUnitId; - const isEventProgram = !trackedEntityTypeId; - - return noProgramSelected || noOrgUnitSelected || isEventProgram || displayFrontPageList || selectedTemplateId; - }, [programId, orgUnitId, trackedEntityTypeId, displayFrontPageList, selectedTemplateId]); - -const MainPageBody = compose( - withErrorMessageHandler(), - withStyles(getStyles), -)(({ MainPageStatus, setShowAccessible, programId, showMainPage, classes, ...passOnProps }: PlainProps) => ( - <> - {showMainPage ? ( - <> - {MainPageStatus === MainPageStatuses.WITHOUT_ORG_UNIT_SELECTED && ( - - )} - {MainPageStatus === MainPageStatuses.CATEGORY_OPTION_INVALID_FOR_ORG_UNIT && ( - - )} - {MainPageStatus === MainPageStatuses.WITHOUT_PROGRAM_CATEGORY_SELECTED && ( - - )} - {MainPageStatus === MainPageStatuses.SHOW_WORKING_LIST && ( -
- -
- )} - - ) : ( -
-
- -
-
- -
-
- )} - -)); - -const MainPage = ({ +const MainPagePlain = ({ programId, orgUnitId, trackedEntityTypeId, displayFrontPageList, selectedTemplateId, selectedCategories, + MainPageStatus, + setShowAccessible, + classes, ...passOnProps }: Props) => { - const showMainPage = useShowMainPage({ - programId, - orgUnitId, - trackedEntityTypeId, - displayFrontPageList, - selectedTemplateId, - }); + const showMainPage = useMemo(() => { + const noProgramSelected = !programId; + const noOrgUnitSelected = !orgUnitId; + const isEventProgram = !trackedEntityTypeId; + return noProgramSelected || noOrgUnitSelected || isEventProgram || displayFrontPageList || selectedTemplateId; + }, [programId, orgUnitId, trackedEntityTypeId, displayFrontPageList, selectedTemplateId]); return ( <> - - + {showMainPage ? ( + <> + {MainPageStatus === MainPageStatuses.WITHOUT_ORG_UNIT_SELECTED && ( + + )} + {MainPageStatus === MainPageStatuses.CATEGORY_OPTION_INVALID_FOR_ORG_UNIT && ( + + )} + {MainPageStatus === MainPageStatuses.WITHOUT_PROGRAM_CATEGORY_SELECTED && ( + + )} + {MainPageStatus === MainPageStatuses.SHOW_WORKING_LIST && ( +
+ +
+ )} + + ) : ( +
+
+ +
+
+ +
+
+ )} ); }; -export const MainPageComponent = withLoadingIndicator()(MainPage); + +export const MainPageComponent: ComponentType = + compose( + withLoadingIndicator(), + withErrorMessageHandler(), + withStyles(getStyles), + )(MainPagePlain); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js index 52a10ab4e4..3fbedb8b00 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js @@ -11,6 +11,7 @@ import { buildUrlQueryString, useLocationQuery } from '../../../utils/routing'; import { MainPageStatuses } from './MainPage.constants'; import { OrgUnitFetcher } from '../../OrgUnitFetcher'; import { useCategoryOptionIsValidForOrgUnit } from '../../../hooks/useCategoryComboIsValidForOrgUnit'; +import { TopBar } from './TopBar.container'; const mapStateToProps = (state: ReduxState) => ({ error: state.activePage.selectionsError && state.activePage.selectionsError.error, // TODO: Should probably remove this @@ -152,6 +153,7 @@ const MainPageContainer = () => { return ( + { const { scopeType } = useScopeInfo(currentScopeId); const [selectedScopeId, setScopeId] = useState(currentScopeId); @@ -61,7 +56,8 @@ const NewPagePlain = ({ } else { showDefaultViewOnNewPage(); } - }, [ + }, + [ programCategorySelectionIncomplete, orgUnitSelectionIncomplete, showMessageToSelectOrgUnitOnNewPage, @@ -70,104 +66,87 @@ const NewPagePlain = ({ categoryOptionIsInvalidForOrgUnit, showMessageThatCategoryOptionIsInvalidForOrgUnit, ]); - const orgUnitId = useSelector(({ currentSelections }) => currentSelections.orgUnitId); - const renderContent = () => { - if (error) { - return ( - - {error} - - ); - } - - if (!writeAccess) { - return ( - - ); - } - - return ( - - {newPageStatus === newPageStatuses.DEFAULT && ( - + { + !writeAccess ? + - )} + : + + { + newPageStatus === newPageStatuses.DEFAULT && + + } - {newPageStatus === newPageStatuses.WITHOUT_ORG_UNIT_SELECTED && ( - <> - - {i18n.t('Choose an organisation unit to start reporting')} - - - - )} + { + newPageStatus === newPageStatuses.WITHOUT_ORG_UNIT_SELECTED && + <> + + {i18n.t('Choose an organisation unit to start reporting')} + + + + } - {newPageStatus === newPageStatuses.WITHOUT_PROGRAM_CATEGORY_SELECTED && ( - - {i18n.t('Choose the {{missingCategories}} to start reporting', { - missingCategories: missingCategoriesInProgramSelection.reduce((acc, { name }, index) => { - if ((index + 1 === missingCategoriesInProgramSelection.length)) { - return `${acc} ${name} ${missingCategoriesInProgramSelection.length > 1 ? 'categories' : 'category'}`; - } - return `${acc} ${name},`; - }, ''), - interpolation: { escapeValue: false }, - })} - - )} + { + newPageStatus === newPageStatuses.WITHOUT_PROGRAM_CATEGORY_SELECTED && + (() => { + const missingCategories = missingCategoriesInProgramSelection.reduce((acc, { name }, index) => { + if ((index + 1 === missingCategoriesInProgramSelection.length)) { + return `${acc} ${name} ${missingCategoriesInProgramSelection.length > 1 ? 'categories' : 'category'}`; + } + return `${acc} ${name},`; + }, ''); - {newPageStatus === newPageStatuses.CATEGORY_OPTION_INVALID_FOR_ORG_UNIT && ( - - {i18n.t( - 'The category option is not valid for the selected organisation unit. Please select a valid combination.', - )} - - )} - - ); - }; + return ( + + {i18n.t('Choose the {{missingCategories}} to start reporting', { + missingCategories, interpolation: { escapeValue: false }, + })} + + ); + })() + } - return ( - <> - -
- {renderContent()} -
- + { + newPageStatus === newPageStatuses.CATEGORY_OPTION_INVALID_FOR_ORG_UNIT && ( + + {i18n.t( + 'The category option is not valid for the selected organisation unit. Please select a valid combination.', + )} + + ) + } + +
+ } + ); }; export const NewPageComponent: ComponentType = compose( withLoadingIndicator(), + withErrorMessageHandler(), withStyles(getStyles), )(NewPagePlain); diff --git a/src/core_modules/capture-core/components/Pages/New/NewPage.container.js b/src/core_modules/capture-core/components/Pages/New/NewPage.container.js index 8ea87c19e2..66420dfb5f 100644 --- a/src/core_modules/capture-core/components/Pages/New/NewPage.container.js +++ b/src/core_modules/capture-core/components/Pages/New/NewPage.container.js @@ -9,7 +9,7 @@ import { showDefaultViewOnNewPage, showMessageToSelectProgramCategoryOnNewPage, showMessageThatCategoryOptionIsInvalidForOrgUnit, } from './NewPage.actions'; -import { typeof newPageStatuses } from './NewPage.constants'; +import { newPageStatuses } from './NewPage.constants'; import { buildUrlQueryString, useLocationQuery } from '../../../utils/routing'; import { getScopeFromScopeId, TrackerProgram, TrackedEntityType } from '../../../metaData'; import { useMissingCategoriesInProgramSelection } from '../../../hooks/useMissingCategoriesInProgramSelection'; @@ -18,6 +18,7 @@ import { useTrackedEntityInstances } from './hooks'; import { deriveTeiName } from '../common/EnrollmentOverviewDomain/useTeiDisplayName'; import { programCollection } from '../../../metaDataMemoryStores/programCollection/programCollection'; import { useCategoryOptionIsValidForOrgUnit } from '../../../hooks/useCategoryComboIsValidForOrgUnit'; +import { TopBar } from './TopBar.container'; const useUserWriteAccess = (scopeId) => { const scope = getScopeFromScopeId(scopeId); @@ -108,26 +109,34 @@ export const NewPage: ComponentType<{||}> = () => { ); return ( - ); + <> + + + + ); }; diff --git a/src/core_modules/capture-core/metaData/helpers/getProgramFromProgramIdThrowIfNotFound.js b/src/core_modules/capture-core/metaData/helpers/getProgramFromProgramIdThrowIfNotFound.js index 29244c60da..a8180ff534 100644 --- a/src/core_modules/capture-core/metaData/helpers/getProgramFromProgramIdThrowIfNotFound.js +++ b/src/core_modules/capture-core/metaData/helpers/getProgramFromProgramIdThrowIfNotFound.js @@ -13,7 +13,6 @@ export function getProgramFromProgramIdThrowIfNotFound(programId: string) { const program = programCollection.get(programId); if (!program) { log.error(errorCreator(errorMessages.PROGRAM_NOT_FOUND)({ programId })); - throw Error(i18n.t(errorMessages.GENERIC_ERROR)); } return program; } From 1661517cd6c657f6a0f7d43f1ac3269b50647b67 Mon Sep 17 00:00:00 2001 From: alaa-yahia Date: Thu, 29 Aug 2024 02:30:03 +0300 Subject: [PATCH 5/8] fix: app stuck in loading state when url has invalid enrollmentId or teiId --- .../components/Pages/Enrollment/TopBar.container.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/TopBar.container.js b/src/core_modules/capture-core/components/Pages/Enrollment/TopBar.container.js index 47477a965d..66ff491361 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/TopBar.container.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/TopBar.container.js @@ -52,7 +52,7 @@ export const TopBar = ({ onResetOrgUnitId={() => resetOrgUnitId()} onStartAgain={() => reset()} > - resetTeiId('/')} options={[ @@ -64,7 +64,7 @@ export const TopBar = ({ selectedValue="alwaysPreselected" title={trackedEntityName} displayOnly - /> + />) : <>} {enrollmentsAsOptions?.length > 0 ? ( Date: Thu, 29 Aug 2024 02:47:16 +0300 Subject: [PATCH 6/8] chore: comment throw error when programId not found --- .../Pages/Enrollment/TopBar.container.js | 28 ++++++++++--------- .../Pages/MainPage/MainPage.component.js | 2 +- .../getProgramFromProgramIdThrowIfNotFound.js | 3 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/TopBar.container.js b/src/core_modules/capture-core/components/Pages/Enrollment/TopBar.container.js index 66ff491361..c258a96dc3 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/TopBar.container.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/TopBar.container.js @@ -52,19 +52,21 @@ export const TopBar = ({ onResetOrgUnitId={() => resetOrgUnitId()} onStartAgain={() => reset()} > - {trackedEntityName ? ( resetTeiId('/')} - options={[ - { - label: teiDisplayName, - value: 'alwaysPreselected', - }, - ]} - selectedValue="alwaysPreselected" - title={trackedEntityName} - displayOnly - />) : <>} + {trackedEntityName ? ( + resetTeiId('/')} + options={[ + { + label: teiDisplayName, + value: 'alwaysPreselected', + }, + ]} + selectedValue="alwaysPreselected" + title={trackedEntityName} + displayOnly + /> + ) : <>} {enrollmentsAsOptions?.length > 0 ? ( Date: Thu, 29 Aug 2024 02:51:49 +0300 Subject: [PATCH 7/8] fix: flow errors --- .../Pages/MainPage/MainPage.component.js | 12 ++++++---- .../Pages/MainPage/MainPage.container.js | 1 - .../Pages/MainPage/mainPage.types.js | 24 +++++++------------ .../components/Pages/New/NewPage.container.js | 2 +- .../components/Pages/New/NewPage.types.js | 1 - .../getProgramFromProgramIdThrowIfNotFound.js | 4 ++-- 6 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js index 7feb0edf7b..5c9952f28d 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js @@ -4,7 +4,7 @@ import { compose } from 'redux'; import { colors, spacers } from '@dhis2/ui'; import { withStyles } from '@material-ui/core/styles'; import type { ComponentType } from 'react'; -import type { Props } from './mainPage.types'; +import type { Props, ContainerProps } from './mainPage.types'; import { WorkingListsType } from './WorkingListsType'; import { MainPageStatuses } from './MainPage.constants'; import { WithoutOrgUnitSelectedMessage } from './WithoutOrgUnitSelectedMessage/WithoutOrgUnitSelectedMessage'; @@ -47,11 +47,10 @@ const MainPagePlain = ({ trackedEntityTypeId, displayFrontPageList, selectedTemplateId, - selectedCategories, MainPageStatus, setShowAccessible, classes, - ...passOnProps + onChangeTemplate, }: Props) => { const showMainPage = useMemo(() => { const noProgramSelected = !programId; @@ -75,7 +74,12 @@ const MainPagePlain = ({ )} {MainPageStatus === MainPageStatuses.SHOW_WORKING_LIST && (
- +
)} diff --git a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js index 3fbedb8b00..42e6137e59 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js @@ -165,7 +165,6 @@ const MainPageContainer = () => { error={error} ready={ready} displayFrontPageList={displayFrontPageList} - selectedCategories={selectedCategories} />
); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/mainPage.types.js b/src/core_modules/capture-core/components/Pages/MainPage/mainPage.types.js index 25cff6c98e..bac702d632 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/mainPage.types.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/mainPage.types.js @@ -1,27 +1,21 @@ // @flow -type PassOnProps = $ReadOnly<{| +export type ContainerProps = $ReadOnly<{| programId: string, orgUnitId: string, selectedTemplateId?: string, + trackedEntityTypeId?: string, + displayFrontPageList?: boolean, onChangeTemplate?: (selectedTemplateId?: string) => void, -|}>; - -export type PlainProps = $ReadOnly<{| - ...PassOnProps, setShowAccessible: () => void, MainPageStatus: boolean, selectedTemplateId: string, - showMainPage: boolean, - ...CssClasses, -|}>; - -export type Props = $ReadOnly<{| - ...PassOnProps, - ...PlainProps, error: boolean, ready: boolean, - trackedEntityTypeId?: string, - displayFrontPageList?: boolean, - selectedCategories: ?{ [categoryId: string]: { writeAccess: boolean } }, +|} +>; + +export type Props = $ReadOnly<{| + ...ContainerProps, + ...CssClasses |}>; diff --git a/src/core_modules/capture-core/components/Pages/New/NewPage.container.js b/src/core_modules/capture-core/components/Pages/New/NewPage.container.js index 66420dfb5f..4286ee2c70 100644 --- a/src/core_modules/capture-core/components/Pages/New/NewPage.container.js +++ b/src/core_modules/capture-core/components/Pages/New/NewPage.container.js @@ -90,7 +90,7 @@ export const NewPage: ComponentType<{||}> = () => { ({ currentSelections }) => !currentSelections.orgUnitId && !currentSelections.complete, ); - const newPageStatus: $Keys = + const newPageStatus: $Keys = useSelector(({ newPage }) => newPage.newPageStatus); const handleMainPageNavigation = () => { 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 438b61a88f..9004f99945 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 @@ -29,7 +29,6 @@ export type ContainerProps = $ReadOnly<{| writeAccess: boolean, error: boolean, ready: boolean, - isUserInteractionInProgress: boolean, programId?: string, teiId?: string, trackedEntityName?: string, diff --git a/src/core_modules/capture-core/metaData/helpers/getProgramFromProgramIdThrowIfNotFound.js b/src/core_modules/capture-core/metaData/helpers/getProgramFromProgramIdThrowIfNotFound.js index 7516e6202a..29244c60da 100644 --- a/src/core_modules/capture-core/metaData/helpers/getProgramFromProgramIdThrowIfNotFound.js +++ b/src/core_modules/capture-core/metaData/helpers/getProgramFromProgramIdThrowIfNotFound.js @@ -1,6 +1,6 @@ // @flow import log from 'loglevel'; -// import i18n from '@dhis2/d2-i18n'; +import i18n from '@dhis2/d2-i18n'; import { errorCreator } from 'capture-core-utils'; import { programCollection } from '../../metaDataMemoryStores'; @@ -13,7 +13,7 @@ export function getProgramFromProgramIdThrowIfNotFound(programId: string) { const program = programCollection.get(programId); if (!program) { log.error(errorCreator(errorMessages.PROGRAM_NOT_FOUND)({ programId })); - // throw Error(i18n.t(errorMessages.GENERIC_ERROR)); + throw Error(i18n.t(errorMessages.GENERIC_ERROR)); } return program; } From 2ea35112a79b25ad567dd38078b83dded8051bf9 Mon Sep 17 00:00:00 2001 From: alaa-yahia Date: Thu, 29 Aug 2024 12:58:59 +0300 Subject: [PATCH 8/8] fix: useProgramInfo hook breaks the app --- .../EnrollmentAddEventPageDefault.container.js | 4 ++-- .../EnrollmentAddEventPageDefault.types.js | 2 +- .../EnrollmentEditEventPage.component.js | 2 +- .../EnrollmentEditEventPage.container.js | 4 ++-- .../EnrollmentEditEventPage.types.js | 2 +- .../EnrollmentEditEvent/TopBar.container.js | 2 +- .../WithoutCategorySelectedMessage.js | 2 +- .../WithoutOrgUnitSelectedMessage.js | 7 +++---- .../RegUnitSelector.component.js | 2 +- .../epics/getCategoriesDataFromEvent.js | 2 +- .../RegUnitSelector.component.js | 2 +- .../QuickSelector/QuickSelector.types.js | 2 +- .../ScopeSelector/ScopeSelector.types.js | 2 +- .../TopBarActions/TopBarActions.types.js | 4 ++-- .../hooks/useProgramInfo/useProgramInfo.js | 17 ++++++++++++----- .../metaData/helpers/getProgramEventAccess.js | 2 +- 16 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.js index fb3656915b..88b39abb5d 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.js @@ -109,9 +109,9 @@ export const EnrollmentAddEventPageDefault = ({ const dataEntryHasChanges = useSelector(state => getDataEntryHasChanges(state, widgetReducerName)); const { program } = useProgramInfo(programId); - const selectedProgramStage = [...program.stages.values()].find(item => item.id === stageId); + const selectedProgramStage = [...program?.stages.values() ?? []].find(item => item.id === stageId); const outputEffects = useWidgetDataFromStore(widgetReducerName); - const hideWidgets = useHideWidgetByRuleLocations(program.programRules.concat(selectedProgramStage?.programRules ?? [])); + const hideWidgets = useHideWidgetByRuleLocations(program?.programRules.concat(selectedProgramStage?.programRules ?? [])); // $FlowFixMe const trackedEntityName = program?.trackedEntityType?.name; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.js index d900462c76..a8929f2b14 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.types.js @@ -7,7 +7,7 @@ import type { import { Program } from '../../../../metaData'; export type Props = {| - program: Program, + program: ?Program, stageId: string, orgUnitId: string, teiId: string, diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js index 03b56579de..d2acb94e3b 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js @@ -60,7 +60,7 @@ export const EnrollmentEditEventPageComponent = ({ mode={mode} programStage={programStage} enrollmentId={enrollmentId} - programId={program.id} + programId={program?.id} enrollmentsAsOptions={enrollmentsAsOptions} trackedEntityName={trackedEntityName} teiDisplayName={teiDisplayName} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js index 9ba1bfcdd8..995fca5429 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js @@ -132,8 +132,8 @@ const EnrollmentEditEventPageWithContextPlain = ({ }, [dispatch]); const { program } = useProgramInfo(programId); - const programStage = [...program.stages?.values()].find(item => item.id === stageId); - const hideWidgets = useHideWidgetByRuleLocations(program.programRules.concat(programStage?.programRules)); + const programStage = [...program?.stages?.values() ?? []].find(item => item.id === stageId); + const hideWidgets = useHideWidgetByRuleLocations(program?.programRules.concat(programStage?.programRules)); const onDeleteTrackedEntitySuccess = useCallback(() => { history.push(`/?${buildUrlQueryString({ orgUnitId, programId })}`); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js index e164d9fc02..7f48be3b24 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js @@ -17,7 +17,7 @@ export type PlainProps = {| enrollmentId: string, eventId: string, stageId: string, - program: Program, + program: ?Program, trackedEntityTypeId: string, mode: string, orgUnitId: string, diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.js index 1798a80a16..e613228db2 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.js @@ -21,7 +21,7 @@ import { TopBarActions } from '../../TopBarActions'; type Props = {| programStage: ?ProgramStage, enrollmentId: string, - programId: string, + programId: ?string, mode: string, orgUnitId: string, trackedEntityName: string, diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WithoutCategorySelectedMessage/WithoutCategorySelectedMessage.js b/src/core_modules/capture-core/components/Pages/MainPage/WithoutCategorySelectedMessage/WithoutCategorySelectedMessage.js index d30ef8e3dd..fd23dceaa9 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/WithoutCategorySelectedMessage/WithoutCategorySelectedMessage.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/WithoutCategorySelectedMessage/WithoutCategorySelectedMessage.js @@ -32,7 +32,7 @@ const WithoutCategorySelectedMessagePlain = ({ programId, classes }) => { categories: currentSelections.categories, }), shallowEqual); - if (!program.categoryCombination) { + if (!program?.categoryCombination) { log.error(errorCreator(errorMessages.MISSING_CATEGORY)({ programId })); throw Error(i18n.t(errorMessages.GENERIC_ERROR)); } diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WithoutOrgUnitSelectedMessage/WithoutOrgUnitSelectedMessage.js b/src/core_modules/capture-core/components/Pages/MainPage/WithoutOrgUnitSelectedMessage/WithoutOrgUnitSelectedMessage.js index f791f50b93..cb2379c151 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/WithoutOrgUnitSelectedMessage/WithoutOrgUnitSelectedMessage.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/WithoutOrgUnitSelectedMessage/WithoutOrgUnitSelectedMessage.js @@ -39,16 +39,15 @@ type Props = {| |} const WithoutOrgUnitSelectedMessagePlain = ({ programId, setShowAccessible, classes }: Props) => { - // TODO - this hook breaks the app when the program is not found const { program, programType } = useProgramInfo(programId); const IncompleteSelectionMessage = useMemo(() => (programType === programTypes.TRACKER_PROGRAM ? ( i18n.t('Or see all records accessible to you in {{program}} ', { - program: program.name, + program: program?.name, interpolation: { escapeValue: false }, }) ) : i18n.t('Or see all events accessible to you in {{program}}', - { program: program.name, interpolation: { escapeValue: false } })), - [program.name, programType]); + { program: program?.name, interpolation: { escapeValue: false } })), + [program?.name, programType]); return (
{ return; } - onUpdateSelectedOrgUnit(orgUnit, program.organisationUnits ? !program.organisationUnits[orgUnit.id] : false); + onUpdateSelectedOrgUnit(orgUnit, program?.organisationUnits ? !program.organisationUnits[orgUnit.id] : false); } render() { diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/epics/getCategoriesDataFromEvent.js b/src/core_modules/capture-core/components/Pages/ViewEvent/epics/getCategoriesDataFromEvent.js index c3609baa25..1f98cf8bcd 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/epics/getCategoriesDataFromEvent.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/epics/getCategoriesDataFromEvent.js @@ -25,7 +25,7 @@ export async function getCategoriesDataFromEventAsync( const program = getProgramFromProgramIdThrowIfNotFound(event.programId); - const categoryCombination = program.categoryCombination; + const categoryCombination = program?.categoryCombination; if (!categoryCombination) { return null; } diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/RegUnitSelector.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/RegUnitSelector.component.js index e75b8513ea..42cba9027f 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/RegUnitSelector.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegistrationSection/RegUnitSelector/RegUnitSelector.component.js @@ -47,7 +47,7 @@ class RegUnitSelectorPlain extends React.Component { return; } - onUpdateSelectedOrgUnit(orgUnit, program.organisationUnits ? !program.organisationUnits[orgUnit.id] : false); + onUpdateSelectedOrgUnit(orgUnit, program?.organisationUnits ? !program.organisationUnits[orgUnit.id] : false); } render() { diff --git a/src/core_modules/capture-core/components/ScopeSelector/QuickSelector/QuickSelector.types.js b/src/core_modules/capture-core/components/ScopeSelector/QuickSelector/QuickSelector.types.js index 3b8451471c..148a0b7bfd 100644 --- a/src/core_modules/capture-core/components/ScopeSelector/QuickSelector/QuickSelector.types.js +++ b/src/core_modules/capture-core/components/ScopeSelector/QuickSelector/QuickSelector.types.js @@ -3,7 +3,7 @@ import type { Node } from 'react'; export type Props = { selectedOrgUnitId?: string, - selectedProgramId?: string, + selectedProgramId?: ?string, selectedCategories: Object, selectedOrgUnit: Object, previousOrgUnitId?: string, diff --git a/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.types.js b/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.types.js index e92d91a8a2..09faa0ece8 100644 --- a/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.types.js +++ b/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.types.js @@ -5,7 +5,7 @@ export type OwnProps = $ReadOnly<{| isUserInteractionInProgress?: boolean, pageToPush?: string, selectedOrgUnitId?: string, - selectedProgramId?: string, + selectedProgramId?: ?string, previousOrgUnitId?: string, selectedCategories?: { [categoryId: string]: { writeAccess: boolean } }, onSetProgramId?: (id: string) => void, diff --git a/src/core_modules/capture-core/components/TopBarActions/TopBarActions.types.js b/src/core_modules/capture-core/components/TopBarActions/TopBarActions.types.js index 0f13e109f2..249ab2faa9 100644 --- a/src/core_modules/capture-core/components/TopBarActions/TopBarActions.types.js +++ b/src/core_modules/capture-core/components/TopBarActions/TopBarActions.types.js @@ -1,13 +1,13 @@ // @flow export type Props = { - selectedProgramId?: string, + selectedProgramId?: ?string, selectedOrgUnitId?: string, isUserInteractionInProgress?: boolean, }; export type PlainProps = { - selectedProgramId?: string, + selectedProgramId?: ?string, onNewClick: () => void, onNewClickWithoutProgramId: () => void, onFindClick: () => void, diff --git a/src/core_modules/capture-core/hooks/useProgramInfo/useProgramInfo.js b/src/core_modules/capture-core/hooks/useProgramInfo/useProgramInfo.js index dc2437ed04..2b2a4ddbbb 100644 --- a/src/core_modules/capture-core/hooks/useProgramInfo/useProgramInfo.js +++ b/src/core_modules/capture-core/hooks/useProgramInfo/useProgramInfo.js @@ -4,9 +4,16 @@ import { getProgramFromProgramIdThrowIfNotFound, TrackerProgram } from '../../me import { programTypes } from './programTypes.const'; export const useProgramInfo = (programId: string) => useMemo(() => { - const program = getProgramFromProgramIdThrowIfNotFound(programId); - return { - program, - programType: program instanceof TrackerProgram ? programTypes.TRACKER_PROGRAM : programTypes.EVENT_PROGRAM, - }; + try { + const program = getProgramFromProgramIdThrowIfNotFound(programId); + return { + program, + programType: program instanceof TrackerProgram ? programTypes.TRACKER_PROGRAM : programTypes.EVENT_PROGRAM, + }; + } catch (error) { + return { + program: undefined, + programType: undefined, + }; + } }, [programId]); diff --git a/src/core_modules/capture-core/metaData/helpers/getProgramEventAccess.js b/src/core_modules/capture-core/metaData/helpers/getProgramEventAccess.js index 8fad80830f..3fddd73db8 100644 --- a/src/core_modules/capture-core/metaData/helpers/getProgramEventAccess.js +++ b/src/core_modules/capture-core/metaData/helpers/getProgramEventAccess.js @@ -14,7 +14,7 @@ export function getProgramEventAccess( if (program instanceof EventProgram) { stage = program.stage; } else if (programStageId) { - stage = program.getStage(programStageId); + stage = program?.getStage(programStageId); } if (!stage) { log.error(errorCreator('stage not found')({ programId, programStageId }));