From 409b07e3cf0cd35ac079386f281da8bfc489b322 Mon Sep 17 00:00:00 2001 From: Christos Paschalidis Date: Tue, 16 Feb 2021 11:26:24 +0000 Subject: [PATCH] feat: adds missing parts from the chain (#1513) * chore: adds the missing parts * chore: adds text --- CHANGELOG.md | 7 - README.md | 30 ++-- cypress/integration/EnrollmentPage.feature | 29 ++++ cypress/integration/EnrollmentPage/index.js | 92 ++++++++++++ cypress/integration/LockedSelector.feature | 24 ++-- cypress/integration/LockedSelector/index.js | 20 +-- cypress/integration/NewPage/index.js | 5 +- cypress/support/helpers.js | 4 +- i18n/en.pot | 39 +++-- .../LockedSelector/LockedSelector.actions.js | 12 +- .../LockedSelector/LockedSelector.epics.js | 32 +++-- .../Program/ProgramSelector.component.js | 2 +- .../QuickSelector/QuickSelector.container.js | 9 +- .../SingleLockedSelect.component.js | 2 +- .../Enrollment/EnrollmentPage.actions.js | 7 +- .../Enrollment/EnrollmentPage.component.js | 3 +- .../Enrollment/EnrollmentPage.container.js | 3 - .../Pages/Enrollment/EnrollmentPage.epics.js | 38 +++-- .../Enrollment/MissingMessage.component.js | 135 ++++++++++++------ .../components/Pages/New/NewPage.container.js | 29 +--- .../components/Pages/New/NewPage.types.js | 2 +- .../useMissingCategoriesInProgramSelection.js | 6 +- .../metaData/helpers/getScopeInfo.js | 11 +- .../activePage.reducerDescription.js | 12 +- .../app.reducerDescriptionGetter.js | 2 +- ...rentSelections.reducerDescriptionGetter.js | 2 +- .../enrollmentPage.reducerDescription.js | 2 + 27 files changed, 374 insertions(+), 185 deletions(-) create mode 100644 cypress/integration/EnrollmentPage.feature create mode 100644 cypress/integration/EnrollmentPage/index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ede1ff885e..887039d3e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,3 @@ -# [1.9.0](https://github.com/dhis2/capture-app/compare/v1.8.3...v1.9.0) (2021-02-12) - - -### Features - -* DHIS2-10161 Create enrollment page with routing ([#1387](https://github.com/dhis2/capture-app/issues/1387)) ([efa2828](https://github.com/dhis2/capture-app/commit/efa282893c51cb97c66f6df0c2a6f3b4126da8f1)), closes [#1448](https://github.com/dhis2/capture-app/issues/1448) [#1449](https://github.com/dhis2/capture-app/issues/1449) [#1450](https://github.com/dhis2/capture-app/issues/1450) [#1451](https://github.com/dhis2/capture-app/issues/1451) [#1388](https://github.com/dhis2/capture-app/issues/1388) [#1495](https://github.com/dhis2/capture-app/issues/1495) [#1497](https://github.com/dhis2/capture-app/issues/1497) - ## [1.8.3](https://github.com/dhis2/capture-app/compare/v1.8.2...v1.8.3) (2021-02-08) diff --git a/README.md b/README.md index 898a72df57..675e18beed 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,14 @@ You can download and install Node on your machine from [here](https://nodejs.org You can find a tutorial on how to install `git` on your machine [here](https://www.atlassian.com/git/tutorials/install-git). -#### Yarn 1 +#### Yarn -You can install `yarn 1` on your machine following the instructions [here](https://classic.yarnpkg.com/en/docs/install/). +You can install `yarn` on your machine [here](https://classic.yarnpkg.com/en/docs/install/). ### Installing -Step by step instructions for setting up a development environment. +A step by step series of examples that tell you how to get a development env running. #### 1. Clone the repository @@ -50,31 +50,27 @@ To install the dependencies you will have to be at the source folder of the clon ``` yarn ``` -#### 3. Enable cross-site cookies in your browser (if server is running on a different domain) -Read about cross-site cookies and DHIS2 applications [here](https://dhis2.nu/2020/08/cross-origin-cookies) +#### 3. Set environment variables for backend communication -TL;DR: Using Google Chrome or a Chromium-based browser open chrome://flags/#same-site-by-default-cookies and set `SameSite by default cookies` to `Disabled` (Browser restart required). - -#### 4. Run the application - -To start the application locally and interact with it in the browser, run: +The application needs to communicate with a DHIS2 backend instance. The `.env.development` file contains default configuration, but you can override it by supplying a `.env.development.local` file in the root folder of the project. +An example of `.env.development`: ``` -yarn start +# Default admin/district authorization for development +REACT_APP_DHIS2_BASE_URL="https://debug.dhis2.org/2.34dev/" +REACT_APP_DHIS2_AUTHORIZATION="Basic c3lzdGVtOlN5c3RlbTEyMw==" +REACT_APP_TRACKER_CAPTURE_APP_PATH="https://debug.dhis2.org/2.34dev/dhis-web-tracker-capture" ``` -`http://localhost:3000` should automatically open in your browser. - -You will be prompted for a path to the server instance, a user name and a password. +#### 4. Run the application -The path to the server instance can also be set by supplying a `.env.development.local` file in the root folder of the project. An example of an `.env.development.local` file: +To start the application locally and interact with it on the browser, run: ``` -REACT_APP_DHIS2_BASE_URL="http://localhost:8080" +yarn start ``` - ## Built With This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). diff --git a/cypress/integration/EnrollmentPage.feature b/cypress/integration/EnrollmentPage.feature new file mode 100644 index 0000000000..912dda0de0 --- /dev/null +++ b/cypress/integration/EnrollmentPage.feature @@ -0,0 +1,29 @@ +Feature: User interacts with Search page + + Scenario: Navigating to the registration page for new event by clicking the link button + Given you are on an enrollment page + And you reset the program selection + And you select the Inpatient morbidity program + When you choose to register a new event program by clicking the link button + Then you see the registration form for the Inpatient morbidity program + + Scenario: Navigating to the working lists page for new event by clicking the link button + Given you are on an enrollment page + And you reset the program selection + And you select the Inpatient morbidity program + When you choose to be navigated to the working list by clicking the link button + Then you see the working lists for the Inpatient morbidity program + + Scenario: Navigating to the enrollment page for the same tet by clicking the link button + Given you are on an enrollment page + And you reset the program selection + When you select the MNCH PNC program + When you choose to enroll a person by clicking the link button + Then you see the registration form for the MNCH PNC program + + Scenario: Navigating to the enrollment page for a different tet by clicking the link button + Given you are on an enrollment page + And you reset the program selection + When you select the Malaria case diagnosis program + When you choose to enroll a malaria entity by clicking the link button + Then you see the registration form for the Malaria case diagnosis diff --git a/cypress/integration/EnrollmentPage/index.js b/cypress/integration/EnrollmentPage/index.js new file mode 100644 index 0000000000..9714c5701d --- /dev/null +++ b/cypress/integration/EnrollmentPage/index.js @@ -0,0 +1,92 @@ +beforeEach(() => { + cy.loginThroughForm(); +}); + +Given('you are on an enrollment page', () => { + cy.visit('/#/enrollment?programId=IpHINAT79UW&orgUnitId=UgYg0YW7ZIh&teiId=fhFQhO0xILJ&enrollmentId=gPDueU02tn8'); + cy.get('[data-test="dhis2-capture-enrollment-page-content"]') + .contains('Enrollment Dashboard'); +}); + +And('you reset the program selection', () => { + cy.get('[data-test="reset-selection-button"]') + .should('have.length.greaterThan', 2); + cy.get('[data-test="reset-selection-button"]') + .eq(0) + .click(); +}); + +And('you select the Inpatient morbidity program', () => { + cy.get('.Select').eq(0) + .type('Inpatient morbidi'); + cy.contains('Inpatient morbidity and mortality') + .click(); +}); + +And('you see the registration form for the Inpatient morbidity program', () => { + cy.get('[data-test="dhis2-capture-registration-page-content"]') + .contains('New Inpatient morbidity and mortality') + .should('exist'); + cy.get('[data-test="dhis2-capture-registration-page-content"]') + .contains('Saving to Inpatient morbidity and mortality in Taninahun (Malen) CHP') + .should('exist'); +}); + +And('you see the registration form for the Malaria case diagnosis', () => { + cy.get('[data-test="dhis2-capture-registration-page-content"]') + .contains('New malaria entity in program: Malaria case diagnosis, treatment and investigation') + .should('exist'); + cy.get('[data-test="dhis2-capture-registration-page-content"]') + .contains('Saving a malaria entity in Malaria case diagnosis, treatment and investigation in Taninahun (Malen) CHP.') + .should('exist'); +}); + +And('you see the registration form for the MNCH PNC program', () => { + cy.get('[data-test="dhis2-capture-registration-page-content"]') + .contains('New person in program: MNCH / PNC (Adult Woman)') + .should('exist'); + cy.get('[data-test="dhis2-capture-registration-page-content"]') + .contains('Saving a person in MNCH / PNC (Adult Woman) in Taninahun (Malen) CHP.') + .should('exist'); +}); + +And('you see the working lists for the Inpatient morbidity program', () => { + cy.get('[data-test="event-working-lists"]') + .find('tbody') + .find('tr') + .should('have.length', 15); +}); + +And('you select the Malaria case diagnosis program', () => { + cy.get('.Select').eq(0) + .type('Malaria case diag'); + cy.contains('Malaria case diagnosis') + .click(); +}); + +And('you select the MNCH PNC program', () => { + cy.get('.Select').eq(0) + .type('MNCH'); + cy.contains('PNC (Adult Woman)') + .click(); +}); + +And('you choose to register a new event program by clicking the link button', () => { + cy.contains('Create a new event in this program.') + .click(); +}); + +And('you choose to be navigated to the working list by clicking the link button', () => { + cy.contains('View working list in this program.') + .click(); +}); + +And('you choose to enroll a malaria entity by clicking the link button', () => { + cy.contains('Enroll a new malaria entity in this program.') + .click(); +}); + +And('you choose to enroll a person by clicking the link button', () => { + cy.contains('Enroll Carlos Cruz in this program.') + .click(); +}); diff --git a/cypress/integration/LockedSelector.feature b/cypress/integration/LockedSelector.feature index a7b96d82af..f5a29f88a5 100644 --- a/cypress/integration/LockedSelector.feature +++ b/cypress/integration/LockedSelector.feature @@ -137,17 +137,19 @@ Feature: Use the LockedSelector to navigate And you see the following Examples: - | url | state | message | - | /#/enrollment?programId=IpHINAT79UW&orgUnitId=UgYg0YW7ZIh&teiId=fhFQhO0xILJ&enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | - | /#/enrollment?enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | - | /#/enrollment?programId=IpHINAT79UW&enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | - | /#/enrollment?orgUnitId=UgYg0YW7ZIh&enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | - | /#/enrollment?orgUnitId=UgYg0YW7ZIh&teiId=fhFQhO0xILJ | teiAndOrgUnit | Choose program to view more information. | - | /#/enrollment?programId=IpHINAT79UW&teiId=fhFQhO0xILJ | teiAndChildProgram | Choose enrollment to view more information. | - | /#/enrollment?programId=qDkgAbB5Jlk&teiId=fhFQhO0xILJ | teiAndMalariaProgram | There are no enrollments | - | /#/enrollment?programId=lxAQ7Zs9VYR&teiId=fhFQhO0xILJ | teiAndEventProgram | You selected an Event program. | - | /#/enrollment?programId=IpHINAT79UW | error | Please enter a valid url. | - | /#/enrollment?orgUnitId=UgYg0YW7ZIh | error | Please enter a valid url. | + | url | state | message | + | /#/enrollment?enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | + | /#/enrollment?teiId=fhFQhO0xILJ&enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | + | /#/enrollment?orgUnitId=UgYg0YW7ZIh&enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | + | /#/enrollment?orgUnitId=UgYg0YW7ZIh&teiId=fhFQhO0xILJ&enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | + | /#/enrollment?programId=IpHINAT79UW&enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | + | /#/enrollment?programId=IpHINAT79UW&teiId=fhFQhO0xILJ&enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | + | /#/enrollment?programId=IpHINAT79UW&orgUnitId=UgYg0YW7ZIh&enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | + | /#/enrollment?programId=IpHINAT79UW&orgUnitId=UgYg0YW7ZIh&teiId=fhFQhO0xILJ&enrollmentId=gPDueU02tn8 | all | Enrollment Dashboard | + | /#/enrollment?orgUnitId=UgYg0YW7ZIh&teiId=fhFQhO0xILJ | teiAndOrgUnit | Carlos Cruz is enrolled in multiple programs. Choose a program. | + | /#/enrollment?programId=IpHINAT79UW&teiId=fhFQhO0xILJ | teiAndChildProgram | There are multiple enrollments for this program. Choose an enrollment to view the dashboard. | + | /#/enrollment?programId=qDkgAbB5Jlk&teiId=fhFQhO0xILJ | teiAndMalariaProgram | Carlos Cruz is a person and cannot be enrolled in the Malaria case diagnosis, treatment and investigation. Choose another program that allows person enrollment. Enroll a new malaria entity in this program.| + | /#/enrollment?programId=lxAQ7Zs9VYR&teiId=fhFQhO0xILJ | teiAndEventProgram | Antenatal care visit is an event program and does not have enrollments. | Scenario: Enrollment page > resetting the tei Given you land on the enrollment page by having typed only the enrollmentId on the url diff --git a/cypress/integration/LockedSelector/index.js b/cypress/integration/LockedSelector/index.js index cfb6df3c39..54a25efa08 100644 --- a/cypress/integration/LockedSelector/index.js +++ b/cypress/integration/LockedSelector/index.js @@ -257,14 +257,14 @@ And('there should be Child Programme domain forms visible to search with', () => .should('have.length', 1); }); -const selectedChildProgram = ['Selected Program', 'Child Programme']; -const selectedMalariaProgram = ['Selected Program', 'Malaria case diagnosis']; -const selectedEventProgram = ['Selected Program', 'Antenatal care visit']; +const selectedChildProgram = ['Selected program', 'Child Programme']; +const selectedMalariaProgram = ['Selected program', 'Malaria case diagnosis']; +const selectedEventProgram = ['Selected program', 'Antenatal care visit']; const emptyProgramSelection = ['Program', 'Select program']; const selectedOrgUnit = ['Selected registering unit', 'Taninahun (Malen) CHP']; const emptyOrgUnitSelection = ['Registering Organisation Unit']; const selectedTei = ['Selected', 'Carlos Cruz']; -const selectedEnrollment = ['Selected Enrollment', '2018-08-07 15:47']; +const selectedEnrollment = ['Selected enrollment', '2018-08-07 15:47']; const emptyEnrollmentSelection = ['Enrollment', 'Select...']; const lockedSelectorCases = { @@ -290,6 +290,8 @@ Then(/^you see the following (.*)$/, (message) => { And('you land on the enrollment page by having typed only the enrollmentId on the url', () => { cy.visit('/#/enrollment?enrollmentId=gPDueU02tn8'); + cy.get('[data-test="dhis2-capture-enrollment-page-content"]') + .contains('Enrollment Dashboard'); }); And('you reset the tei selection', () => { @@ -315,7 +317,7 @@ And('you reset the program selection', () => { And('you see message explaining you need to select a program', () => { cy.url().should('eq', `${Cypress.config().baseUrl}/#/enrollment?orgUnitId=UgYg0YW7ZIh&teiId=fhFQhO0xILJ&enrollmentId=gPDueU02tn8`); cy.get('[data-test="dhis2-capture-enrollment-page-content"]') - .contains('Choose program to view more information.'); + .contains('Carlos Cruz is enrolled in multiple programs. Choose a program.'); }); And('you reset the org unit selection', () => { @@ -350,7 +352,7 @@ And('you reset the enrollment selection', () => { And('you see message explaining you need to select an enrollment', () => { cy.url().should('eq', `${Cypress.config().baseUrl}/#/enrollment?programId=IpHINAT79UW&orgUnitId=UgYg0YW7ZIh&teiId=fhFQhO0xILJ`); cy.get('[data-test="dhis2-capture-enrollment-page-content"]') - .contains('Choose enrollment to view more information.'); + .contains('There are multiple enrollments for this program. Choose an enrollment to view the dashboard.'); }); And('you select the MNCH PNC program', () => { @@ -369,7 +371,9 @@ And('you select the Child Programme', () => { And('you see message explaining there are no enrollments for this program', () => { cy.get('[data-test="dhis2-capture-enrollment-page-content"]') - .contains('There are no enrollments'); + .contains('Carlos Cruz is not enrolled in this program.'); + cy.get('[data-test="dhis2-capture-enrollment-page-content"]') + .contains('Enroll Carlos Cruz in this program.'); }); And('you select the Antenatal care visit', () => { @@ -381,6 +385,6 @@ And('you select the Antenatal care visit', () => { And('you see message explaining this is an Event program', () => { cy.get('[data-test="dhis2-capture-enrollment-page-content"]') - .contains('You selected an Event program.'); + .contains('Antenatal care visit is an event program and does not have enrollments.'); }); diff --git a/cypress/integration/NewPage/index.js b/cypress/integration/NewPage/index.js index 77e538c12c..242e1eee66 100644 --- a/cypress/integration/NewPage/index.js +++ b/cypress/integration/NewPage/index.js @@ -36,10 +36,7 @@ And('you select tracked entity type person', () => { And('you see the registration form for the Person', () => { cy.get('[data-test="dhis2-capture-registration-page-content"]') - .contains('New Person') - .should('exist'); - cy.get('[data-test="dhis2-capture-registration-page-content"]') - .contains('New Person') + .contains('New person') .should('exist'); cy.get('[data-test="dhis2-capture-registration-page-content"]') .contains('Profile') diff --git a/cypress/support/helpers.js b/cypress/support/helpers.js index a16ccc91c9..9c01947080 100644 --- a/cypress/support/helpers.js +++ b/cypress/support/helpers.js @@ -22,6 +22,6 @@ Cypress.Commands.add('loginThroughForm', () => { cy.get('form').submit(); }); - cy.get('[data-test="loading-indicator-for-page"]', { timeout: 60000 }) - .should('not.exist'); + cy.get('[data-test="dhis2-capture-locked-selector"]', { timeout: 60000 }) + .should('exist'); }); diff --git a/i18n/en.pot b/i18n/en.pot index 27a0250685..56d85c7ad9 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: 2021-02-12T12:19:06.824Z\n" -"PO-Revision-Date: 2021-02-12T12:19:06.824Z\n" +"POT-Creation-Date: 2021-02-16T11:21:42.194Z\n" +"PO-Revision-Date: 2021-02-16T11:21:42.194Z\n" msgid "Choose one or more dates..." msgstr "" @@ -531,7 +531,7 @@ msgstr "" msgid "Show all" msgstr "" -msgid "Selected Program" +msgid "Selected program" msgstr "" msgid "Some programs are being filtered." @@ -582,27 +582,41 @@ msgstr "" msgid "There is an error while opening this enrollment. Please enter a valid url." msgstr "" -msgid "Enrollment with id '{{selectedEnrollmentId}}' doesn't exist" +msgid "Enrollment with id \"{{enrollmentId}}\" does not exist" +msgstr "" + +msgid "{{teiDisplayName}} is enrolled in multiple programs. Choose a program." msgstr "" -msgid "Choose program to view more information." +msgid "{{programName}} has categories. Choose all categories to view dashboard." msgstr "" -msgid "Choose enrollment to view more information." +msgid "" +"There are multiple enrollments for this program. Choose an enrollment to " +"view the dashboard." msgstr "" -msgid "There are no enrollments for {{teiDisplayName}} in the selected program" +msgid "{{teiDisplayName}} is not enrolled in this program." +msgstr "" + +msgid "Enroll {{teiDisplayName}} in this program." msgstr "" msgid "" -"You selected an Event program. Event programs do not have tracked entities " -"nor enrollments." +"{{teiDisplayName}} is a {{tetName}} and cannot be enrolled in the " +"{{programName}}. Choose another program that allows {{tetName}} enrollment. " msgstr "" -msgid "To create a new event" +msgid "Enroll a new {{selectedTetName}} in this program." msgstr "" -msgid "To view the working lists click" +msgid "{{programName}} is an event program and does not have enrollments." +msgstr "" + +msgid "Create a new event in this program." +msgstr "" + +msgid "View working list in this program." msgstr "" msgid "Delete event" @@ -840,9 +854,6 @@ msgstr "" msgid "Results found" msgstr "" -msgid "Selected program" -msgstr "" - msgid "Search {{uniqueAttrName}}" msgstr "" diff --git a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.actions.js b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.actions.js index 1481669733..4277da9419 100644 --- a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.actions.js +++ b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.actions.js @@ -15,9 +15,11 @@ export const lockedSelectorActionTypes = { ENROLLMENT_SELECTION_SET: 'LockedSelector.EnrollmentSelectionSet', ENROLLMENT_SELECTION_RESET: 'LockedSelector.EnrollmentSelectionReset', - FROM_URL_CURRENT_SELECTIONS_UPDATE: 'LockedSelector.FromUrlCurrentSelectionsUpdate', - FROM_URL_CURRENT_SELECTIONS_VALID: 'LockedSelector.FromUrlCurrentSelectionsValid', - FROM_URL_CURRENT_SELECTIONS_INVALID: 'LockedSelector.FromUrlCurrentSelectionsInvalid', + LOADING_START: 'LockedSelector.Loading', + FROM_URL_UPDATE: 'LockedSelector.FromUrlUpdate', + FROM_URL_UPDATE_COMPLETE: 'LockedSelector.FromUrlUpdateComplete', + FROM_URL_CURRENT_SELECTIONS_VALID: 'LockedSelector.FromUrlQueriesValid', + FROM_URL_CURRENT_SELECTIONS_INVALID: 'LockedSelector.FromUrlQueriesInvalid', EMPTY_ORG_UNIT_SET: 'LockedSelector.EmptyOrgUnitSet', NEW_REGISTRATION_PAGE_OPEN: 'LockedSelector.NewRegistrationPageOpen', @@ -51,10 +53,12 @@ export const openSearchPageFromLockedSelector = () => actionCreator(lockedSelect // these actions are being triggered only when the user updates the url from the url bar. // this way we keep our stored data in sync with the page the user is. -export const updateSelectionsFromUrl = (data: Object) => actionCreator(lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_UPDATE)(data); +export const updateSelectionsFromUrl = (data: Object) => actionCreator(lockedSelectorActionTypes.FROM_URL_UPDATE)(data); export const validSelectionsFromUrl = () => actionCreator(lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_VALID)(); export const invalidSelectionsFromUrl = (error: string) => actionCreator(lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_INVALID)({ error }); export const setCurrentOrgUnitBasedOnUrl = (orgUnit: Object) => actionCreator(lockedSelectorActionTypes.FETCH_ORG_UNIT_SUCCESS)(orgUnit); +export const startLoading = () => actionCreator(lockedSelectorActionTypes.LOADING_START)(); +export const completeUrlUpdate = () => actionCreator(lockedSelectorActionTypes.FROM_URL_UPDATE_COMPLETE)(); export const errorRetrievingOrgUnitBasedOnUrl = (error: string) => actionCreator(lockedSelectorActionTypes.FETCH_ORG_UNIT_ERROR)({ error }); export const setEmptyOrgUnitBasedOnUrl = () => actionCreator(lockedSelectorActionTypes.EMPTY_ORG_UNIT_SET)(); diff --git a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.epics.js b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.epics.js index 79ac26a54f..13fd2c6756 100644 --- a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.epics.js +++ b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.epics.js @@ -2,7 +2,7 @@ import i18n from '@dhis2/d2-i18n'; import { push } from 'connected-react-router'; import { ofType } from 'redux-observable'; -import { catchError, filter, map, switchMap } from 'rxjs/operators'; +import { catchError, filter, flatMap, map, startWith, switchMap } from 'rxjs/operators'; import { from, of } from 'rxjs'; import { lockedSelectorActionTypes, @@ -12,6 +12,8 @@ import { setCurrentOrgUnitBasedOnUrl, errorRetrievingOrgUnitBasedOnUrl, setEmptyOrgUnitBasedOnUrl, + startLoading, + completeUrlUpdate, } from './LockedSelector.actions'; import { programCollection } from '../../metaDataMemoryStores'; import { deriveUrlQueries, pageFetchesOrgUnitUsingTheOldWay, urlArguments } from '../../utils/url'; @@ -70,28 +72,38 @@ export const startAgainEpic = (action$: InputObservable) => export const getOrgUnitDataBasedOnUrlUpdateEpic = ( action$: InputObservable, - _: ReduxStore, + store: ReduxStore, { querySingleResource }: ApiUtils) => action$.pipe( - ofType(lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_UPDATE), + ofType(lockedSelectorActionTypes.FROM_URL_UPDATE), filter(action => action.payload.nextProps.orgUnitId), - switchMap(action => - querySingleResource(orgUnitsQuery(action.payload.nextProps.orgUnitId)) - .then(response => - setCurrentOrgUnitBasedOnUrl({ id: response.id, name: response.displayName })) - .catch(() => - errorRetrievingOrgUnitBasedOnUrl(i18n.t('Could not get organisation unit'))), + switchMap((action) => { + const { organisationUnits } = store.value; + const { orgUnitId } = action.payload.nextProps; + if (organisationUnits[orgUnitId]) { + return of(completeUrlUpdate()); + } + return from(querySingleResource(orgUnitsQuery(action.payload.nextProps.orgUnitId))) + .pipe( + flatMap(response => + of(setCurrentOrgUnitBasedOnUrl({ id: response.id, name: response.displayName }))), + catchError(() => + of(errorRetrievingOrgUnitBasedOnUrl(i18n.t('Could not get organisation unit')))), + startWith(startLoading()), + ); + }, )); export const setOrgUnitDataEmptyBasedOnUrlUpdateEpic = (action$: InputObservable) => action$.pipe( - ofType(lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_UPDATE), + ofType(lockedSelectorActionTypes.FROM_URL_UPDATE), filter(action => !action.payload.nextProps.orgUnitId), map(() => setEmptyOrgUnitBasedOnUrl())); export const validateSelectionsBasedOnUrlUpdateEpic = (action$: InputObservable, store: ReduxStore) => action$.pipe( ofType( + lockedSelectorActionTypes.FROM_URL_UPDATE_COMPLETE, lockedSelectorActionTypes.FETCH_ORG_UNIT_SUCCESS, lockedSelectorActionTypes.EMPTY_ORG_UNIT_SET, ), diff --git a/src/core_modules/capture-core/components/LockedSelector/QuickSelector/Program/ProgramSelector.component.js b/src/core_modules/capture-core/components/LockedSelector/QuickSelector/Program/ProgramSelector.component.js index f6041a5885..537404a7da 100644 --- a/src/core_modules/capture-core/components/LockedSelector/QuickSelector/Program/ProgramSelector.component.js +++ b/src/core_modules/capture-core/components/LockedSelector/QuickSelector/Program/ProgramSelector.component.js @@ -243,7 +243,7 @@ class ProgramSelector extends Component { renderSelectedProgram(selectedProgram) { return ( -

{ i18n.t('Selected Program') }

+

{ i18n.t('Selected program') }

{ router: { location: { pathname, query: { enrollmentId } } }, currentSelections: { categoriesMeta }, organisationUnits, - enrollmentPage: { enrollments, teiDisplayName }, + enrollmentPage: { enrollments, teiDisplayName, tetId }, } = state; const enrollmentsAsOptions = buildEnrollmentsAsOptions(enrollments, programId); - const { trackedEntityName } = getScopeInfo(programId); + const { trackedEntityName } = getScopeInfo(tetId); + const enrollmentLockedSelectReady = Array.isArray(enrollments); return { selectedProgramId: programId, selectedOrgUnitId: orgUnitId, @@ -41,10 +42,10 @@ const mapStateToProps = (state: Object) => { selectedOrgUnit: orgUnitId ? organisationUnits[orgUnitId] : null, currentPage: pathname.substring(1), selectedTeiName: teiDisplayName, - selectedTetName: trackedEntityName.length ? trackedEntityName : 'instance', + selectedTetName: trackedEntityName, selectedEnrollmentId: enrollmentId, enrollmentsAsOptions, - enrollmentLockedSelectReady: Array.isArray(enrollments), + enrollmentLockedSelectReady, }; }; diff --git a/src/core_modules/capture-core/components/LockedSelector/QuickSelector/SingleLockedSelect.component.js b/src/core_modules/capture-core/components/LockedSelector/QuickSelector/SingleLockedSelect.component.js index a023833245..be76ec8d63 100644 --- a/src/core_modules/capture-core/components/LockedSelector/QuickSelector/SingleLockedSelect.component.js +++ b/src/core_modules/capture-core/components/LockedSelector/QuickSelector/SingleLockedSelect.component.js @@ -99,7 +99,7 @@ const SingleLockedSelectPlain = selected && label ?

- {i18n.t('Selected')} {title} + {i18n.t('Selected')} {title.toLowerCase()}

{label}
diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.actions.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.actions.js index aed0650be2..b3398b9b1b 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.actions.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.actions.js @@ -38,15 +38,12 @@ export const showMissingMessageViewOnEnrollmentPage = () => export const showErrorViewOnEnrollmentPage = ({ error }: { error: string }) => actionCreator(enrollmentPageActionTypes.INFORMATION_ERROR_FETCH)({ error }); -export const successfulFetchingEnrollmentPageInformationFromUrl = ({ teiDisplayName, enrollmentsSortedByDate }: Object) => +export const successfulFetchingEnrollmentPageInformationFromUrl = ({ teiDisplayName, tetId, enrollmentsSortedByDate }: Object) => actionCreator(enrollmentPageActionTypes.INFORMATION_SUCCESS_FETCH)( - { teiDisplayName, enrollmentsSortedByDate }); + { teiDisplayName, tetId, enrollmentsSortedByDate }); export const openEnrollmentPage = ({ programId, orgUnitId, teiId, enrollmentId }: Object) => actionCreator(enrollmentPageActionTypes.PAGE_OPEN)({ programId, orgUnitId, teiId, enrollmentId }); export const cleanEnrollmentPage = () => actionCreator(enrollmentPageActionTypes.PAGE_CLEAN)(); - -export const resetProgramOnEnrollmentPage = () => - actionCreator(enrollmentPageActionTypes.CUSTOM_PROGRAM_RESET)(); 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 3e7dc24541..3c5d385eed 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 @@ -7,7 +7,6 @@ import { LockedSelector } from '../../LockedSelector'; import type { Props } from './EnrollmentPage.types'; import { enrollmentPageStatuses } from './EnrollmentPage.constants'; import LoadingMaskForPage from '../../LoadingMasks/LoadingMaskForPage.component'; -import { resetProgramOnEnrollmentPage } from './EnrollmentPage.actions'; import { withErrorMessageHandler } from '../../../HOC'; import { MissingMessage } from './MissingMessage.component'; @@ -24,7 +23,7 @@ const getStyles = ({ typography }) => ({ }); const EnrollmentPagePlain = ({ classes, enrollmentPageStatus }) => (<> - +
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 0e5cfe2cf3..5964514f37 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 @@ -45,7 +45,6 @@ const useComponentLifecycle = () => { scopeType, ]); - useEffect(() => () => dispatch(cleanEnrollmentPage()), [dispatch, teiId]); }; @@ -61,8 +60,6 @@ export const EnrollmentPage: ComponentType<{||}> = () => { useEffect(() => { dispatch(fetchEnrollmentPageInformation()); }, - // todo there is a bug when you have http://localhost:3000/#/enrollment?teiId=EaOyKGOIGRp&enrollmentId=W9YcBFADeRj - // because you have both tei and enrollmentId it stucks in an attermon loop [ selectedTeiId, dispatch, diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.epics.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.epics.js index 1565598102..92c7ab294d 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.epics.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPage.epics.js @@ -3,7 +3,7 @@ import { ofType } from 'redux-observable'; import { push } from 'connected-react-router'; import { catchError, flatMap, map, startWith } from 'rxjs/operators'; import i18n from '@dhis2/d2-i18n'; -import { from, of } from 'rxjs'; +import { concat, empty, from, of } from 'rxjs'; import moment from 'moment'; import { enrollmentPageActionTypes, @@ -17,7 +17,6 @@ import { import { urlArguments } from '../../../utils/url'; import { getAttributesFromScopeId } from '../../../metaData/helpers'; - const sortByDate = (enrollments = []) => enrollments.sort((a, b) => moment.utc(b.enrollmentDate).diff(moment.utc(a.enrollmentDate))); @@ -49,6 +48,7 @@ const fetchTeiStream = (teiId, querySingleResource) => return successfulFetchingEnrollmentPageInformationFromUrl({ teiDisplayName, + tetId: trackedEntityType, enrollmentsSortedByDate, }); }), @@ -78,25 +78,23 @@ export const startFetchingTeiFromEnrollmentIdEpic = (action$: InputObservable, s action$.pipe( ofType(enrollmentPageActionTypes.INFORMATION_USING_ENROLLMENT_ID_FETCH), flatMap(() => { - const { query: { enrollmentId, orgUnitId, programId, teiId } } = store.value.router.location; - const urlCompleted = Boolean(enrollmentId && orgUnitId && programId && teiId); + const { query: { enrollmentId } } = store.value.router.location; return from(querySingleResource({ resource: 'enrollments', id: enrollmentId })) .pipe( flatMap(({ trackedEntityInstance, program, orgUnit }) => ( - urlCompleted - ? - fetchTeiStream(trackedEntityInstance, querySingleResource) - : + concat( + fetchTeiStream(trackedEntityInstance, querySingleResource), of(openEnrollmentPage({ programId: program, orgUnitId: orgUnit, teiId: trackedEntityInstance, enrollmentId, - })) + })), + ) )), catchError(() => { - const error = i18n.t("Enrollment with id '{{selectedEnrollmentId}}' doesn't exist", { enrollmentId }); + const error = i18n.t('Enrollment with id "{{enrollmentId}}" does not exist', { enrollmentId }); return of(showErrorViewOnEnrollmentPage({ error })); }), startWith(showLoadingViewOnEnrollmentPage()), @@ -114,10 +112,24 @@ export const startFetchingTeiFromTeiIdEpic = (action$: InputObservable, store: R }), ); -export const openEnrollmentPageEpic = (action$: InputObservable) => +export const openEnrollmentPageEpic = (action$: InputObservable, store: ReduxStore) => action$.pipe( ofType(enrollmentPageActionTypes.PAGE_OPEN), - map(({ payload: { enrollmentId, programId, orgUnitId, teiId } }) => - push(`/enrollment?${urlArguments({ programId, orgUnitId, teiId, enrollmentId })}`), + flatMap(({ payload: { enrollmentId, programId, orgUnitId, teiId } }) => { + const { + query: { + enrollmentId: queryEnrollment, + orgUnitId: queryOrgUnitId, + programId: queryProgramId, + teiId: queryTeiId, + }, + } = store.value.router.location; + const urlCompleted = Boolean(queryEnrollment && queryOrgUnitId && queryProgramId && queryTeiId); + + if (!urlCompleted) { + return of(push(`/enrollment?${urlArguments({ programId, orgUnitId, teiId, enrollmentId })}`)); + } + return empty(); + }, ), ); 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 b53e45b440..c2b48250de 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 @@ -1,9 +1,11 @@ // @flow -import React, { useMemo } from 'react'; +import React, { useEffect, useState } from 'react'; import i18n from '@dhis2/d2-i18n'; import { useHistory } from 'react-router'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; +import { withStyles } from '@material-ui/core/styles'; import { useScopeInfo } from '../../../hooks/useScopeInfo'; +import { useMissingCategoriesInProgramSelection } from '../../../hooks/useMissingCategoriesInProgramSelection'; import { scopeTypes } from '../../../metaData/helpers/constants'; import { urlArguments } from '../../../utils/url'; import { IncompleteSelectionsMessage } from '../../IncompleteSelectionsMessage'; @@ -12,12 +14,17 @@ import { useEnrollmentInfo } from './hooks'; export const missingStatuses = { TRACKER_PROGRAM_WITH_ZERO_ENROLLMENTS_SELECTED: 'TRACKER_PROGRAM_WITH_ZERO_ENROLLMENTS_SELECTED', + TRACKER_PROGRAM_OF_DIFFERENT_TYPE_SELECTED: 'TRACKER_PROGRAM_OF_DIFFERENT_TYPE_SELECTED', EVENT_PROGRAM_SELECTED: 'EVENT_PROGRAM_SELECTED', MISSING_ENROLLMENT_SELECTION: 'MISSING_ENROLLMENT_SELECTION', + MISSING_PROGRAM_CATEGORIES_SELECTION: 'MISSING_PROGRAM_CATEGORIES_SELECTION', MISSING_PROGRAM_SELECTION: 'MISSING_PROGRAM_SELECTION', }; const useMissingStatus = () => { + const dispatch = useDispatch(); + const [missingStatus, setStatus] = useState(null); + const { programId, enrollmentId } = useSelector(({ router: { location: { query } } }) => ({ @@ -27,23 +34,34 @@ const useMissingStatus = () => { }), ); - const { scopeType } = useScopeInfo(programId); - const { programHasEnrollments, enrollmentsOnProgramContainEnrollmentId } = useEnrollmentInfo(enrollmentId, programId); - const missingStatus = useMemo(() => { + const { scopeType, tetId: scopeTetId } = useScopeInfo(programId); + const { programSelectionIsIncomplete } = useMissingCategoriesInProgramSelection(); + const { programHasEnrollments, enrollmentsOnProgramContainEnrollmentId, tetId } = useEnrollmentInfo(enrollmentId, programId); + const selectedProgramIsOfDifferentTypTetype = scopeTetId !== tetId; + useEffect(() => { const selectedProgramIsTracker = programId && scopeType === scopeTypes.TRACKER_PROGRAM; const selectedProgramIsEvent = programId && scopeType === scopeTypes.EVENT_PROGRAM; - if (selectedProgramIsTracker && programHasEnrollments && !enrollmentsOnProgramContainEnrollmentId) { - return missingStatuses.MISSING_ENROLLMENT_SELECTION; + + if (selectedProgramIsTracker && programSelectionIsIncomplete) { + setStatus(missingStatuses.MISSING_PROGRAM_CATEGORIES_SELECTION); + } else if (selectedProgramIsTracker && selectedProgramIsOfDifferentTypTetype) { + setStatus(missingStatuses.TRACKER_PROGRAM_OF_DIFFERENT_TYPE_SELECTED); + } else if (selectedProgramIsTracker && programHasEnrollments && !enrollmentsOnProgramContainEnrollmentId) { + setStatus(missingStatuses.MISSING_ENROLLMENT_SELECTION); } else if (selectedProgramIsTracker && !programHasEnrollments) { - return missingStatuses.TRACKER_PROGRAM_WITH_ZERO_ENROLLMENTS_SELECTED; + setStatus(missingStatuses.TRACKER_PROGRAM_WITH_ZERO_ENROLLMENTS_SELECTED); } else if (selectedProgramIsEvent) { - return missingStatuses.EVENT_PROGRAM_SELECTED; + setStatus(missingStatuses.EVENT_PROGRAM_SELECTED); + } else { + setStatus(missingStatuses.MISSING_PROGRAM_SELECTION); } - return missingStatuses.MISSING_PROGRAM_SELECTION; }, [ + dispatch, programId, + programSelectionIsIncomplete, programHasEnrollments, enrollmentsOnProgramContainEnrollmentId, + selectedProgramIsOfDifferentTypTetype, scopeType, ]); @@ -52,35 +70,58 @@ const useMissingStatus = () => { const useNavigations = () => { const history = useHistory(); + const { tetId } = useSelector(({ enrollmentPage }) => enrollmentPage); + const selectedProgramId: string = useSelector(({ router: { location: { query } } }) => query.programId); const selectedOrgUnitId: string = useSelector(({ router: { location: { query } } }) => query.orgUnitId); - const navigateToEventRegistrationPage = () => + const navigateToProgramRegistrationPage = () => history.push(`/new?${urlArguments({ programId: selectedProgramId, orgUnitId: selectedOrgUnitId })}`); const navigateToEventWorkingList = () => history.push(`/?${urlArguments({ programId: selectedProgramId, orgUnitId: selectedOrgUnitId })}`); + const navigateToTetRegistrationPage = () => + history.push(`/new?${urlArguments({ programId: selectedProgramId, orgUnitId: selectedOrgUnitId, trackedEntityTypeId: tetId })}`); - return { navigateToEventRegistrationPage, navigateToEventWorkingList }; + return { navigateToProgramRegistrationPage, navigateToEventWorkingList, navigateToTetRegistrationPage }; }; -export const MissingMessage = () => { - const { navigateToEventRegistrationPage, navigateToEventWorkingList } = useNavigations(); +const getStyles = () => ({ + lineHeight: { lineHeight: 1.8 }, + link: { + background: 'transparent', + padding: 0, + }, +}); + +export const MissingMessage = withStyles(getStyles)(({ classes }) => { + const { navigateToProgramRegistrationPage, navigateToEventWorkingList } = useNavigations(); const { missingStatus } = useMissingStatus(); - const { teiDisplayName } = useSelector(({ enrollmentPage }) => enrollmentPage); + const { teiDisplayName, tetId } = useSelector(({ enrollmentPage }) => enrollmentPage); + const selectedProgramId: string = + useSelector(({ router: { location: { query } } }) => query.programId); + const { trackedEntityName: tetName } = useScopeInfo(tetId); + const { programName, trackedEntityName: selectedTetName } = useScopeInfo(selectedProgramId); return (<> { missingStatus === missingStatuses.MISSING_PROGRAM_SELECTION && - {i18n.t('Choose program to view more information.')} + {i18n.t('{{teiDisplayName}} is enrolled in multiple programs. Choose a program.', { teiDisplayName })} + + } + + { + missingStatus === missingStatuses.MISSING_PROGRAM_CATEGORIES_SELECTION && + + {i18n.t('{{programName}} has categories. Choose all categories to view dashboard.', { programName })} } { missingStatus === missingStatuses.MISSING_ENROLLMENT_SELECTION && - {i18n.t('Choose enrollment to view more information.')} + {i18n.t('There are multiple enrollments for this program. Choose an enrollment to view the dashboard.')} } @@ -88,48 +129,62 @@ export const MissingMessage = () => { { missingStatus === missingStatuses.TRACKER_PROGRAM_WITH_ZERO_ENROLLMENTS_SELECTED && - {i18n.t('There are no enrollments for {{teiDisplayName}} in the selected program', { teiDisplayName })} +
+ {i18n.t('{{teiDisplayName}} is not enrolled in this program.', { teiDisplayName })} +
+ + + {i18n.t('Enroll {{teiDisplayName}} in this program.', { teiDisplayName })} + +
+
+
+ } + + { + missingStatus === missingStatuses.TRACKER_PROGRAM_OF_DIFFERENT_TYPE_SELECTED && + +
+ {i18n.t('{{teiDisplayName}} is a {{tetName}} and cannot be enrolled in the {{programName}}. Choose another program that allows {{tetName}} enrollment. ', { teiDisplayName, programName, tetName })} +
+ + {i18n.t('Enroll a new {{selectedTetName}} in this program.', { selectedTetName })} + +
+
} { missingStatus === missingStatuses.EVENT_PROGRAM_SELECTED && -
- {i18n.t('You selected an Event program. Event programs do not have tracked entities nor enrollments.')} +
+ {i18n.t('{{programName}} is an event program and does not have enrollments.', { programName })}
- {i18n.t('To create a new event')} - {' '} - here + {i18n.t('Create a new event in this program.')} - .
- {i18n.t('To view the working lists click')} - {' '} - here + {i18n.t('View working list in this program.')} - .
} ); -}; +}); 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 6b9ed9cd23..541994d1a3 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 @@ -14,9 +14,7 @@ import { urlArguments } from '../../../utils/url'; import { useCurrentOrgUnitInfo } from '../../../hooks/useCurrentOrgUnitInfo'; import { useCurrentProgramInfo } from '../../../hooks/useCurrentProgramInfo'; import { getScopeFromScopeId, TrackerProgram, TrackedEntityType } from '../../../metaData'; -import type { ProgramCategories } from './NewPage.types'; -import { programCollection } from '../../../metaDataMemoryStores'; - +import { useMissingCategoriesInProgramSelection } from '../../../hooks/useMissingCategoriesInProgramSelection'; const useUserWriteAccess = (scopeId) => { const scope = getScopeFromScopeId(scopeId); @@ -67,27 +65,10 @@ export const NewPage: ComponentType<{||}> = () => { // This is combo category selection. When you have selected a program but // the selection is incomplete we want the user to see a specific message - const programCategorySelectionIncomplete: boolean = - useSelector(({ currentSelections: { programId, complete } }) => programId && !complete); - - const missingCategoriesInProgramSelection: ProgramCategories = - useSelector(({ currentSelections: { categoriesMeta = {}, programId, complete } }) => { - const selectedProgram = programId && programCollection.get(programId); - if (selectedProgram && selectedProgram.categoryCombination && !complete) { - const programCategories = Array.from(selectedProgram.categoryCombination.categories.values()) - .map(({ id, name }) => ({ id, name })); - - return programCategories.filter(({ id }) => - !(Object.keys(categoriesMeta) - .some((programCategoryId => programCategoryId === id)) - ), - ); - } - return []; - }); + const { missingCategories, programSelectionIsIncomplete } = useMissingCategoriesInProgramSelection(); const orgUnitSelectionIncomplete: boolean = - useSelector(({ currentSelections: { orgUnitId, complete } }) => !orgUnitId && !complete); + useSelector(({ currentSelections: { orgUnitId, complete }, router: { location: { query } } }) => !(query.orgUnitId || orgUnitId) && !complete); const newPageStatus: $Keys = useSelector(({ newPage }) => newPage.newPageStatus); @@ -107,8 +88,8 @@ export const NewPage: ComponentType<{||}> = () => { handleMainPageNavigation={handleMainPageNavigation} currentScopeId={currentScopeId} orgUnitSelectionIncomplete={orgUnitSelectionIncomplete} - programCategorySelectionIncomplete={programCategorySelectionIncomplete} - missingCategoriesInProgramSelection={missingCategoriesInProgramSelection} + programCategorySelectionIncomplete={programSelectionIsIncomplete} + missingCategoriesInProgramSelection={missingCategories} writeAccess={writeAccess} newPageStatus={newPageStatus} error={error} 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 3b7bf7b983..dd976270ca 100644 --- a/src/core_modules/capture-core/components/Pages/New/NewPage.types.js +++ b/src/core_modules/capture-core/components/Pages/New/NewPage.types.js @@ -1,7 +1,7 @@ // @flow import { typeof newPageStatuses } from './NewPage.constants'; -export type ProgramCategories = Array<{|name: string, id: string|}> +type ProgramCategories = Array<{|name: string, id: string|}> export type ContainerProps = $ReadOnly<{| showMessageToSelectOrgUnitOnNewPage: ()=>void, diff --git a/src/core_modules/capture-core/hooks/useMissingCategoriesInProgramSelection.js b/src/core_modules/capture-core/hooks/useMissingCategoriesInProgramSelection.js index cbba2bb553..7e168e7739 100644 --- a/src/core_modules/capture-core/hooks/useMissingCategoriesInProgramSelection.js +++ b/src/core_modules/capture-core/hooks/useMissingCategoriesInProgramSelection.js @@ -5,8 +5,10 @@ import { programCollection } from '../metaDataMemoryStores'; type MissingCategories = {| missingCategories: any, programSelectionIsIncomplete: boolean |} export const useMissingCategoriesInProgramSelection = (): MissingCategories => - useSelector(({ currentSelections: { categoriesMeta = {}, programId, complete } }) => { - const selectedProgram = programId && programCollection.get(programId); + useSelector(({ currentSelections: { categoriesMeta = {}, programId, complete }, router: { location: { query } } }) => { + const program = query.program || programId; + + const selectedProgram = program && programCollection.get(program); if (selectedProgram && selectedProgram.categoryCombination && !complete) { const programCategories = Array.from(selectedProgram.categoryCombination.categories.values()) .map(({ id, name }) => ({ id, name })); diff --git a/src/core_modules/capture-core/metaData/helpers/getScopeInfo.js b/src/core_modules/capture-core/metaData/helpers/getScopeInfo.js index f7c4dbbe2e..30d377ff4f 100644 --- a/src/core_modules/capture-core/metaData/helpers/getScopeInfo.js +++ b/src/core_modules/capture-core/metaData/helpers/getScopeInfo.js @@ -15,17 +15,16 @@ export const deriveInfoFromScope = (scope: ?Scope) => { const trackedEntityName = ''; const programName = scope.name; - return { trackedEntityName, programName, scopeType: scopeTypes.EVENT_PROGRAM }; + return { trackedEntityName, programName, scopeType: scopeTypes.EVENT_PROGRAM, tetId: null }; } else if (scope instanceof TrackerProgram) { const trackedEntityName = scope.trackedEntityType ? scope.trackedEntityType.name.toLowerCase() : ''; const programName = scope.name; - - return { trackedEntityName, programName, scopeType: scopeTypes.TRACKER_PROGRAM }; + return { trackedEntityName, programName, scopeType: scopeTypes.TRACKER_PROGRAM, tetId: scope.trackedEntityType.id }; } else if (scope instanceof TrackedEntityType) { - const trackedEntityName = scope.name; + const trackedEntityName = scope.name.toLowerCase(); const programName = ''; - return { trackedEntityName, programName, scopeType: scopeTypes.TRACKED_ENTITY_TYPE }; + return { trackedEntityName, programName, scopeType: scopeTypes.TRACKED_ENTITY_TYPE, tetId: scope.id }; } - return { programName: '', scopeType: '', trackedEntityName: '' }; + return { programName: '', scopeType: '', trackedEntityName: '', tetId: '' }; }; diff --git a/src/core_modules/capture-core/reducers/descriptions/activePage.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/activePage.reducerDescription.js index 7a478ae569..3bc9dac223 100644 --- a/src/core_modules/capture-core/reducers/descriptions/activePage.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/activePage.reducerDescription.js @@ -11,10 +11,6 @@ import { eventWorkingListsActionTypes } from '../../components/Pages/MainPage/Ev import { enrollmentPageActionTypes } from '../../components/Pages/Enrollment/EnrollmentPage.actions'; export const activePageDesc = createReducerDescription({ - [lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_UPDATE]: state => ({ - ...state, - lockedSelectorLoads: true, - }), [lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_VALID]: state => ({ ...state, selectionsError: null, @@ -35,6 +31,14 @@ export const activePageDesc = createReducerDescription({ ...state, lockedSelectorLoads: false, }), + [lockedSelectorActionTypes.LOADING_START]: state => ({ + ...state, + lockedSelectorLoads: true, + }), + [lockedSelectorActionTypes.FROM_URL_UPDATE_COMPLETE]: state => ({ + ...state, + lockedSelectorLoads: false, + }), [viewEventPageActionTypes.VIEW_EVENT_FROM_URL]: state => ({ ...state, diff --git a/src/core_modules/capture-core/reducers/descriptions/app.reducerDescriptionGetter.js b/src/core_modules/capture-core/reducers/descriptions/app.reducerDescriptionGetter.js index 2fea868bc1..166b934420 100644 --- a/src/core_modules/capture-core/reducers/descriptions/app.reducerDescriptionGetter.js +++ b/src/core_modules/capture-core/reducers/descriptions/app.reducerDescriptionGetter.js @@ -118,7 +118,7 @@ export const getAppReducerDesc = (appUpdaters: Updaters) => createReducerDescrip ...state, locationSwitchInProgress: true, }), - [lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_UPDATE]: (state, action) => ({ + [lockedSelectorActionTypes.FROM_URL_UPDATE]: (state, action) => ({ ...state, page: action.payload.nextPage, }), diff --git a/src/core_modules/capture-core/reducers/descriptions/currentSelections.reducerDescriptionGetter.js b/src/core_modules/capture-core/reducers/descriptions/currentSelections.reducerDescriptionGetter.js index 40f1ee2cb9..04cf98f5d7 100644 --- a/src/core_modules/capture-core/reducers/descriptions/currentSelections.reducerDescriptionGetter.js +++ b/src/core_modules/capture-core/reducers/descriptions/currentSelections.reducerDescriptionGetter.js @@ -172,7 +172,7 @@ export const getCurrentSelectionsReducerDesc = (appUpdaters: Updaters) => create orgUnitId: null, complete: false, }), - [lockedSelectorActionTypes.FROM_URL_CURRENT_SELECTIONS_UPDATE]: (state, action) => { + [lockedSelectorActionTypes.FROM_URL_UPDATE]: (state, action) => { const { nextProps: selections } = action.payload; return { ...state, diff --git a/src/core_modules/capture-core/reducers/descriptions/enrollmentPage.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/enrollmentPage.reducerDescription.js index d0c8114932..2df9b37586 100644 --- a/src/core_modules/capture-core/reducers/descriptions/enrollmentPage.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/enrollmentPage.reducerDescription.js @@ -29,12 +29,14 @@ export const enrollmentPageDesc = createReducerDescription({ { enrollmentsSortedByDate, teiDisplayName, + tetId, }, }) => ({ ...state, enrollments: enrollmentsSortedByDate, enrollmentPageStatus: enrollmentPageStatuses.DEFAULT, teiDisplayName, + tetId, }), [DEFAULT_VIEW]: state => ({