diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c5bd7b301..ced94ac160 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# [100.55.0](https://github.com/dhis2/capture-app/compare/v100.54.0...v100.55.0) (2024-02-05) + + +### Features + +* [DHIS2-16305] Enrollment Overview Plugins ([#3515](https://github.com/dhis2/capture-app/issues/3515)) ([8e06dbc](https://github.com/dhis2/capture-app/commit/8e06dbcb54bdb9b308b9f01427b4139a9a00c977)) + +# [100.54.0](https://github.com/dhis2/capture-app/compare/v100.53.1...v100.54.0) (2024-02-04) + + +### Features + +* [DHIS2-16262] Support Custom Layout for Enrollment Pages ([#3481](https://github.com/dhis2/capture-app/issues/3481)) ([9cc3d70](https://github.com/dhis2/capture-app/commit/9cc3d707f855bba0971a144f1371765afedda55f)) + ## [100.53.1](https://github.com/dhis2/capture-app/compare/v100.53.0...v100.53.1) (2024-02-01) diff --git a/cypress/e2e/ScopeSelector/index.js b/cypress/e2e/ScopeSelector/index.js index 3daaa87f6e..2a883d8298 100644 --- a/cypress/e2e/ScopeSelector/index.js +++ b/cypress/e2e/ScopeSelector/index.js @@ -294,7 +294,7 @@ And('you see the enrollment event Edit page but there is no org unit id in the u And('you see the enrollment event New page but there is no org unit id in the url', () => { cy.url().should('eq', `${Cypress.config().baseUrl}/#/enrollmentEventNew?enrollmentId=gPDueU02tn8&programId=IpHINAT79UW&stageId=A03MvHHogjR&teiId=fhFQhO0xILJ`); - cy.contains('Enrollment: New Event'); + cy.contains('Choose a registering unit to start reporting'); }); And('you see the enrollment event New page but there is no stage id in the url', () => { diff --git a/cypress/e2e/SearchPage/index.js b/cypress/e2e/SearchPage/index.js index 322dc9d377..7f5976ade7 100644 --- a/cypress/e2e/SearchPage/index.js +++ b/cypress/e2e/SearchPage/index.js @@ -263,13 +263,13 @@ Given('you are in the search page with the TB program being preselected from the When('you fill in the zip code range numbers', () => { cy.get('[data-test="form-attributes"]') - .find('[data-test="capture-ui-input"]') - .eq(5) + .find('input[description="Zip code"]') + .eq(0) .type('7130') .blur(); cy.get('[data-test="form-attributes"]') - .find('[data-test="capture-ui-input"]') - .eq(6) + .find('input[description="Zip code"]') + .eq(1) .type('7135') .blur(); }); diff --git a/cypress/e2e/SearchThroughAddRelationship/index.js b/cypress/e2e/SearchThroughAddRelationship/index.js index 4d46ed2b22..51f61acb74 100644 --- a/cypress/e2e/SearchThroughAddRelationship/index.js +++ b/cypress/e2e/SearchThroughAddRelationship/index.js @@ -131,14 +131,13 @@ And('you fill in the the form with values that will return exactly 5 results', ( When('you fill in the zip code range numbers', () => { cy.get('[data-test="d2-form-area"]') - .find('[data-test="capture-ui-input"]') - .eq(5) + .find('input[description="Zip code"]') + .eq(0) .type('7130') .blur(); - cy.get('[data-test="d2-form-area"]') - .find('[data-test="capture-ui-input"]') - .eq(6) + .find('input[description="Zip code"]') + .eq(1) .type('7135') .blur(); }); diff --git a/i18n/en.pot b/i18n/en.pot index 82e4ae8f8b..a3cc181b37 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-01-25T12:12:47.253Z\n" -"PO-Revision-Date: 2024-01-25T12:12:47.253Z\n" +"POT-Creation-Date: 2024-02-07T10:43:51.606Z\n" +"PO-Revision-Date: 2024-02-07T10:43:51.606Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -668,15 +668,6 @@ msgstr "There is an error while opening this enrollment. Please enter a valid ur msgid "An error occurred while fetching enrollments. Please enter a valid url." msgstr "An error occurred while fetching enrollments. Please enter a valid url." -msgid "Enrollment Dashboard" -msgstr "Enrollment Dashboard" - -msgid "No indicator output for this enrollment yet" -msgstr "No indicator output for this enrollment yet" - -msgid "No feedback for this enrollment yet" -msgstr "No feedback for this enrollment yet" - msgid "Quick actions" msgstr "Quick actions" @@ -757,21 +748,12 @@ msgstr "Org unit is not valid with current program" msgid "There was an error opening the Page" msgstr "There was an error opening the Page" -msgid "Enrollment{{escape}} New Event" -msgstr "Enrollment{{escape}} New Event" - msgid "There was an error loading the page" msgstr "There was an error loading the page" msgid "Choose a registering unit to start reporting" msgstr "Choose a registering unit to start reporting" -msgid "There are no feedbacks for this event" -msgstr "There are no feedbacks for this event" - -msgid "There are no indicators for this event" -msgstr "There are no indicators for this event" - msgid "Program stage is invalid" msgstr "Program stage is invalid" @@ -799,18 +781,6 @@ msgstr "Program Stages could not be loaded" msgid "Stage" msgstr "Stage" -msgid "The enrollment event data could not be found" -msgstr "The enrollment event data could not be found" - -msgid "There are no feedback for this event" -msgstr "There are no feedback for this event" - -msgid "Enrollment{{escape}} View Event" -msgstr "Enrollment{{escape}} View Event" - -msgid "Enrollment{{escape}} Edit Event" -msgstr "Enrollment{{escape}} Edit Event" - msgid "Registered events" msgstr "Registered events" @@ -938,6 +908,21 @@ msgstr "Event could not be loaded" msgid "Organisation unit could not be loaded" msgstr "Organisation unit could not be loaded" +msgid "Dashboard" +msgstr "Dashboard" + +msgid "Edit Event" +msgstr "Edit Event" + +msgid "View Event" +msgstr "View Event" + +msgid "No feedback for this enrollment yet" +msgstr "No feedback for this enrollment yet" + +msgid "No indicator output for this enrollment yet" +msgstr "No indicator output for this enrollment yet" + msgid "Selected program" msgstr "Selected program" @@ -950,6 +935,9 @@ msgstr "Search by attributes" msgid "Could not retrieve metadata. Please try again later." msgstr "Could not retrieve metadata. Please try again later." +msgid "The enrollment event data could not be found" +msgstr "The enrollment event data could not be found" + msgid "Possible duplicates found" msgstr "Possible duplicates found" @@ -1377,9 +1365,6 @@ msgstr "This stage can only have one event" msgid "Events could not be retrieved. Please try again later." msgstr "Events could not be retrieved. Please try again later." -msgid "Assigned to" -msgstr "Assigned to" - msgid "{{ totalEvents }} events" msgstr "{{ totalEvents }} events" @@ -1392,8 +1377,8 @@ msgstr "{{ scheduledEvents }} scheduled" msgid "Stages and Events" msgstr "Stages and Events" -msgid "New TEI Relationship" -msgstr "New TEI Relationship" +msgid "New {{trackedEntityTypeName}} relationship" +msgstr "New {{trackedEntityTypeName}} relationship" msgid "Missing implementation step" msgstr "Missing implementation step" diff --git a/package.json b/package.json index 920d59f046..356ffc7f3b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "capture-app", "homepage": ".", - "version": "100.53.1", + "version": "100.55.0", "cacheVersion": "7", "serverVersion": "38", "license": "BSD-3-Clause", @@ -10,7 +10,7 @@ "packages/rules-engine" ], "dependencies": { - "@dhis2/rules-engine-javascript": "100.53.1", + "@dhis2/rules-engine-javascript": "100.55.0", "@dhis2/app-runtime": "^3.9.3", "@dhis2/d2-i18n": "^1.1.0", "@dhis2/d2-icons": "^1.0.1", @@ -73,14 +73,13 @@ "build": "yarn verifyCacheVersion && yarn workspaces run build && GENERATE_SOURCEMAP=false d2-app-scripts build && cp ./package.json ./build/app/package.json", "build:standalone": "yarn workspaces run build && GENERATE_SOURCEMAP=false d2-app-scripts build --standalone", "test": "yarn workspaces run build && d2-app-scripts test", - "test2": "react-scripts test", "test:debug": "yarn workspaces run build && react-scripts --inspect-brk test --runInBand", "jsdoc": "NODE_ENV=development jsdoc -c jsdoc-conf.json", "linter:check": "yarn workspaces run linter:check && yarn workspaces run build && eslint -c .eslintrc . --quiet", "flow:check": "./node_modules/.bin/flow check ./src", "flow:addTypes": "flow-typed install", - "cy:open": "concurrently --kill-others \"yarn start:forCypress\" \"wait-on 'http-get://localhost:3000' && cypress open\"", - "cy:run": "concurrently --kill-others \"yarn start:forCypress\" \"wait-on 'http-get://localhost:3000' && cypress run\"", + "cy:open": "export NODE_OPTIONS=--openssl-legacy-provider && concurrently --kill-others \"yarn start:forCypress\" \"wait-on 'http-get://127.0.0.1:3000' && cypress open\"", + "cy:run": "export NODE_OPTIONS=--openssl-legacy-provider && concurrently --kill-others \"yarn start:forCypress\" \"wait-on 'http-get://127.0.0.1:3000' && cypress run\"", "verifyCacheVersion": "node scripts/verifyCacheVersion.js", "postinstall": "husky install && node scripts/createSymlinkToInternalPackages.mjs", "i18n:add": "d2-app-scripts i18n extract && git add ./i18n/", @@ -106,7 +105,7 @@ "babel-plugin-module-resolver": "^5.0.0", "concurrently": "^7.0.0", "cypress": "12", - "docdash": "^1.2.0", + "docdash": "^2.0.2", "dotenv": "^16.0.3", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.6", diff --git a/packages/rules-engine/package.json b/packages/rules-engine/package.json index c4eac2494e..6d365fa47f 100644 --- a/packages/rules-engine/package.json +++ b/packages/rules-engine/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/rules-engine-javascript", - "version": "100.53.1", + "version": "100.55.0", "license": "BSD-3-Clause", "main": "./build/cjs/index.js", "scripts": { diff --git a/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/hooks/useDataEntryFormConfig.js b/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/hooks/useDataEntryFormConfig.js index a350842a5a..8bcef9be74 100644 --- a/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/hooks/useDataEntryFormConfig.js +++ b/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm/hooks/useDataEntryFormConfig.js @@ -1,30 +1,22 @@ // @flow -import { useDataEngine } from '@dhis2/app-runtime'; -import { useQuery } from 'react-query'; +import { useApiMetadataQuery } from '../../../../../../utils/reactQueryHelpers'; type Props = {| selectedScopeId: string, |} const configQuery = { - dataEntryFormConfigQuery: { - resource: 'dataStore/capture/dataEntryForms', - }, + resource: 'dataStore/capture/dataEntryForms', }; export const useDataEntryFormConfig = ({ selectedScopeId }: Props) => { - const dataEngine = useDataEngine(); - - - const { data: dataEntryFormConfig, isFetched: configIsFetched } = useQuery( - ['dataEntryFormConfig'], - () => dataEngine.query(configQuery), + const { data: dataEntryFormConfig, isFetched: configIsFetched } = useApiMetadataQuery( + ['dataEntryFormConfig', selectedScopeId], + configQuery, { enabled: !!selectedScopeId, - select: ({ dataEntryFormConfigQuery }) => dataEntryFormConfigQuery?.[selectedScopeId], - cacheTime: Infinity, - staleTime: Infinity, + select: dataEntryFormConfigQuery => dataEntryFormConfigQuery[selectedScopeId], }, ); 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 c1930d52f8..9de818c2b2 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 @@ -12,9 +12,6 @@ import { EnrollmentPageDefault } from './EnrollmentPageDefault'; import { TopBar } from './TopBar.container'; const getStyles = ({ typography }) => ({ - container: { - padding: '16px 24px 16px 24px', - }, loadingMask: { height: '100vh', }, @@ -43,7 +40,7 @@ const EnrollmentPagePlain = ({ enrollmentId={enrollmentId} /> -
+
{enrollmentPageStatus === enrollmentPageStatuses.MISSING_SELECTIONS && } {enrollmentPageStatus === enrollmentPageStatuses.DEFAULT && } diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/DefaultPageLayout/DefaultPageLayout.constants.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/DefaultPageLayout/DefaultPageLayout.constants.js new file mode 100644 index 0000000000..ea7fa191a7 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/DefaultPageLayout/DefaultPageLayout.constants.js @@ -0,0 +1,68 @@ +// @flow +import { + QuickActions, + StagesAndEvents, + EnrollmentComment, + DefaultWidgetsForEnrollmentOverview, + WidgetTypes, +} from '../../../common/EnrollmentOverviewDomain/EnrollmentPageLayout'; +import type { + PageLayoutConfig, + WidgetConfig, +} from '../../../common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types'; + +export const WidgetsForEnrollmentPageDefault: $ReadOnly<{ [key: string]: WidgetConfig }> = Object.freeze({ + QuickActions, + StagesAndEvents, + EnrollmentComment, + ...DefaultWidgetsForEnrollmentOverview, +}); + +export const DefaultPageLayout: PageLayoutConfig = Object.freeze({ + leftColumn: [ + { + type: WidgetTypes.COMPONENT, + name: 'QuickActions', + }, + { + type: WidgetTypes.COMPONENT, + name: 'StagesAndEvents', + }, + ], + rightColumn: [ + { + type: WidgetTypes.COMPONENT, + name: 'ErrorWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'WarningWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'EnrollmentComment', + }, + { + type: WidgetTypes.COMPONENT, + name: 'FeedbackWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'IndicatorWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'TrackedEntityRelationship', + }, + { + type: WidgetTypes.COMPONENT, + name: 'ProfileWidget', + settings: { readOnlyMode: false }, + }, + { + type: WidgetTypes.COMPONENT, + name: 'EnrollmentWidget', + settings: { readOnlyMode: false }, + }, + ], +}); diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/DefaultPageLayout/index.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/DefaultPageLayout/index.js new file mode 100644 index 0000000000..9742ac1c5c --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/DefaultPageLayout/index.js @@ -0,0 +1,2 @@ +// @flow +export { WidgetsForEnrollmentPageDefault, DefaultPageLayout } from './DefaultPageLayout.constants'; 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 deleted file mode 100644 index b59ad5a3a5..0000000000 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.component.js +++ /dev/null @@ -1,163 +0,0 @@ -// @flow -import React, { type ComponentType, useState, useCallback } from 'react'; -import withStyles from '@material-ui/core/styles/withStyles'; -import { spacersNum, spacers, colors } from '@dhis2/ui'; -import i18n from '@dhis2/d2-i18n'; -import { WidgetStagesAndEvents } from '../../../WidgetStagesAndEvents'; -import { WidgetEnrollment } from '../../../WidgetEnrollment'; -import { WidgetProfile } from '../../../WidgetProfile'; -import type { Props, PlainProps } from './EnrollmentPageDefault.types'; -import { WidgetWarning } from '../../../WidgetErrorAndWarning/WidgetWarning'; -import { WidgetFeedback } from '../../../WidgetFeedback'; -import { WidgetError } from '../../../WidgetErrorAndWarning/WidgetError'; -import { WidgetIndicator } from '../../../WidgetIndicator'; -import { WidgetEnrollmentComment } from '../../../WidgetEnrollmentComment'; -import { EnrollmentQuickActions } from './EnrollmentQuickActions'; -import { - TrackedEntityRelationshipsWrapper, -} from '../../common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper'; -import { AddRelationshipRefWrapper } from '../../EnrollmentEditEvent/AddRelationshipRefWrapper'; - -const getStyles = () => ({ - container: { - position: 'relative', - }, - columns: { - display: 'flex', - }, - leftColumn: { - flexGrow: 3, - flexShrink: 1, - width: 872, - display: 'flex', - flexDirection: 'column', - gap: spacers.dp16, - }, - rightColumn: { - flexGrow: 1, - flexShrink: 1, - paddingLeft: spacersNum.dp16, - width: 360, - display: 'flex', - flexDirection: 'column', - gap: spacers.dp16, - }, - title: { - fontSize: '1.25rem', - color: colors.grey900, - fontWeight: 500, - paddingTop: spacersNum.dp8, - paddingBottom: spacersNum.dp16, - }, -}); - -export const EnrollmentPageDefaultPlain = ({ - program, - teiId, - orgUnitId, - events, - enrollmentId, - stages, - onDelete, - onAddNew, - onViewAll, - onCreateNew, - widgetEffects, - hideWidgets, - classes, - onEventClick, - onLinkedRecordClick, - onUpdateTeiAttributeValues, - onUpdateEnrollmentDate, - onUpdateIncidentDate, - onEnrollmentError, - ruleEffects, -}: PlainProps) => { - const [mainContentVisible, setMainContentVisibility] = useState(true); - const [addRelationShipContainerElement, setAddRelationshipContainerElement] = - useState(undefined); - - const toggleVisibility = useCallback(() => setMainContentVisibility(current => !current), []); - - return ( - <> - -
-
{i18n.t('Enrollment Dashboard')}
-
-
- - -
-
- {addRelationShipContainerElement && - {}} - onOpenAddRelationship={toggleVisibility} - onCloseAddRelationship={toggleVisibility} - teiId={teiId} - onLinkedRecordClick={onLinkedRecordClick} - /> - } - - - - {!hideWidgets.indicator && ( - - )} - {!hideWidgets.feedback && ( - - )} - - {enrollmentId !== 'AUTO' && } -
-
-
- - ); -}; - - -export const EnrollmentPageDefaultComponent: ComponentType = withStyles( - getStyles, -)(EnrollmentPageDefaultPlain); diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.js index 9e37366cad..a3940941cf 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.js @@ -20,7 +20,7 @@ import { } from '../EnrollmentPage.actions'; import { useTrackerProgram } from '../../../../hooks/useTrackerProgram'; import { useCoreOrgUnit } from '../../../../metadataRetrieval/coreOrgUnit'; -import { EnrollmentPageDefaultComponent } from './EnrollmentPageDefault.component'; +import { EnrollmentPageLayout, DataStoreKeyByPage } from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout'; import { useProgramMetadata, useHideWidgetByRuleLocations, @@ -29,6 +29,15 @@ import { import { buildUrlQueryString, useLocationQuery } from '../../../../utils/routing'; import { useFilteredWidgetData } from './hooks/useFilteredWidgetData'; import { useLinkedRecordClick } from '../../common/TEIRelationshipsWidget'; +import { + useEnrollmentPageLayout, +} from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout/hooks/useEnrollmentPageLayout'; +import { DefaultPageLayout, WidgetsForEnrollmentPageDefault } from './DefaultPageLayout'; +import { LoadingMaskForPage } from '../../../LoadingMasks'; +import { + EnrollmentPageKeys, +} from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.constants'; + export const EnrollmentPageDefault = () => { const history = useHistory(); @@ -36,6 +45,14 @@ export const EnrollmentPageDefault = () => { const { enrollmentId, programId, teiId, orgUnitId } = useLocationQuery(); const { orgUnit, error } = useCoreOrgUnit(orgUnitId); const { onLinkedRecordClick } = useLinkedRecordClick(); + const { + pageLayout, + isLoading, + } = useEnrollmentPageLayout({ + selectedScopeId: programId, + defaultPageLayout: DefaultPageLayout, + dataStoreKey: DataStoreKeyByPage.ENROLLMENT_OVERVIEW, + }); const program = useTrackerProgram(programId); const { @@ -105,12 +122,22 @@ export const EnrollmentPageDefault = () => { const onEnrollmentError = message => dispatch(showEnrollmentError({ message })); + if (isLoading) { + return ( + + ); + } + if (error) { - return error.errorComponent; + return error?.errorComponent; } return ( - , program: TrackerProgram, enrollmentId: string, teiId: string, @@ -26,6 +33,8 @@ export type Props = {| onUpdateIncidentDate: (incidentDate: string) => void, onEnrollmentError: (message: string) => void, ruleEffects?: Array<{id: string, type: $Values}>; + pageLayout: PageLayoutConfig, + availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>, |}; export type PlainProps = {| 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 2cbfe51989..c2a486bad0 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 @@ -13,6 +13,11 @@ import { useCommonEnrollmentDomainData } from '../common/EnrollmentOverviewDomai 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'; const styles = { informativeMessage: { @@ -29,6 +34,11 @@ const EnrollmentAddEventPagePlain = ({ classes }: Props) => { attributeValues, error: commonDataError, } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); + const { pageLayout, isLoading } = useEnrollmentPageLayout({ + selectedScopeId: validIds[IdTypes.PROGRAM_ID]?.id, + dataStoreKey: DataStoreKeyByPage.ENROLLMENT_EVENT_NEW, + defaultPageLayout: DefaultPageLayout, + }); // $FlowFixMe const pageIsInvalid = (!loading && !Object.values(validIds)?.every(Id => Id?.valid)) || commonDataError || validatedIdsError; @@ -47,11 +57,11 @@ const EnrollmentAddEventPagePlain = ({ classes }: Props) => { if (pageIsInvalid) { return EnrollmentAddEventPageStatuses.PAGE_INVALID; } - if (loading) { + if (loading || isLoading) { return EnrollmentAddEventPageStatuses.LOADING; } return EnrollmentAddEventPageStatuses.DEFAULT; - }, [enrollmentId, loading, pageIsInvalid, programId, teiId, validIds]); + }, [enrollmentId, isLoading, loading, pageIsInvalid, programId, teiId, validIds]); if (pageStatus === EnrollmentAddEventPageStatuses.LOADING) { return ; @@ -60,6 +70,8 @@ const EnrollmentAddEventPagePlain = ({ classes }: Props) => { if (pageStatus === EnrollmentAddEventPageStatuses.DEFAULT) { return ( ({ container: { @@ -42,7 +38,9 @@ const styles = ({ typography }) => ({ }); const EnrollmentAddEventPagePain = ({ - programId, + pageLayout, + availableWidgets, + program, stageId, orgUnitId, teiId, @@ -57,88 +55,48 @@ const EnrollmentAddEventPagePain = ({ ready, classes, ...passOnProps -}: Props) => ( -
-
{i18n.t('Enrollment{{escape}} New Event', { escape: ':' })}
- {(() => { - if (pageFailure) { - return ( -
- {i18n.t('There was an error loading the page')} -
- ); - } else if (!orgUnitId) { - return ( - - {i18n.t('Choose a registering unit to start reporting')} - - ); - } else if (!ready) { - return null; - } +}: Props) => { + if (pageFailure) { + return ( +
+ {i18n.t('There was an error loading the page')} +
+ ); + } - return ( -
-
-
-
- {!stageId ? - - : - - } -
-
-
- - - {!hideWidgets.feedback && ( - - )} - {!hideWidgets.indicator && ( - - )} - - -
-
-
- ); - })()} -
); + if (!orgUnitId) { + return ( + + {i18n.t('Choose a registering unit to start reporting')} + + ); + } + + if (!ready) { + return null; + } + return ( +
+ +
+ ); +}; export const EnrollmentAddEventPageDefaultComponent: ComponentType<$Diff> = withStyles(styles)(EnrollmentAddEventPagePain); 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 bac3544e67..b7ca9e8460 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 @@ -10,7 +10,6 @@ import { NoticeBox } from '@dhis2/ui'; import { buildUrlQueryString, useLocationQuery } from '../../../../utils/routing'; import { useProgramInfo } from '../../../../hooks/useProgramInfo'; import { useEnrollmentAddEventTopBar, EnrollmentAddEventTopBar } from '../TopBar'; -import { EnrollmentAddEventPageDefaultComponent } from './EnrollmentAddEventPageDefault.component'; import { deleteEnrollment, fetchEnrollments } from '../../Enrollment/EnrollmentPage.actions'; import { useWidgetDataFromStore } from '../hooks'; @@ -21,8 +20,11 @@ import { updateEnrollmentEventsWithoutId, showEnrollmentError } from '../../comm import { dataEntryHasChanges as getDataEntryHasChanges } from '../../../DataEntry/common/dataEntryHasChanges'; import type { ContainerProps } from './EnrollmentAddEventPageDefault.types'; import { convertEventAttributeOptions } from '../../../../events/convertEventAttributeOptions'; +import { WidgetsForEnrollmentEventNew } from '../PageLayout/DefaultPageLayout.constants'; +import { EnrollmentAddEventPageDefaultComponent } from './EnrollmentAddEventPageDefault.component'; export const EnrollmentAddEventPageDefault = ({ + pageLayout, enrollment, attributeValues, commonDataError, @@ -133,7 +135,9 @@ export const EnrollmentAddEventPageDefault = ({ enrollmentSelectorFailure={commonDataError} /> 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 f6f342bc68..1c1dcc8799 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 @@ -1,9 +1,13 @@ // @flow import type { WidgetEffects, HideWidgets } from '../../common/EnrollmentOverviewDomain'; import type { ExternalSaveHandler } from '../../../WidgetEnrollmentEventNew'; +import type { + PageLayoutConfig, WidgetConfig, +} from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types'; +import { Program } from '../../../../metaData'; export type Props = {| - programId: string, + program: Program, stageId: string, orgUnitId: string, teiId: string, @@ -21,10 +25,13 @@ export type Props = {| pageFailure: boolean, ready: boolean, widgetReducerName: string, + pageLayout: PageLayoutConfig, + availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>, ...CssClasses, |}; export type ContainerProps = {| + pageLayout: PageLayoutConfig, enrollment: ?Object, attributeValues: ?Object, commonDataError: boolean, diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/NewEventWorkspace.component.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/NewEventWorkspace.component.js index e9f2dedb08..772a89e7b7 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/NewEventWorkspace.component.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/NewEventWorkspace.component.js @@ -12,7 +12,7 @@ import { Widget } from '../../../Widget'; import { WidgetStageHeader } from './WidgetStageHeader'; import { WidgetEventSchedule } from '../../../WidgetEventSchedule'; import { addEnrollmentEventPageDefaultActionTypes } from '../EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.actions'; -import type { Props } from './newEventWorkspace.types'; +import type { PlainProps, Props } from './newEventWorkspace.types'; import { useLocationQuery } from '../../../../utils/routing'; import { defaultDialogProps } from '../../../Dialogs/DiscardDialog.constants'; @@ -33,7 +33,7 @@ const NewEventWorkspacePlain = ({ onSave, classes, ...passOnProps -}: Props) => { +}: PlainProps) => { const { tab } = useLocationQuery(); const { events, enrolledAt, occurredAt } = useSelector(({ enrollmentDomain }) => enrollmentDomain?.enrollment); const [mode, setMode] = useState(tab ?? tabMode.REPORT); @@ -58,7 +58,7 @@ const NewEventWorkspacePlain = ({ } > -
+
, + Props, > = withStyles(styles)(NewEventWorkspacePlain); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/newEventWorkspace.types.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/newEventWorkspace.types.js index 55195aab2b..0621fb2b54 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/newEventWorkspace.types.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/newEventWorkspace.types.js @@ -12,5 +12,9 @@ export type Props = {| rulesExecutionDependencies: Object, onSave: ExternalSaveHandler, onCancel: () => void, +|}; + +export type PlainProps = {| + ...Props, ...CssClasses |}; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/PageLayout/DefaultPageLayout.constants.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/PageLayout/DefaultPageLayout.constants.js new file mode 100644 index 0000000000..6c869d96b6 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/PageLayout/DefaultPageLayout.constants.js @@ -0,0 +1,56 @@ +// @flow +import type { + PageLayoutConfig, + WidgetConfig, +} from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types'; +import { + DefaultWidgetsForEnrollmentOverview, + NewEventWorkspace, + WidgetTypes, +} from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout'; + +export const WidgetsForEnrollmentEventNew: $ReadOnly<{ [key: string]: WidgetConfig }> = Object.freeze({ + NewEventWorkspace, + ...DefaultWidgetsForEnrollmentOverview, +}); + +export const DefaultPageLayout: PageLayoutConfig = Object.freeze({ + leftColumn: [ + { + type: WidgetTypes.COMPONENT, + name: 'NewEventWorkspace', + }, + ], + rightColumn: [ + { + type: WidgetTypes.COMPONENT, + name: 'TrackedEntityRelationship', + }, + { + type: WidgetTypes.COMPONENT, + name: 'ErrorWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'WarningWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'FeedbackWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'IndicatorWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'ProfileWidget', + settings: { readOnlyMode: true }, + }, + { + type: WidgetTypes.COMPONENT, + name: 'EnrollmentWidget', + settings: { readOnlyMode: true }, + }, + ], +}); 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 16d527e887..06f6f99b68 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 @@ -1,182 +1,24 @@ // @flow -import React, { useCallback, useState } from 'react'; -import type { ComponentType } from 'react'; -import i18n from '@dhis2/d2-i18n'; -import { spacersNum } from '@dhis2/ui'; -import { dataEntryIds, dataEntryKeys } from 'capture-core/constants'; -import withStyles from '@material-ui/core/styles/withStyles'; +import React from 'react'; +import { dataEntryIds } from 'capture-core/constants'; import type { PlainProps } from './EnrollmentEditEventPage.types'; -import { pageStatuses } from './EnrollmentEditEventPage.constants'; -import { WidgetEventEdit } from '../../WidgetEventEdit/'; -import { WidgetError } from '../../WidgetErrorAndWarning/WidgetError'; -import { WidgetWarning } from '../../WidgetErrorAndWarning/WidgetWarning'; -import { WidgetFeedback } from '../../WidgetFeedback'; -import { WidgetIndicator } from '../../WidgetIndicator'; -import { WidgetProfile } from '../../WidgetProfile'; -import { WidgetEnrollment } from '../../WidgetEnrollment'; -import { WidgetAssignee } from '../../WidgetAssignee'; -import { IncompleteSelectionsMessage } from '../../IncompleteSelectionsMessage'; -import { WidgetEventComment } from '../../WidgetEventComment'; import { OrgUnitFetcher } from '../../OrgUnitFetcher'; import { TopBar } from './TopBar.container'; -import { - TrackedEntityRelationshipsWrapper, -} from '../common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper'; -import { AddRelationshipRefWrapper } from './AddRelationshipRefWrapper'; import { NoticeBox } from '../../NoticeBox'; +import { EnrollmentPageLayout } from '../common/EnrollmentOverviewDomain/EnrollmentPageLayout'; +import { WidgetsForEnrollmentEventEdit } from './PageLayout/DefaultPageLayout.constants'; +import { + EnrollmentPageKeys, +} from '../common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.constants'; -const styles = ({ typography }) => ({ - page: { - margin: spacersNum.dp16, - }, - addRelationshipContainer: { - margin: spacersNum.dp16, - }, - columns: { - display: 'flex', - }, - leftColumn: { - flexGrow: 3, - flexShrink: 1, - width: 872, - }, - rightColumn: { - flexGrow: 1, - flexShrink: 1, - paddingLeft: spacersNum.dp16, - width: 360, - display: 'flex', - flexDirection: 'column', - gap: '12px', - }, - title: { - ...typography.title, - margin: `${spacersNum.dp16}px 0`, - }, -}); - -const EnrollmentEditEventPageLeft = ({ - programStage, - teiId, - enrollmentId, - programId, - onGoBack, - orgUnitId, - scheduleDate, - eventStatus, - pageStatus, - onCancelEditEvent, - onHandleScheduleSave, - assignee, -}) => ( - <> - {pageStatus === pageStatuses.DEFAULT && programStage && ( - - )} - {pageStatus === pageStatuses.MISSING_DATA && ( - {i18n.t('The enrollment event data could not be found')} - )} - {pageStatus === pageStatuses.WITHOUT_ORG_UNIT_SELECTED && ( - - {i18n.t('Choose a registering unit to start reporting')} - - )} - -); - -const EnrollmentEditEventPageRight = ({ - mode, - programStage, - teiId, - enrollmentId, - trackedEntityTypeId, - programId, - widgetEffects, - hideWidgets, - onDelete, - onAddNew, - onLinkedRecordClick, - orgUnitId, - eventAccess, - assignee, - onEnrollmentError, - onEnrollmentSuccess, - getAssignedUserSaveContext, - onSaveAssignee, - onSaveAssigneeError, - addRelationShipContainerElement, - toggleVisibility, -}) => ( - <> - - - - - {!hideWidgets.feedback && ( - - )} - {!hideWidgets.indicator && ( - - )} - {addRelationShipContainerElement && ( - {}} - onLinkedRecordClick={onLinkedRecordClick} - /> - )} - - - -); - -const EnrollmentEditEventPagePain = ({ +export const EnrollmentEditEventPageComponent = ({ + pageLayout, mode, programStage, teiId, enrollmentId, trackedEntityTypeId, - programId, + program, enrollmentsAsOptions, trackedEntityName, teiDisplayName, @@ -184,7 +26,6 @@ const EnrollmentEditEventPagePain = ({ hideWidgets, onDelete, onAddNew, - classes, onGoBack, onLinkedRecordClick, orgUnitId, @@ -201,85 +42,53 @@ const EnrollmentEditEventPagePain = ({ getAssignedUserSaveContext, onSaveAssignee, onSaveAssigneeError, -}: PlainProps) => { - const [mainContentVisible, setMainContentVisible] = useState(true); - const [addRelationShipContainerElement, setAddRelationShipContainerElement] = useState(undefined); - - const toggleVisibility = useCallback(() => setMainContentVisible(current => !current), []); - - return ( - - -
- -
-
-
- {mode === dataEntryKeys.VIEW - ? i18n.t('Enrollment{{escape}} View Event', { escape: ':' }) - : i18n.t('Enrollment{{escape}} Edit Event', { escape: ':' })} -
-
-
- -
-
- -
-
- -
-
- ); -}; +}: PlainProps) => ( + + + > = - withStyles(styles)(EnrollmentEditEventPagePain); + trackedEntityTypeId={trackedEntityTypeId} + programStage={programStage} + onGoBack={onGoBack} + program={program} + orgUnitId={orgUnitId} + teiId={teiId} + enrollmentId={enrollmentId} + eventStatus={eventStatus} + initialScheduleDate={scheduleDate} + onCancelEditEvent={onCancelEditEvent} + onHandleScheduleSave={onHandleScheduleSave} + dataEntryKey={mode} + dataEntryId={dataEntryIds.ENROLLMENT_EVENT} + onLinkedRecordClick={onLinkedRecordClick} + onEnrollmentError={onEnrollmentError} + onEnrollmentSuccess={onEnrollmentSuccess} + pageStatus={pageStatus} + widgetEffects={widgetEffects} + hideWidgets={hideWidgets} + onDelete={onDelete} + onAddNew={onAddNew} + eventAccess={eventAccess} + assignee={assignee} + getAssignedUserSaveContext={getAssignedUserSaveContext} + onSaveAssignee={onSaveAssignee} + onSaveAssigneeError={onSaveAssigneeError} + /> + + +); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.constants.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.constants.js index b63e22ff46..ea8aa79bf6 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.constants.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.constants.js @@ -4,4 +4,5 @@ export const pageStatuses = { DEFAULT: 'DEFAULT', MISSING_DATA: 'MISSING_DATA', WITHOUT_ORG_UNIT_SELECTED: 'WITHOUT_ORG_UNIT_SELECTED', + LOADING: 'LOADING', }; 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 7c3de5ae8b..c7b8f81f27 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 @@ -24,6 +24,11 @@ import { cleanUpDataEntry } from '../../DataEntry'; import { useLinkedRecordClick } from '../common/TEIRelationshipsWidget'; import { pageKeys } from '../../App/withAppUrlSync'; import { withErrorMessageHandler } from '../../../HOC'; +import { + useEnrollmentPageLayout, +} from '../common/EnrollmentOverviewDomain/EnrollmentPageLayout/hooks/useEnrollmentPageLayout'; +import { DataStoreKeyByPage } from '../common/EnrollmentOverviewDomain/EnrollmentPageLayout'; +import { DefaultPageLayout } from './PageLayout/DefaultPageLayout.constants'; import { getProgramEventAccess } from '../../../metaData'; import { setAssignee, rollbackAssignee } from './EnrollmentEditEventPage.actions'; import { convertClientToServer } from '../../../converters'; @@ -40,7 +45,10 @@ const getEventScheduleDate = (event) => { return eventDataConvertValue?.toString(); }; -const getPageStatus = ({ orgUnitId, enrollmentSite, teiDisplayName, trackedEntityName, programStage, event }) => { +const getPageStatus = ({ orgUnitId, enrollmentSite, teiDisplayName, trackedEntityName, programStage, isLoading, event }) => { + if (isLoading) { + return pageStatuses.LOADING; + } if (orgUnitId) { return enrollmentSite && teiDisplayName && trackedEntityName && programStage && event ? pageStatuses.DEFAULT @@ -97,6 +105,11 @@ const EnrollmentEditEventPageWithContextPlain = ({ }: Props) => { const history = useHistory(); const dispatch = useDispatch(); + const { pageLayout, isLoading } = useEnrollmentPageLayout({ + selectedScopeId: programId, + dataStoreKey: DataStoreKeyByPage.ENROLLMENT_EVENT_EDIT, + defaultPageLayout: DefaultPageLayout, + }); const { event: eventId } = event; const { onLinkedRecordClick } = useLinkedRecordClick(); @@ -153,6 +166,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ trackedEntityName, programStage, event, + isLoading, }); const assignee = useAssignee(event); const getAssignedUserSaveContext = useAssignedUserSaveContext(event); @@ -169,8 +183,13 @@ const EnrollmentEditEventPageWithContextPlain = ({ dispatch(rollbackAssignee(assignedUser, prevAssignee, eventId)); }; + if (pageStatus === pageStatuses.LOADING) { + return ; + } + return ( void, onSaveAssigneeError: (prevAssignee: UserFormField | null) => void, - ...CssClasses, |}; export type Props = {| diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/PageLayout/DefaultPageLayout.constants.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/PageLayout/DefaultPageLayout.constants.js new file mode 100644 index 0000000000..6a12ec64e7 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/PageLayout/DefaultPageLayout.constants.js @@ -0,0 +1,68 @@ +// @flow +import type { + PageLayoutConfig, + WidgetConfig, +} from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types'; +import { + DefaultWidgetsForEnrollmentOverview, + EditEventWorkspace, + EventComment, + AssigneeWidget, + WidgetTypes, +} from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout'; + +export const WidgetsForEnrollmentEventEdit: $ReadOnly<{ [key: string]: WidgetConfig }> = Object.freeze({ + EditEventWorkspace, + EventComment, + AssigneeWidget, + ...DefaultWidgetsForEnrollmentOverview, +}); + +export const DefaultPageLayout: PageLayoutConfig = { + leftColumn: [ + { + type: WidgetTypes.COMPONENT, + name: 'EditEventWorkspace', + }, + ], + rightColumn: [ + { + type: WidgetTypes.COMPONENT, + name: 'AssigneeWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'ErrorWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'WarningWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'EventComment', + }, + { + type: WidgetTypes.COMPONENT, + name: 'FeedbackWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'IndicatorWidget', + }, + { + type: WidgetTypes.COMPONENT, + name: 'TrackedEntityRelationship', + }, + { + type: WidgetTypes.COMPONENT, + name: 'ProfileWidget', + settings: { readOnlyMode: true }, + }, + { + type: WidgetTypes.COMPONENT, + name: 'EnrollmentWidget', + settings: { readOnlyMode: true }, + }, + ], +}; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/AddRelationshipRefWrapper/AddRelationshipRefWrapper.component.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/AddRelationshipRefWrapper/AddRelationshipRefWrapper.component.js similarity index 100% rename from src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/AddRelationshipRefWrapper/AddRelationshipRefWrapper.component.js rename to src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/AddRelationshipRefWrapper/AddRelationshipRefWrapper.component.js diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/AddRelationshipRefWrapper/index.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/AddRelationshipRefWrapper/index.js similarity index 100% rename from src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/AddRelationshipRefWrapper/index.js rename to src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/AddRelationshipRefWrapper/index.js diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.constants.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.constants.js new file mode 100644 index 0000000000..b06c213045 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.constants.js @@ -0,0 +1,46 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import { + EnrollmentWidget, + ErrorWidget, + FeedbackWidget, + IndicatorWidget, + ProfileWidget, + TrackedEntityRelationship, + WarningWidget, +} from './LayoutComponentConfig'; + +export const EnrollmentPageKeys = Object.freeze({ + OVERVIEW: 'overview', + NEW_EVENT: 'newEvent', + EDIT_EVENT: 'editEvent', + VIEW_EVENT: 'viewEvent', +}); + +export const DefaultPageTitle = { + OVERVIEW: i18n.t('Dashboard'), + NEW_EVENT: i18n.t('New Event'), + EDIT_EVENT: i18n.t('Edit Event'), + VIEW_EVENT: i18n.t('View Event'), +}; + +// Default components are available across all Enrollment Pages +export const DefaultWidgetsForEnrollmentOverview = { + TrackedEntityRelationship, + ErrorWidget, + WarningWidget, + FeedbackWidget, + IndicatorWidget, + ProfileWidget, + EnrollmentWidget, +}; +export const WidgetTypes = Object.freeze({ + COMPONENT: 'component', + PLUGIN: 'plugin', +}); + +export const DataStoreKeyByPage = Object.freeze({ + ENROLLMENT_OVERVIEW: 'enrollmentOverviewLayout', + ENROLLMENT_EVENT_NEW: 'enrollmentEventNewLayout', + ENROLLMENT_EVENT_EDIT: 'enrollmentEventEditLayout', +}); diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types.js new file mode 100644 index 0000000000..22642dce02 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types.js @@ -0,0 +1,45 @@ +// @flow + + +import { WidgetTypes } from './DefaultEnrollmentLayout.constants'; + +type DefaultComponents = 'QuickActions' + | 'StagesAndEvents' + | 'AssigneeWidget' + | 'NewEventWorkspace' + | 'EditEventWorkspace' + | 'EnrollmentComment' + | 'EventComment' + | 'TrackedEntityRelationship' + | 'ErrorWidget' + | 'WarningWidget' + | 'FeedbackWidget' + | 'IndicatorWidget' + | 'ProfileWidget' + | 'EnrollmentWidget'; + +export type DefaultWidgetColumnConfig = { + type: typeof WidgetTypes.COMPONENT, + name: DefaultComponents, + settings?: Object, +} + +export type PluginWidgetColumnConfig = { + type: typeof WidgetTypes.PLUGIN, + source: string, +} + +export type ColumnConfig = DefaultWidgetColumnConfig | PluginWidgetColumnConfig; + +export type PageLayoutConfig = { + title?: ?string, + leftColumn: ?Array, + rightColumn: ?Array, +} + +export type WidgetConfig = { + Component: React$ComponentType, + shouldHideWidget?: (props: Object) => boolean, + getProps: Function, + getCustomSettings?: Function, +}; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/EnrollmentPageLayout.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/EnrollmentPageLayout.js new file mode 100644 index 0000000000..2b47e27789 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/EnrollmentPageLayout.js @@ -0,0 +1,112 @@ +// @flow +import React, { useCallback, useMemo, useState } from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { colors, spacers, spacersNum } from '@dhis2/ui'; +import { withStyles } from '@material-ui/core/styles'; +import { useWidgetColumns } from './hooks/useWidgetColumns'; +import { AddRelationshipRefWrapper } from './AddRelationshipRefWrapper'; +import type { PlainProps } from '../../../Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.types'; +import { DefaultPageTitle, EnrollmentPageKeys } from './DefaultEnrollmentLayout.constants'; + +const getEnrollmentPageStyles = () => ({ + container: { + padding: '16px 24px 16px 24px', + }, + contentContainer: { + position: 'relative', + }, + columns: { + display: 'flex', + gap: spacers.dp16, + }, + leftColumn: { + flexGrow: 3, + flexShrink: 1, + width: 872, + display: 'flex', + flexDirection: 'column', + gap: spacers.dp16, + }, + rightColumn: { + flexGrow: 1, + flexShrink: 1, + width: 360, + display: 'flex', + flexDirection: 'column', + gap: spacers.dp16, + }, + title: { + fontSize: '1.25rem', + color: colors.grey900, + fontWeight: 500, + paddingTop: spacersNum.dp8, + paddingBottom: spacersNum.dp16, + }, +}); + +const getTitle = (inputTitle, page) => { + const title = inputTitle || i18n.t('Enrollment'); + const titles = { + [EnrollmentPageKeys.OVERVIEW]: !inputTitle ? `${title} ${DefaultPageTitle.OVERVIEW}` : title, + [EnrollmentPageKeys.NEW_EVENT]: `${title}: ${DefaultPageTitle.NEW_EVENT}`, + [EnrollmentPageKeys.EDIT_EVENT]: `${title}: ${DefaultPageTitle.EDIT_EVENT}`, + [EnrollmentPageKeys.VIEW_EVENT]: `${title}: ${DefaultPageTitle.VIEW_EVENT}`, + }; + return titles[page] || title; +}; + +const EnrollmentPageLayoutPlain = ({ + pageLayout, + availableWidgets, + currentPage, + classes, + ...passOnProps +}: PlainProps) => { + const [mainContentVisible, setMainContentVisibility] = useState(true); + const [addRelationShipContainerElement, setAddRelationshipContainerElement] = + useState(undefined); + const toggleVisibility = useCallback(() => setMainContentVisibility(current => !current), []); + + const allProps = useMemo(() => ({ + ...passOnProps, + toggleVisibility, + addRelationShipContainerElement, + }), [addRelationShipContainerElement, passOnProps, toggleVisibility]); + + const { + leftColumnWidgets, + rightColumnWidgets, + } = useWidgetColumns({ + pageLayout, + availableWidgets, + props: allProps, + }); + + return ( +
+ +
+
{getTitle(pageLayout.title, currentPage)}
+
+ {pageLayout.leftColumn && !!leftColumnWidgets?.length && ( +
+ {leftColumnWidgets} +
+ )} + {pageLayout.rightColumn && !!rightColumnWidgets?.length && ( +
+ {rightColumnWidgets} +
+ )} +
+
+
+ ); +}; + +export const EnrollmentPageLayout = withStyles( + getEnrollmentPageStyles, +)(EnrollmentPageLayoutPlain); diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js new file mode 100644 index 0000000000..a7de4addff --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js @@ -0,0 +1,231 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import { WidgetStagesAndEvents } from '../../../../../WidgetStagesAndEvents'; +import type { Props as StagesAndEventProps } from '../../../../../WidgetStagesAndEvents/stagesAndEvents.types'; +import { TrackedEntityRelationshipsWrapper } from '../../../TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper'; +import type { + Props as TrackedEntityRelationshipProps, +} from '../../../TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.types'; +import { WidgetError } from '../../../../../WidgetErrorAndWarning/WidgetError'; +import type { Props as WidgetErrorProps } from '../../../../../WidgetErrorAndWarning/WidgetError/WidgetError.types'; +import { EnrollmentQuickActions } from '../../../../Enrollment/EnrollmentPageDefault/EnrollmentQuickActions'; +import { WidgetWarning } from '../../../../../WidgetErrorAndWarning/WidgetWarning'; +import type { + Props as WidgetWarningProps, +} from '../../../../../WidgetErrorAndWarning/WidgetWarning/WidgetWarning.types'; +import { WidgetFeedback } from '../../../../../WidgetFeedback'; +import type { IndicatorProps, Props as WidgetFeedbackProps } from '../../../../../WidgetFeedback/WidgetFeedback.types'; +import { WidgetIndicator } from '../../../../../WidgetIndicator'; +import { WidgetEnrollmentComment } from '../../../../../WidgetEnrollmentComment'; +import { WidgetProfile } from '../../../../../WidgetProfile'; +import type { Props as WidgetProfileProps } from '../../../../../WidgetProfile/widgetProfile.types'; +import { WidgetEnrollment } from '../../../../../WidgetEnrollment'; +import type { Props as WidgetEnrollmentProps } from '../../../../../WidgetEnrollment/enrollment.types'; +import type { WidgetConfig } from '../DefaultEnrollmentLayout.types'; +import { NewEventWorkspaceWrapper } from '../../../NewEventWorkspaceWrapper'; +import { WidgetEventEditWrapper } from '../../../WidgetEventEditWrapper'; +import { WidgetEventComment } from '../../../../../WidgetEventComment'; +import { WidgetAssignee } from '../../../../../WidgetAssignee'; + +export const QuickActions: WidgetConfig = { + Component: EnrollmentQuickActions, + getProps: ({ stages, events, ruleEffects }) => ({ + stages, + events, + ruleEffects, + }), +}; + +export const StagesAndEvents: WidgetConfig = { + Component: WidgetStagesAndEvents, + getProps: ({ program, stages, events, onViewAll, onCreateNew, onEventClick, ruleEffects }): StagesAndEventProps => ({ + programId: program.id, + stages, + events, + onViewAll, + onCreateNew, + onEventClick, + ruleEffects, + }), +}; + +export const TrackedEntityRelationship: WidgetConfig = { + Component: TrackedEntityRelationshipsWrapper, + shouldHideWidget: ({ addRelationShipContainerElement }) => !addRelationShipContainerElement, + getProps: ({ + program, + orgUnitId, + addRelationShipContainerElement, + toggleVisibility, + teiId, + onLinkedRecordClick, + }): TrackedEntityRelationshipProps => ({ + trackedEntityTypeId: program.trackedEntityType.id, + programId: program.id, + orgUnitId, + addRelationshipRenderElement: addRelationShipContainerElement, + onOpenAddRelationship: toggleVisibility, + onCloseAddRelationship: toggleVisibility, + teiId, + onLinkedRecordClick, + }), +}; + +export const ErrorWidget: WidgetConfig = { + Component: WidgetError, + getProps: ({ widgetEffects }): WidgetErrorProps => ({ + error: widgetEffects?.errors, + }), +}; + +export const WarningWidget: WidgetConfig = { + Component: WidgetWarning, + getProps: ({ widgetEffects }): WidgetWarningProps => ({ + warning: widgetEffects?.warnings, + }), +}; + +export const FeedbackWidget: WidgetConfig = { + Component: WidgetFeedback, + shouldHideWidget: ({ hideWidgets }) => hideWidgets?.feedback, + getProps: ({ widgetEffects }): WidgetFeedbackProps => ({ + feedback: widgetEffects?.feedbacks, + emptyText: i18n.t('No feedback for this enrollment yet'), + }), +}; + +export const IndicatorWidget: WidgetConfig = { + Component: WidgetIndicator, + shouldHideWidget: ({ hideWidgets }) => hideWidgets?.indicator, + getProps: ({ widgetEffects }): IndicatorProps => ({ + indicators: widgetEffects?.indicators, + emptyText: i18n.t('No indicator output for this enrollment yet'), + }), +}; + +export const EnrollmentComment: WidgetConfig = { + Component: WidgetEnrollmentComment, + getProps: (): void => {}, +}; + +export const ProfileWidget: WidgetConfig = { + Component: WidgetProfile, + getCustomSettings: ({ readOnlyMode = true }) => ({ + readOnlyMode, + }), + getProps: ({ + teiId, + program, + orgUnitId, + onUpdateTeiAttributeValues, + }): WidgetProfileProps => ({ + teiId, + programId: program.id, + orgUnitId, + onUpdateTeiAttributeValues, + }), +}; + +export const NewEventWorkspace: WidgetConfig = { + Component: NewEventWorkspaceWrapper, + getProps: ({ + program, + stageId, + orgUnitId, + teiId, + enrollmentId, + dataEntryHasChanges, + widgetReducerName, + rulesExecutionDependencies, + onSave, + onCancel, + }) => ({ + programId: program.id, + stageId, + orgUnitId, + teiId, + enrollmentId, + dataEntryHasChanges, + widgetReducerName, + rulesExecutionDependencies, + onSave, + onCancel, + }), +}; + +export const EnrollmentWidget: WidgetConfig = { + Component: WidgetEnrollment, + shouldHideWidget: ({ enrollmentId }) => enrollmentId === 'AUTO', + getCustomSettings: ({ readOnlyMode }) => ({ + readOnlyMode, + }), + getProps: ({ teiId, enrollmentId, program, onDelete, onAddNew, onUpdateEnrollmentDate, onUpdateIncidentDate, onEnrollmentError }): WidgetEnrollmentProps => ({ + teiId, + enrollmentId, + programId: program.id, + onDelete, + onAddNew, + onUpdateEnrollmentDate, + onUpdateIncidentDate, + onError: onEnrollmentError, + }), +}; + +export const EditEventWorkspace: WidgetConfig = { + Component: WidgetEventEditWrapper, + getProps: ({ + programStage, + onGoBack, + program, + orgUnitId, + teiId, + enrollmentId, + eventStatus, + onCancelEditEvent, + onHandleScheduleSave, + initialScheduleDate, + assignee, + }) => ({ + programStage, + onGoBack, + programId: program.id, + orgUnitId, + teiId, + enrollmentId, + eventStatus, + onCancelEditEvent, + onHandleScheduleSave, + initialScheduleDate, + assignee, + }), +}; + +export const AssigneeWidget: WidgetConfig = { + Component: WidgetAssignee, + getProps: ({ + programStage, + assignee, + getAssignedUserSaveContext, + eventAccess, + onSaveAssignee, + onSaveAssigneeError, + }) => ({ + enabled: programStage?.enableUserAssignment || false, + assignee, + getSaveContext: getAssignedUserSaveContext, + writeAccess: eventAccess?.write || false, + onSave: onSaveAssignee, + onSaveError: onSaveAssigneeError, + }), +}; + +export const EventComment: WidgetConfig = { + Component: WidgetEventComment, + getProps: ({ + dataEntryKey, + dataEntryId, + }) => ({ + dataEntryKey, + dataEntryId, + }), +}; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/index.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/index.js new file mode 100644 index 0000000000..54fb562c27 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/index.js @@ -0,0 +1,2 @@ +// @flow +export * from './LayoutComponentConfig'; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/hooks/useEnrollmentPageLayout.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/hooks/useEnrollmentPageLayout.js new file mode 100644 index 0000000000..26b6b3d02d --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/hooks/useEnrollmentPageLayout.js @@ -0,0 +1,41 @@ +// @flow +import { useApiMetadataQuery } from '../../../../../../utils/reactQueryHelpers'; +import type { PageLayoutConfig } from '../DefaultEnrollmentLayout.types'; + +type Props = { + selectedScopeId: ?string, + defaultPageLayout: PageLayoutConfig, + dataStoreKey: string, +} +export const useEnrollmentPageLayout = ({ selectedScopeId, defaultPageLayout, dataStoreKey }: Props) => { + const { data, isLoading, isError } = useApiMetadataQuery( + ['customEnrollmentPageLayout'], + { + resource: 'dataStore/capture?fields=.', + }, + { + enabled: !!selectedScopeId, + select: (captureDataStore: any) => { + const { entries } = captureDataStore ?? {}; + const enrollmentPageConfig = entries?.find(({ key }) => key === dataStoreKey)?.value; + const enrollmentPageConfigForScope: ?PageLayoutConfig = enrollmentPageConfig?.[selectedScopeId]; + + if (!enrollmentPageConfigForScope) return defaultPageLayout; + + const { leftColumn, rightColumn } = enrollmentPageConfigForScope; + + if (!leftColumn && !rightColumn) { + throw new Error('Invalid enrollment page layout configuration'); + } + + return enrollmentPageConfigForScope; + }, + }, + ); + + return { + pageLayout: data, + isLoading, + isError, + }; +}; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/hooks/useWidgetColumns.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/hooks/useWidgetColumns.js new file mode 100644 index 0000000000..e85137f6e3 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/hooks/useWidgetColumns.js @@ -0,0 +1,38 @@ +// @flow +import { useCallback, useMemo } from 'react'; +import type { + ColumnConfig, + PageLayoutConfig, + WidgetConfig, +} from '../DefaultEnrollmentLayout.types'; +import { renderWidgets } from '../renderPageComponents'; + +type Props = { + pageLayout: PageLayoutConfig, + availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>, + props: Object, +}; + +export const useWidgetColumns = ({ + pageLayout, + availableWidgets, + props, +}: Props) => { + const { + leftColumn, + rightColumn, + } = pageLayout; + + const createColumnWidgets = useCallback(column => + column?.map((widget: ColumnConfig) => renderWidgets(widget, availableWidgets, props)).filter(Boolean), + [availableWidgets, props], + ); + + const leftColumnWidgets = useMemo(() => createColumnWidgets(leftColumn), [leftColumn, createColumnWidgets]); + const rightColumnWidgets = useMemo(() => createColumnWidgets(rightColumn), [rightColumn, createColumnWidgets]); + + return { + leftColumnWidgets, + rightColumnWidgets, + }; +}; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/index.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/index.js new file mode 100644 index 0000000000..f75721d6aa --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/index.js @@ -0,0 +1,9 @@ +// @flow + +export { + DefaultWidgetsForEnrollmentOverview, +} from './DefaultEnrollmentLayout.constants'; +export { DataStoreKeyByPage } from './DefaultEnrollmentLayout.constants'; +export { WidgetTypes } from './DefaultEnrollmentLayout.constants'; +export * from './LayoutComponentConfig'; +export { EnrollmentPageLayout } from './EnrollmentPageLayout'; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/renderPageComponents/index.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/renderPageComponents/index.js new file mode 100644 index 0000000000..23535aaee6 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/renderPageComponents/index.js @@ -0,0 +1,2 @@ +// @flow +export { renderWidgets } from './renderPageComponents'; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/renderPageComponents/renderPageComponents.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/renderPageComponents/renderPageComponents.js new file mode 100644 index 0000000000..7d803df660 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/renderPageComponents/renderPageComponents.js @@ -0,0 +1,110 @@ +// @flow +import React from 'react'; +import log from 'loglevel'; +import type { + ColumnConfig, + DefaultWidgetColumnConfig, + PluginWidgetColumnConfig, WidgetConfig, +} from '../DefaultEnrollmentLayout.types'; +import { errorCreator } from '../../../../../../../capture-core-utils'; +import { EnrollmentPlugin } from '../../../EnrollmentPlugin'; +import { WidgetTypes } from '../DefaultEnrollmentLayout.constants'; + +const MemoizedWidgets: { [key: string]: React$ComponentType } = {}; +const UnsupportedWidgets: { [key: string]: boolean } = {}; + +const renderComponent = ( + widget: ColumnConfig, + availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>, + props: Object, +) => { + // Manually casting the type to DefaultWidgetColumnConfig + const { name, settings = {} } = ((widget: any): DefaultWidgetColumnConfig); + const widgetConfig = availableWidgets[name]; + + if (!widgetConfig) { + if (!UnsupportedWidgets[name]) { + log.error(errorCreator(`Widget ${name} is not supported`)({ name })); + UnsupportedWidgets[name] = true; + } + return null; + } + + const { getProps, shouldHideWidget, getCustomSettings } = widgetConfig; + + const hideWidget = shouldHideWidget && shouldHideWidget(props); + if (hideWidget) return null; + let widgetProps = {}; + + // In case the widget is not supported, we don't want to crash the app + try { + widgetProps = getProps(props); + } catch (error) { + log.error(errorCreator(`Error while getting widget props for widget ${name}`)({ error, props })); + return null; + } + const customSettings = getCustomSettings && getCustomSettings(settings); + + let Widget = MemoizedWidgets[name]; + + if (!Widget) { + Widget = widgetConfig.Component; + MemoizedWidgets[name] = React.memo(Widget); + } + + return ( + + ); +}; + +const getPropsForPlugin = ({ program, enrollmentId, teiId, orgUnitId }) => ({ + programId: program.id, + enrollmentId, + teiId, + orgUnitId, +}); + +const renderPlugin = ( + widget: ColumnConfig, + availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>, + props: Object, +) => { + // Manually casting the type to PluginWidgetColumnConfig + const { source } = ((widget: any): PluginWidgetColumnConfig); + let PluginWidget = MemoizedWidgets[source]; + + if (!PluginWidget) { + PluginWidget = EnrollmentPlugin; + MemoizedWidgets[source] = (PluginWidget); + } + const widgetProps = getPropsForPlugin(props); + + return ( + + ); +}; + +export const renderWidgets = ( + widget: ColumnConfig, + availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>, + props: Object, +) => { + const { type } = widget; + + if (type.toLowerCase() === WidgetTypes.COMPONENT) { + return renderComponent(widget, availableWidgets, props); + } else if (type.toLowerCase() === WidgetTypes.PLUGIN) { + return renderPlugin(widget, availableWidgets, props); + } + + log.error(errorCreator(`Widget type ${type} is not supported`)({ type })); + return null; +}; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentPlugin/EnrollmentPlugin.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentPlugin/EnrollmentPlugin.js new file mode 100644 index 0000000000..50a39fbcab --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentPlugin/EnrollmentPlugin.js @@ -0,0 +1,46 @@ +// @flow +import React, { useEffect, useRef, useState } from 'react'; +import { Plugin } from '@dhis2/app-runtime/build/es/experimental'; +import { useHistory } from 'react-router-dom'; + +type EnrollmentPluginProps = {| + enrollmentId: string, + programId?: string, + teiId: string, + orgUnitId: string, + pluginSource: string, +|}; + +export const EnrollmentPlugin = ({ pluginSource, ...passOnProps }: EnrollmentPluginProps) => { + const [pluginWidth, setPluginWidth] = useState(undefined); + const history = useHistory(); + const containerRef = useRef(); + + useEffect(() => { + const { current: container } = containerRef; + if (!container) return () => {}; + + const resizeObserver = new ResizeObserver((entries) => { + entries.forEach(entry => setPluginWidth(entry.contentRect.width)); + }); + + resizeObserver.observe(container); + + // Cleanup function + return () => { + resizeObserver.unobserve(container); + resizeObserver.disconnect(); + }; + }, [containerRef]); + + return ( +
+ +
+ ); +}; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentPlugin/index.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentPlugin/index.js new file mode 100644 index 0000000000..60396ea3a1 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentPlugin/index.js @@ -0,0 +1,2 @@ +// @flow +export { EnrollmentPlugin } from './EnrollmentPlugin'; diff --git a/src/core_modules/capture-core/components/Pages/common/NewEventWorkspaceWrapper/NewEventWorkspaceWrapper.js b/src/core_modules/capture-core/components/Pages/common/NewEventWorkspaceWrapper/NewEventWorkspaceWrapper.js new file mode 100644 index 0000000000..5ed4326cdf --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/NewEventWorkspaceWrapper/NewEventWorkspaceWrapper.js @@ -0,0 +1,37 @@ +// @flow + +import React from 'react'; +import { ProgramStageSelector } from '../../EnrollmentAddEvent/ProgramStageSelector'; +import { NewEventWorkspace } from '../../EnrollmentAddEvent/NewEventWorkspace'; +import type { Props } from '../../EnrollmentAddEvent/NewEventWorkspace/newEventWorkspace.types'; + +export const NewEventWorkspaceWrapper = ({ + stageId, + programId, + orgUnitId, + teiId, + enrollmentId, + ...passOnProps +}: Props) => { + if (!stageId) { + return ( + + ); + } + + return ( + + ); +}; diff --git a/src/core_modules/capture-core/components/Pages/common/NewEventWorkspaceWrapper/index.js b/src/core_modules/capture-core/components/Pages/common/NewEventWorkspaceWrapper/index.js new file mode 100644 index 0000000000..22b12d4f48 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/NewEventWorkspaceWrapper/index.js @@ -0,0 +1,2 @@ +// @flow +export { NewEventWorkspaceWrapper } from './NewEventWorkspaceWrapper'; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.types.js index 9fbb88f60b..2be182150f 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.types.js @@ -6,7 +6,6 @@ export type Props = {| teiId: string, programId: string, orgUnitId: string, - onAddRelationship: () => void, addRelationshipRenderElement: HTMLDivElement, onOpenAddRelationship: () => void, onCloseAddRelationship: () => void, diff --git a/src/core_modules/capture-core/components/Pages/common/WidgetEventEditWrapper/WidgetEventEditWrapper.js b/src/core_modules/capture-core/components/Pages/common/WidgetEventEditWrapper/WidgetEventEditWrapper.js new file mode 100644 index 0000000000..df1d6381fd --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/WidgetEventEditWrapper/WidgetEventEditWrapper.js @@ -0,0 +1,34 @@ +// @flow +import React from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { pageStatuses } from '../../EnrollmentEditEvent/EnrollmentEditEventPage.constants'; +import { IncompleteSelectionsMessage } from '../../../IncompleteSelectionsMessage'; +import { WidgetEventEdit } from '../../../WidgetEventEdit'; +import type { Props } from '../../../WidgetEventEdit/widgetEventEdit.types'; + +type WidgetProps = {| + pageStatus: string, + ...Props, +|} + +export const WidgetEventEditWrapper = ({ pageStatus, ...passOnProps }: WidgetProps) => { + if (pageStatus === pageStatuses.WITHOUT_ORG_UNIT_SELECTED) { + return ( + + {i18n.t('Choose a registering unit to start reporting')} + + ); + } + + if (pageStatus === pageStatuses.MISSING_DATA) { + return ( + {i18n.t('The enrollment event data could not be found')} + ); + } + + return ( + + ); +}; diff --git a/src/core_modules/capture-core/components/Pages/common/WidgetEventEditWrapper/index.js b/src/core_modules/capture-core/components/Pages/common/WidgetEventEditWrapper/index.js new file mode 100644 index 0000000000..08d4a64344 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/WidgetEventEditWrapper/index.js @@ -0,0 +1,2 @@ +// @flow +export { WidgetEventEditWrapper } from './WidgetEventEditWrapper'; diff --git a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetError.component.js b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetError.component.js index 3c72ab73b8..0e869beafb 100644 --- a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetError.component.js +++ b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetError.component.js @@ -2,13 +2,13 @@ import React from 'react'; import { colors } from '@dhis2/ui'; import { Widget } from '../../Widget'; -import type { Props } from './WidgetError.types'; +import type { PlainProps } from './WidgetError.types'; import { WidgetErrorAndWarningContent } from '../content/WidgetErrorAndWarningContent'; import { WidgetErrorHeader } from './WidgetErrorHeader'; import { widgetTypes } from '../content/WidgetTypes'; -export const WidgetError = ({ error, classes }: Props) => { +export const WidgetError = ({ error, classes }: PlainProps) => { const widgetType = widgetTypes.ERROR; if (!error?.length) { diff --git a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetError.types.js b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetError.types.js index 30b6d9d318..9ee62e7dfe 100644 --- a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetError.types.js +++ b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetError/WidgetError.types.js @@ -3,5 +3,9 @@ import type { Message } from '../content/WidgetErrorAndWarningContent.types'; export type Props = {| error?: Array, +|} + +export type PlainProps = {| + error?: Array, ...CssClasses |} diff --git a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarning.component.js b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarning.component.js index e72e5ca4f5..287cf6314e 100644 --- a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarning.component.js +++ b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarning.component.js @@ -2,12 +2,12 @@ import React from 'react'; import { colors } from '@dhis2/ui'; import { Widget } from '../../Widget'; -import type { Props } from './WidgetWarning.types'; +import type { PlainProps } from './WidgetWarning.types'; import { WidgetErrorAndWarningContent } from '../content/WidgetErrorAndWarningContent'; import { WidgetWarningHeader } from './WidgetWarningHeader'; import { widgetTypes } from '../content/WidgetTypes'; -export const WidgetWarning = ({ warning }: Props) => { +export const WidgetWarning = ({ warning }: PlainProps) => { const widgetType = widgetTypes.WARNING; if (!warning?.length) { diff --git a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarning.types.js b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarning.types.js index f446584f9b..86b36c31f7 100644 --- a/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarning.types.js +++ b/src/core_modules/capture-core/components/WidgetErrorAndWarning/WidgetWarning/WidgetWarning.types.js @@ -3,5 +3,9 @@ import type { Message } from '../content/WidgetErrorAndWarningContent.types'; export type Props = {| warning?: ?Array, +|} + +export type PlainProps = {| + ...Props, ...CssClasses |} diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js index 4075f5d0da..903bcf1333 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -8,7 +8,7 @@ import i18n from '@dhis2/d2-i18n'; import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import { useEnrollmentEditEventPageMode, useAvailableProgramStages } from 'capture-core/hooks'; import { useCoreOrgUnit } from 'capture-core/metadataRetrieval/coreOrgUnit'; -import type { Props } from './widgetEventEdit.types'; +import type { PlainProps, Props } from './widgetEventEdit.types'; import { startShowEditEventDataEntry } from './WidgetEventEdit.actions'; import { Widget } from '../Widget'; import { EditEventDataEntry } from './EditEventDataEntry/'; @@ -46,7 +46,6 @@ const styles = { }; export const WidgetEventEditPlain = ({ - classes, eventStatus, initialScheduleDate, programStage, @@ -59,7 +58,8 @@ export const WidgetEventEditPlain = ({ enrollmentId, teiId, assignee, -}: Props) => { + classes, +}: PlainProps) => { const dispatch = useDispatch(); const { currentPageMode } = useEnrollmentEditEventPageMode(eventStatus); const { orgUnit, error } = useCoreOrgUnit(orgUnitId); @@ -153,4 +153,4 @@ export const WidgetEventEditPlain = ({
) : ; }; -export const WidgetEventEdit: ComponentType<$Diff> = withStyles(styles)(WidgetEventEditPlain); +export const WidgetEventEdit: ComponentType = withStyles(styles)(WidgetEventEditPlain); diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js b/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js index 01e4caade5..df257a08a5 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js @@ -15,5 +15,9 @@ export type Props = {| teiId: string, initialScheduleDate?: string, assignee?: UserFormField | null, - ...CssClasses, |}; + +export type PlainProps = {| + ...Props, + ...CssClasses, +|} diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.component.js b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.component.js index 4e0790a5cb..9ed41d6db6 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.component.js +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.component.js @@ -2,10 +2,10 @@ import React, { useState } from 'react'; import i18n from '@dhis2/d2-i18n'; import { Widget } from '../Widget'; -import type { Props } from './WidgetFeedback.types'; +import type { PlainProps } from './WidgetFeedback.types'; import { WidgetFeedbackContent } from './WidgetFeedbackContent/WidgetFeedbackContent'; -export const WidgetFeedback = ({ feedback, emptyText }: Props) => { +export const WidgetFeedback = ({ feedback, emptyText }: PlainProps) => { const [openStatus, setOpenStatus] = useState(true); return ( diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.js b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.js index 04edc2cdc7..35d4446ff3 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.js +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.js @@ -24,6 +24,10 @@ export type ContentType = {| export type Props = {| feedback?: ?Array, emptyText: string, +|} + +export type PlainProps = {| + ...PlainProps, ...CssClasses |} diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js index 118ba44f3b..8790a26f25 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js @@ -11,7 +11,7 @@ import { errorCreator } from 'capture-core-utils'; import { Widget } from '../Widget'; import { LoadingMaskElementCenter } from '../LoadingMasks'; import { NoticeBox } from '../NoticeBox'; -import type { Props } from './widgetProfile.types'; +import type { PlainProps } from './widgetProfile.types'; import { useProgram, useTrackedEntityInstances, @@ -40,11 +40,11 @@ const showEditModal = (loading, error, showEdit, modalState) => const WidgetProfilePlain = ({ teiId, programId, - showEdit = false, + readOnlyMode = false, orgUnitId = '', onUpdateTeiAttributeValues, classes, -}: Props) => { +}: PlainProps) => { const [open, setOpenStatus] = useState(true); const [modalState, setTeiModalState] = useState(TEI_MODAL_STATE.CLOSE); const { loading: programsLoading, program, error: programsError } = useProgram(programId); @@ -67,8 +67,8 @@ const WidgetProfilePlain = ({ } = useUserRoles(); const isEditable = useMemo(() => - trackedEntityInstanceAttributes.length > 0 && showEdit, - [trackedEntityInstanceAttributes, showEdit]); + trackedEntityInstanceAttributes.length > 0 && !readOnlyMode, + [trackedEntityInstanceAttributes, readOnlyMode]); const loading = programsLoading || trackedEntityInstancesLoading || userRolesLoading; const error = programsError || trackedEntityInstancesError || userRolesError; @@ -158,4 +158,4 @@ const WidgetProfilePlain = ({ ); }; -export const WidgetProfile: ComponentType<$Diff> = withStyles(styles)(WidgetProfilePlain); +export const WidgetProfile: ComponentType<$Diff> = withStyles(styles)(WidgetProfilePlain); diff --git a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.js b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.js index 1127f4fbf2..4d9dd4f36a 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.js +++ b/src/core_modules/capture-core/components/WidgetProfile/widgetProfile.types.js @@ -3,9 +3,12 @@ export type Props = {| teiId: string, programId: string, - trackedEntityType: string, orgUnitId: string, - showEdit?: ?boolean, + readOnlyMode?: ?boolean, onUpdateTeiAttributeValues?: ?(attributes: Array<{ [key: string]: string }>, teiDisplayName: string) => void, +|}; + +export type PlainProps = {| + ...Props, ...CssClasses, |}; diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/Breadcrumbs/Breadcrumbs.component.js b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/Breadcrumbs/Breadcrumbs.component.js index 5b6d46cb08..5d1a015b11 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/Breadcrumbs/Breadcrumbs.component.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/Breadcrumbs/Breadcrumbs.component.js @@ -15,8 +15,10 @@ const styles = { const Slash = withStyles({ slash: { padding: 5 } })(({ classes }) => /); -const LinkedEntityMetadataSelectorStep = ({ currentStep, onNavigate }) => { - const initialText = i18n.t('New TEI Relationship'); +const LinkedEntityMetadataSelectorStep = ({ currentStep, trackedEntityTypeName, onNavigate }) => { + const initialText = i18n.t('New {{trackedEntityTypeName}} relationship', { + trackedEntityTypeName: trackedEntityTypeName?.toLowerCase(), + }); return (currentStep.value > NEW_TRACKED_ENTITY_RELATIONSHIP_WIZARD_STEPS.SELECT_LINKED_ENTITY_METADATA.value ? {initialText} : {initialText}); @@ -64,11 +66,13 @@ const BreadcrumbsPlain = ({ currentStep, onNavigate, linkedEntityMetadataName, + trackedEntityTypeName, classes, }: PlainProps) => (
onNavigate(NEW_TRACKED_ENTITY_RELATIONSHIP_WIZARD_STEPS.SELECT_LINKED_ENTITY_METADATA)} /> diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/Breadcrumbs/breadcrumbs.types.js b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/Breadcrumbs/breadcrumbs.types.js index 5f59de427b..4b92306053 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/Breadcrumbs/breadcrumbs.types.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/Breadcrumbs/breadcrumbs.types.js @@ -4,6 +4,7 @@ import { NEW_TRACKED_ENTITY_RELATIONSHIP_WIZARD_STEPS } from '../wizardSteps.con export type Props = {| currentStep: $Values, onNavigate: ($Values) => void, + trackedEntityTypeName: ?string, linkedEntityMetadataName?: string, |}; diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.component.js b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.component.js index 060eae794c..48b90410dc 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.component.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.component.js @@ -42,6 +42,7 @@ const NewTrackedEntityRelationshipPlain = ({ trackedEntityTypeId, programId, teiId, + trackedEntityTypeName, orgUnitId, onCancel, onSave, @@ -275,6 +276,7 @@ const NewTrackedEntityRelationshipPlain = ({ currentStep={currentStep} onNavigate={handleNavigation} linkedEntityMetadataName={selectedLinkedEntityMetadata?.name} + trackedEntityTypeName={trackedEntityTypeName} /> )} > diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.container.js b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.container.js index 03a05eb491..5fd43ec96f 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.container.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.container.js @@ -17,6 +17,7 @@ export const NewTrackedEntityRelationshipPlain = ({ teiId, orgUnitId, programId, + trackedEntityTypeName, relationshipTypes, trackedEntityTypeId, onCloseAddRelationship, @@ -54,6 +55,7 @@ export const NewTrackedEntityRelationshipPlain = ({ relationshipTypes={relationshipTypes} teiId={teiId} orgUnitId={orgUnitId} + trackedEntityTypeName={trackedEntityTypeName} trackedEntityTypeId={trackedEntityTypeId} programId={programId} renderElement={renderElement} diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.types.js b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.types.js index 5be15157cf..af8a45fc18 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.types.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.types.js @@ -24,6 +24,7 @@ type RenderTrackedEntityRegistration = ( export type ContainerProps = $ReadOnly<{| teiId: string, orgUnitId: string, + trackedEntityTypeName: ?string, renderElement: HTMLElement, relationshipTypes: RelationshipTypes, trackedEntityTypeId: string, @@ -46,6 +47,7 @@ export type PortalProps = $ReadOnly<{| orgUnitId: string, relationshipTypes: RelationshipTypes, trackedEntityTypeId: string, + trackedEntityTypeName: ?string, programId: string, onSave: () => void, onCancel: () => void, @@ -65,6 +67,7 @@ export type ComponentProps = $ReadOnly<{| orgUnitId: string, relationshipTypes: RelationshipTypes, trackedEntityTypeId: string, + trackedEntityTypeName: ?string, programId: string, onSave: () => void, onCancel: () => void, diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/WidgetTrackedEntityRelationship.component.js b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/WidgetTrackedEntityRelationship.component.js index 8af24e2195..22022ba633 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/WidgetTrackedEntityRelationship.component.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/WidgetTrackedEntityRelationship.component.js @@ -65,6 +65,7 @@ export const WidgetTrackedEntityRelationship = ({ { const query = useMemo(() => ({ resource: 'trackedEntityTypes', @@ -15,7 +11,7 @@ export const useTrackedEntityTypeName = (tetId: string) => { }, }), [tetId]); - const { data, isLoading, error } = useApiDataQuery( + const { data, isLoading, error } = useApiDataQuery( ['trackedEntityTypeName', tetId], query, { diff --git a/src/core_modules/capture-core/utils/cachedDataHooks/useValidatedIDsFromCache.js b/src/core_modules/capture-core/utils/cachedDataHooks/useValidatedIDsFromCache.js index 9f0a45eafa..6669ff2aef 100644 --- a/src/core_modules/capture-core/utils/cachedDataHooks/useValidatedIDsFromCache.js +++ b/src/core_modules/capture-core/utils/cachedDataHooks/useValidatedIDsFromCache.js @@ -16,7 +16,19 @@ export const IdTypes = Object.freeze({ ORG_UNIT_ID: 'orgUnitId', }); -export const useValidatedIDsFromCache = ({ programId, orgUnitId }: Props) => { +type ReturnTypes = {| + valid: { + [key: string]: ?{ + id: string, + valid: boolean, + type: $Values, + }, + }, + loading: boolean, + error?: Error, +|} + +export const useValidatedIDsFromCache = ({ programId, orgUnitId }: Props): ReturnTypes => { const [valid, setValid] = useState({ [IdTypes.PROGRAM_ID]: undefined, [IdTypes.ORG_UNIT_ID]: undefined, diff --git a/yarn.lock b/yarn.lock index 69534943c0..bf51267948 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3230,6 +3230,13 @@ jsbi "^4.1.0" tslib "^2.3.1" +"@jsdoc/salty@^0.2.1": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@jsdoc/salty/-/salty-0.2.7.tgz#98ddce519fd95d7bee605a658fabf6e8cbf7556d" + integrity sha512-mh8LbS9d4Jq84KLw8pzho7XC2q2/IJGiJss3xwRoLD1A+EE16SjN4PfaG4jRCzKegTFLlN0Zd8SdUPE6XdoPFg== + dependencies: + lodash "^4.17.21" + "@juggle/resize-observer@^3.3.1": version "3.4.0" resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" @@ -8095,10 +8102,12 @@ docdash@^0.4.0: resolved "https://registry.yarnpkg.com/docdash/-/docdash-0.4.0.tgz#05c3a50d83189981699ee0c076d3a3950db7ec00" integrity sha512-cdmktbSCiqO0gr6/YO5AQAZLOpCTv7usry2+cgsjcO0YnTtG4tlZvyVvZ+BLp3vyOovp6cunldeK+gjCPnG4Dg== -docdash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/docdash/-/docdash-1.2.0.tgz#f99dde5b8a89aa4ed083a3150383e042d06c7f49" - integrity sha512-IYZbgYthPTspgqYeciRJNPhSwL51yer7HAwDXhF5p+H7mTDbPvY3PCk/QDjNxdPCpWkaJVFC4t7iCNB/t9E5Kw== +docdash@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/docdash/-/docdash-2.0.2.tgz#e86dafb260b5dee4fbf1f7b34c8752634aa22f78" + integrity sha512-3SDDheh9ddrwjzf6dPFe1a16M6ftstqTNjik2+1fx46l24H9dD2osT2q9y+nBEC1wWz4GIqA48JmicOLQ0R8xA== + dependencies: + "@jsdoc/salty" "^0.2.1" doctrine@^2.1.0: version "2.1.0"