diff --git a/.github/workflows/rebuild-docs.yml b/.github/workflows/rebuild-docs.yml new file mode 100644 index 0000000000..a5c86d59e0 --- /dev/null +++ b/.github/workflows/rebuild-docs.yml @@ -0,0 +1,19 @@ +name: 'dhis2: rebuild developer docs' + +on: + push: + branches: + - master + paths: + - 'docs/developer/**' + - 'CHANGELOG.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + rebuild-docs: + runs-on: ubuntu-latest + steps: + - run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEVELOPER_DOCS_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e021f34b..65bc0a3ec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +## [101.6.2](https://github.com/dhis2/capture-app/compare/v101.6.1...v101.6.2) (2024-10-01) + + +### Bug Fixes + +* [DHIS2-18004] sort events in rules engine by occurredAt and createdAt ([#3788](https://github.com/dhis2/capture-app/issues/3788)) ([2bb485e](https://github.com/dhis2/capture-app/commit/2bb485e0927e9137874131a982fe690b9f6c5361)) + +## [101.6.1](https://github.com/dhis2/capture-app/compare/v101.6.0...v101.6.1) (2024-09-29) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([cbeb102](https://github.com/dhis2/capture-app/commit/cbeb1022bf45b241a91dcfb8c3e49956b28b1c44)) + +# [101.6.0](https://github.com/dhis2/capture-app/compare/v101.5.0...v101.6.0) (2024-09-27) + + +### Features + +* support custom background color ([#3814](https://github.com/dhis2/capture-app/issues/3814)) ([22cfe58](https://github.com/dhis2/capture-app/commit/22cfe585b6b044c7d6b146c1449ccddb85d8abb4)) + +# [101.5.0](https://github.com/dhis2/capture-app/compare/v101.4.1...v101.5.0) (2024-09-24) + + +### Features + +* [DHIS2-17770] Org unit contextualization in self contained widgets ([#3720](https://github.com/dhis2/capture-app/issues/3720)) ([562b03a](https://github.com/dhis2/capture-app/commit/562b03a1cf2cb5cff5382bd433943f289c860095)) + ## [101.4.1](https://github.com/dhis2/capture-app/compare/v101.4.0...v101.4.1) (2024-09-19) diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js b/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js index 7ad8539f77..ca5bef0b08 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js @@ -66,7 +66,7 @@ Then('the user sees the enrollment organisation unit', () => { cy.get('[data-test="widget-enrollment"]').within(() => { cy.get('[data-test="widget-enrollment-icon-orgunit"]').should('exist'); cy.get('[data-test="widget-enrollment-orgunit"]') - .contains('Started at Ngelehun CHC') + .contains('Started at: Ngelehun CHC') .should('exist'); }); }); @@ -77,7 +77,7 @@ Then('the user sees the owner organisation unit', () => { 'exist', ); cy.get('[data-test="widget-enrollment-owner-orgunit"]') - .contains('Owned by Ngelehun CHC') + .contains('Owned by: Ngelehun CHC') .should('exist'); }); }); @@ -232,7 +232,7 @@ Then(/^the user successfully transfers the enrollment/, () => { cy.get('[data-test="widget-enrollment"]').within(() => { cy.get('[data-test="widget-enrollment-owner-orgunit"]') - .contains('Owned by Njandama MCHP') + .contains('Owned by: Njandama MCHP') .should('exist'); }); }); @@ -246,7 +246,7 @@ Then(/^the user types in (.*)/, (orgunit) => { Given(/^the enrollment owner organisation unit is (.*)/, (orgunit) => { cy.get('[data-test="widget-enrollment"]').within(() => { cy.get('[data-test="widget-enrollment-owner-orgunit"]') - .contains(`Owned by ${orgunit}`) + .contains(`Owned by: ${orgunit}`) .should('exist'); }); }); diff --git a/docs/developer/enrollment-plugins/manual-setup.mdx b/docs/developer/enrollment-plugins/manual-setup.mdx index 895973f25b..bae6afbe0f 100644 --- a/docs/developer/enrollment-plugins/manual-setup.mdx +++ b/docs/developer/enrollment-plugins/manual-setup.mdx @@ -76,7 +76,7 @@ You can also have different layouts for the three different enrollment pages. }, { "type": "component", - "name": "EnrollmentComment" + "name": "EnrollmentNote" }, { "type": "component", @@ -188,7 +188,7 @@ You can also have different layouts for the three different enrollment pages. }, { "type": "component", - "name": "EventComment" + "name": "EventNote" }, { "type": "component", @@ -229,8 +229,8 @@ type DefaultComponents = 'QuickActions' | 'AssigneeWidget' | 'NewEventWorkspace' | 'EditEventWorkspace' - | 'EnrollmentComment' - | 'EventComment' + | 'EnrollmentNote' + | 'EventNote' | 'TrackedEntityRelationship' | 'ErrorWidget' | 'WarningWidget' diff --git a/i18n/cs.po b/i18n/cs.po index 798be3e276..61c3bef8ce 100644 --- a/i18n/cs.po +++ b/i18n/cs.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Jiří Podhorecký , 2024\n" "Language-Team: Czech (https://app.transifex.com/hisp-uio/teams/100509/cs/)\n" @@ -1294,12 +1294,6 @@ msgstr "Widget pro zápis nelze načíst. Prosím zkuste to znovu později" msgid "Follow-up" msgstr "Následovat" -msgid "Started at {{orgUnitName}}" -msgstr "Zahájeno v {{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "Vlastník: {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "Zrušeno" diff --git a/i18n/en.pot b/i18n/en.pot index f642a82f83..d299bcedcf 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-09-08T20:48:49.045Z\n" -"PO-Revision-Date: 2024-09-08T20:48:49.045Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" +"PO-Revision-Date: 2024-09-02T11:08:16.281Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -1272,12 +1272,6 @@ msgstr "Enrollment widget could not be loaded. Please try again later" msgid "Follow-up" msgstr "Follow-up" -msgid "Started at {{orgUnitName}}" -msgstr "Started at {{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "Owned by {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "Cancelled" diff --git a/i18n/es.po b/i18n/es.po index 6dd93902ff..534e4de691 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -18,7 +18,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Juan M Alcantara Acosta , 2024\n" "Language-Team: Spanish (https://app.transifex.com/hisp-uio/teams/100509/es/)\n" @@ -1333,12 +1333,6 @@ msgstr "" msgid "Follow-up" msgstr "Seguimiento" -msgid "Started at {{orgUnitName}}" -msgstr "Comenzó en {{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "Propiedad de {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "Cancelar" diff --git a/i18n/fr.po b/i18n/fr.po index d8952a4af7..c9418fa104 100644 --- a/i18n/fr.po +++ b/i18n/fr.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Bram Piot , 2024\n" "Language-Team: French (https://app.transifex.com/hisp-uio/teams/100509/fr/)\n" @@ -1304,12 +1304,6 @@ msgstr "" msgid "Follow-up" msgstr "Suivi" -msgid "Started at {{orgUnitName}}" -msgstr "Commencé à {{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "Propriété de {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "Annulé" diff --git a/i18n/id.po b/i18n/id.po index 450489bebe..a4124e8e01 100644 --- a/i18n/id.po +++ b/i18n/id.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Aprisa Chrysantina , 2024\n" "Language-Team: Indonesian (https://app.transifex.com/hisp-uio/teams/100509/id/)\n" @@ -1272,12 +1272,6 @@ msgstr "Widget pendaftaran tidak dapat dimuat. Silakan coba lagi nanti" msgid "Follow-up" msgstr "Mengikuti" -msgid "Started at {{orgUnitName}}" -msgstr "Dimulai di {{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "Dimiliki oleh {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "Dibatalkan" diff --git a/i18n/lo.po b/i18n/lo.po index cb86ea18bc..b12cc74fe7 100644 --- a/i18n/lo.po +++ b/i18n/lo.po @@ -4,15 +4,15 @@ # Saysamone Sibounma, 2023 # Somkhit Bouavong , 2024 # Thuy Nguyen , 2024 -# Namwan Chanthavisouk, 2024 # Viktor Varland , 2024 +# Namwan Chanthavisouk, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Viktor Varland , 2024\n" +"Last-Translator: Namwan Chanthavisouk, 2024\n" "Language-Team: Lao (https://app.transifex.com/hisp-uio/teams/100509/lo/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1275,12 +1275,6 @@ msgstr "ບໍ່ສາມາດໂຫຼດລາຍການການລົງ msgid "Follow-up" msgstr "ຕິດຕາມ" -msgid "Started at {{orgUnitName}}" -msgstr "ເລີ່ມຈາກ {{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "ເປັນເຈົ້າຂອງໂດຍ {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "ຍົກເລີກແລ້ວ" @@ -1506,18 +1500,20 @@ msgid "New {{ eventName }} event" msgstr "ເຫດການໃໝ່ {{ eventName }}" msgid "An error occurred while deleting the event" -msgstr "" +msgstr "ພົບຂໍ້ຜິດພາດໃນລະຫວ່າງລົບເຫດການ" msgid "" "Deleting an event is permanent and cannot be undone. Are you sure you want " "to delete this event?" msgstr "" +"ການລົບເຫດການແມ່ນຖາວອນ ແລະ ບໍ່ສາມາດຍົກເລີກໄດ້. " +"ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການລົບເຫດການນີ້?" msgid "An error occurred when updating event status" -msgstr "" +msgstr "ພົບຂໍ້ຜິດພາດໃນລະຫວ່າງອັບເດດສະຖານະເຫດການ" msgid "Unskip" -msgstr "" +msgstr "ຍົກເລີກ" msgid "Skip" msgstr "ຂ້າມ" diff --git a/i18n/nb.po b/i18n/nb.po index 2e64114b50..e14d1e2e25 100644 --- a/i18n/nb.po +++ b/i18n/nb.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Merethe Wollan Blisten, 2024\n" "Language-Team: Norwegian Bokmål (https://app.transifex.com/hisp-uio/teams/100509/nb/)\n" @@ -1299,12 +1299,6 @@ msgstr "Registreringsmodulen kunne ikke lastes inn. Prøv igjen senere" msgid "Follow-up" msgstr "Oppfølging" -msgid "Started at {{orgUnitName}}" -msgstr "Begynt på {{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "Eid av {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "Kansellert" diff --git a/i18n/nl.po b/i18n/nl.po index 31991c9398..1ab637a352 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Charel van den Elsen, 2024\n" "Language-Team: Dutch (https://app.transifex.com/hisp-uio/teams/100509/nl/)\n" @@ -1310,12 +1310,6 @@ msgstr "" msgid "Follow-up" msgstr "Opvolgen" -msgid "Started at {{orgUnitName}}" -msgstr "Gestart bij {{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "Eigendom van {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "Geannuleerd" diff --git a/i18n/pt.po b/i18n/pt.po index 306edb6cbd..7531dccfa4 100644 --- a/i18n/pt.po +++ b/i18n/pt.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Viktor Varland , 2024\n" "Language-Team: Portuguese (https://app.transifex.com/hisp-uio/teams/100509/pt/)\n" @@ -1306,12 +1306,6 @@ msgstr "" msgid "Follow-up" msgstr "Acompanhamento" -msgid "Started at {{orgUnitName}}" -msgstr "Iniciado em {{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "Propriedade de {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "Cancelado" diff --git a/i18n/ro.po b/i18n/ro.po index 310d3bc638..5c64022113 100644 --- a/i18n/ro.po +++ b/i18n/ro.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Valeriu Plesca , 2024\n" "Language-Team: Romanian (https://app.transifex.com/hisp-uio/teams/100509/ro/)\n" @@ -1255,12 +1255,6 @@ msgstr "" msgid "Follow-up" msgstr "" -msgid "Started at {{orgUnitName}}" -msgstr "" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "Deținut de {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "Anulat" @@ -1481,6 +1475,23 @@ msgstr "Această etapă poate avea un singur eveniment" msgid "New {{ eventName }} event" msgstr "Eveniment nou {{ eventName }}" +msgid "An error occurred while deleting the event" +msgstr "" + +msgid "" +"Deleting an event is permanent and cannot be undone. Are you sure you want " +"to delete this event?" +msgstr "" + +msgid "An error occurred when updating event status" +msgstr "" + +msgid "Unskip" +msgstr "" + +msgid "Skip" +msgstr "" + msgid "To open this event, please wait until saving is complete" msgstr "" diff --git a/i18n/ru.po b/i18n/ru.po index ff0df86178..01896cce66 100644 --- a/i18n/ru.po +++ b/i18n/ru.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Russian (https://app.transifex.com/hisp-uio/teams/100509/ru/)\n" @@ -1320,12 +1320,6 @@ msgstr "" msgid "Follow-up" msgstr "Наблюдать" -msgid "Started at {{orgUnitName}}" -msgstr "Первоначальная регистрация в {{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "Принадлежит {{ownerOrgUnit}}" - msgid "Cancelled" msgstr "Отменен/а/о" diff --git a/i18n/uz_UZ_Cyrl.po b/i18n/uz_UZ_Cyrl.po index 6ca571beea..32a8d2e932 100644 --- a/i18n/uz_UZ_Cyrl.po +++ b/i18n/uz_UZ_Cyrl.po @@ -1,12 +1,11 @@ # # Translators: -# Philip Larsen Donnelly, 2024 # Khurshid Ibatov , 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Khurshid Ibatov , 2024\n" "Language-Team: Uzbek (Cyrillic) (https://app.transifex.com/hisp-uio/teams/100509/uz@Cyrl/)\n" @@ -1269,12 +1268,6 @@ msgstr "" msgid "Follow-up" msgstr "Кузатиш" -msgid "Started at {{orgUnitName}}" -msgstr " {{orgUnitName}} да бошланган" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr " {{ownerOrgUnit}} эгалиги асосида" - msgid "Cancelled" msgstr "Бекор қилинди" diff --git a/i18n/vi.po b/i18n/vi.po index 8e1ac63a89..10dc9f241e 100644 --- a/i18n/vi.po +++ b/i18n/vi.po @@ -2,15 +2,15 @@ # Translators: # Philip Larsen Donnelly, 2024 # Mai Nguyen , 2024 -# Thuy Nguyen , 2024 # Viktor Varland , 2024 +# Thuy Nguyen , 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Viktor Varland , 2024\n" +"Last-Translator: Thuy Nguyen , 2024\n" "Language-Team: Vietnamese (https://app.transifex.com/hisp-uio/teams/100509/vi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1256,12 +1256,6 @@ msgstr "" msgid "Follow-up" msgstr "Theo dõi sau" -msgid "Started at {{orgUnitName}}" -msgstr "" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "" - msgid "Cancelled" msgstr "Đã hủy" @@ -1314,7 +1308,7 @@ msgid "Scheduled date cannot be changed for {{ eventStatus }} events" msgstr "" msgid "Event completed" -msgstr "" +msgstr "Sự kiện đã hoàn tất" msgid "The event cannot be edited after it has been completed" msgstr "" diff --git a/i18n/zh.po b/i18n/zh.po index 70a175384c..559372ca30 100644 --- a/i18n/zh.po +++ b/i18n/zh.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: easylin , 2024\n" "Language-Team: Chinese (https://app.transifex.com/hisp-uio/teams/100509/zh/)\n" @@ -1241,12 +1241,6 @@ msgstr "无法加载报名的小部件。请稍后再试" msgid "Follow-up" msgstr "后续" -msgid "Started at {{orgUnitName}}" -msgstr "始于{{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "由{{ownerOrgUnit}}拥有" - msgid "Cancelled" msgstr "已取消" diff --git a/i18n/zh_CN.po b/i18n/zh_CN.po index 2216a33b29..d370cc02a6 100644 --- a/i18n/zh_CN.po +++ b/i18n/zh_CN.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-08-10T10:42:21.141Z\n" +"POT-Creation-Date: 2024-09-02T11:08:16.281Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: 晓东 林 <13981924470@126.com>, 2024\n" "Language-Team: Chinese (China) (https://app.transifex.com/hisp-uio/teams/100509/zh_CN/)\n" @@ -1240,12 +1240,6 @@ msgstr "无法加载报名的小部件。请稍后再试" msgid "Follow-up" msgstr "后续" -msgid "Started at {{orgUnitName}}" -msgstr "始于{{orgUnitName}}" - -msgid "Owned by {{ownerOrgUnit}}" -msgstr "由{{ownerOrgUnit}}拥有" - msgid "Cancelled" msgstr "已取消" diff --git a/package.json b/package.json index efb07db0ef..ffe44ffb82 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "capture-app", "homepage": ".", - "version": "101.4.1", + "version": "101.6.2", "cacheVersion": "7", "serverVersion": "38", "license": "BSD-3-Clause", @@ -10,7 +10,7 @@ "packages/rules-engine" ], "dependencies": { - "@dhis2/rules-engine-javascript": "101.4.1", + "@dhis2/rules-engine-javascript": "101.6.2", "@dhis2/app-runtime": "^3.9.3", "@dhis2/d2-i18n": "^1.1.0", "@dhis2/d2-icons": "^1.0.1", diff --git a/packages/rules-engine/package.json b/packages/rules-engine/package.json index e4e1b00d9d..d773ecb1fe 100644 --- a/packages/rules-engine/package.json +++ b/packages/rules-engine/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/rules-engine-javascript", - "version": "101.4.1", + "version": "101.6.2", "license": "BSD-3-Clause", "main": "./build/cjs/index.js", "scripts": { diff --git a/packages/rules-engine/src/services/VariableService/VariableService.js b/packages/rules-engine/src/services/VariableService/VariableService.js index 193de2677e..9b596a8190 100644 --- a/packages/rules-engine/src/services/VariableService/VariableService.js +++ b/packages/rules-engine/src/services/VariableService/VariableService.js @@ -85,7 +85,7 @@ export class VariableService { this.defaultValues = defaultValues; - this.structureEvents = getStructureEvents(dateUtils.compareDates); + this.structureEvents = getStructureEvents(dateUtils.compareDates, onProcessValue); } getVariables({ diff --git a/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js b/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js index 7c4fcab7f8..9660397845 100644 --- a/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js +++ b/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js @@ -1,9 +1,11 @@ // @flow +import { typeKeys } from '../../../constants'; import { eventStatuses } from '../constants'; import type { EventData, EventsData, CompareDates, + ProcessValue, } from '../variableService.types'; const createEventsContainer = (events: EventsData) => { @@ -16,20 +18,16 @@ const createEventsContainer = (events: EventsData) => { return { all: events, byStage: eventsDataByStage }; }; -export const getStructureEvents = (compareDates: CompareDates) => { - const compareEvents = (first: EventData, second: EventData): number => { - let result; - if (!first.occurredAt && !second.occurredAt) { - result = 0; - } else if (!first.occurredAt) { - result = 1; - } else if (!second.occurredAt) { - result = -1; - } else { - result = compareDates(first.occurredAt, second.occurredAt); - } - return result; - }; +export const getStructureEvents = (compareDates: CompareDates, processValue: ProcessValue) => { + const compareEvents = (first: EventData, second: EventData): number => + compareDates( + processValue(first.occurredAt, typeKeys.DATE), + processValue(second.occurredAt, typeKeys.DATE), + ) || + compareDates( + processValue(first.createdAt, typeKeys.DATETIME), + processValue(second.createdAt, typeKeys.DATETIME), + ); return (currentEvent: EventData = {}, otherEvents: EventsData = []) => { const otherEventsFiltered = otherEvents @@ -37,8 +35,8 @@ export const getStructureEvents = (compareDates: CompareDates) => { [eventStatuses.COMPLETED, eventStatuses.ACTIVE, eventStatuses.VISITED].includes(event.status) && event.eventId !== currentEvent.eventId, ); - - const events = Object.keys(currentEvent).length !== 0 ? otherEventsFiltered.concat(currentEvent) : otherEventsFiltered; + const events = Object.keys(currentEvent).length ? + otherEventsFiltered.concat(currentEvent) : otherEventsFiltered; const sortedEvents = events.sort(compareEvents); return createEventsContainer(sortedEvents); diff --git a/packages/rules-engine/src/services/VariableService/variableService.types.js b/packages/rules-engine/src/services/VariableService/variableService.types.js index f31caadc2f..9640354c46 100644 --- a/packages/rules-engine/src/services/VariableService/variableService.types.js +++ b/packages/rules-engine/src/services/VariableService/variableService.types.js @@ -1,4 +1,5 @@ // @flow +import { typeof typeKeys } from '../../constants'; import { typeof eventStatuses } from './constants'; import type { DataElements, TrackedEntityAttributes, OrgUnit } from '../../rulesEngine.types'; @@ -89,3 +90,5 @@ export type VariableServiceInput = {| |}; export type CompareDates = (firstRulesDate: ?string, secondRulesDate: ?string) => number; + +export type ProcessValue = (value: any, type: $Values) => any; diff --git a/src/core_modules/capture-core/components/CardList/CardListItem.component.js b/src/core_modules/capture-core/components/CardList/CardListItem.component.js index d411d1a933..17b7513ce8 100644 --- a/src/core_modules/capture-core/components/CardList/CardListItem.component.js +++ b/src/core_modules/capture-core/components/CardList/CardListItem.component.js @@ -15,7 +15,7 @@ import { searchScopes } from '../SearchBox'; import { enrollmentTypes } from './CardList.constants'; import { ListEntry } from './ListEntry.component'; import { dataElementTypes, getTrackerProgramThrowIfNotFound } from '../../metaData'; -import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; +import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName'; import type { ListItem, RenderCustomCardActions } from './CardList.types'; type OwnProps = $ReadOnly<{| @@ -144,7 +144,7 @@ const CardListItemIndex = ({ const enrollments = item.tei ? item.tei.enrollments : []; const enrollmentType = deriveEnrollmentType(enrollments, currentProgramId); const { orgUnitId, enrolledAt } = deriveEnrollmentOrgUnitIdAndDate(enrollments, enrollmentType, currentProgramId); - const { displayName: orgUnitName } = useOrgUnitName(orgUnitId); + const { displayName: orgUnitName } = useOrgUnitNameWithAncestors(orgUnitId); const program = enrollments && enrollments.length ? deriveProgramFromEnrollment(enrollments, currentSearchScopeType) : undefined; diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js index 457fe3a112..14d9395f29 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js @@ -8,7 +8,7 @@ import { useScopeInfo } from '../../../hooks/useScopeInfo'; import { scopeTypes } from '../../../metaData'; import { TrackedEntityInstanceDataEntry } from '../TrackedEntityInstance'; import { useCurrentOrgUnitId } from '../../../hooks/useCurrentOrgUnitId'; -import { useOrgUnitName } from '../../../metadataRetrieval/orgUnitName'; +import { useOrgUnitNameWithAncestors } from '../../../metadataRetrieval/orgUnitName'; import type { Props, PlainProps } from './TeiRegistrationEntry.types'; import { DiscardDialog } from '../../Dialogs/DiscardDialog.component'; import { withSaveHandler } from '../../DataEntry'; @@ -54,7 +54,7 @@ const TeiRegistrationEntryPlain = const { scopeType } = useScopeInfo(selectedScopeId); const { formId, formFoundation } = useMetadataForRegistrationForm({ selectedScopeId }); const orgUnitId = useCurrentOrgUnitId(); - const { displayName: orgUnitName } = useOrgUnitName(orgUnitId); + const { displayName: orgUnitName } = useOrgUnitNameWithAncestors(orgUnitId); const handleOnCancel = () => { if (!isUserInteractionInProgress) { 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 index 218d77fa41..922a2071fc 100644 --- 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 @@ -34,6 +34,7 @@ export type ColumnConfig = DefaultWidgetColumnConfig | PluginWidgetColumnConfig; export type PageLayoutConfig = { title?: ?string, + backgroundColor?: ?string, leftColumn: ?Array, rightColumn: ?Array, } 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 index d3f18557ff..f6d821e872 100644 --- 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 @@ -10,6 +10,7 @@ import { DefaultPageTitle, EnrollmentPageKeys } from './DefaultEnrollmentLayout. const getEnrollmentPageStyles = () => ({ container: { + minHeight: '90vh', padding: '16px 24px 16px 24px', }, contentContainer: { @@ -44,6 +45,9 @@ const getEnrollmentPageStyles = () => ({ }, }); +// Function to validate hex color +const isValidHex = (color: string) => /^#[0-9A-F]{6}$/i.test(color); + const getTitle = (inputTitle, page) => { const title = inputTitle || i18n.t('Enrollment'); const titles = { @@ -83,8 +87,13 @@ const EnrollmentPageLayoutPlain = ({ props: allProps, }); + const containerStyle = useMemo(() => { + if (!pageLayout.backgroundColor || !isValidHex(pageLayout.backgroundColor)) return undefined; + return { backgroundColor: pageLayout.backgroundColor }; + }, [pageLayout.backgroundColor]); + return ( -
+
= ({ }) => { const dispatch = useDispatch(); const [selectedOrgUnit, setSelectedOrgUnit] = useState({ name: undefined, id: selectedOrgUnitId }); - const { displayName, error: ouNameError } = useOrgUnitName(selectedOrgUnit.id); + const { displayName, error: ouNameError } = useOrgUnitNameWithAncestors(selectedOrgUnit.id); useEffect(() => { if (displayName && selectedOrgUnit.name !== displayName) { diff --git a/src/core_modules/capture-core/components/Tooltips/TooltipOrgUnit/TooltipOrgUnit.component.js b/src/core_modules/capture-core/components/Tooltips/TooltipOrgUnit/TooltipOrgUnit.component.js new file mode 100644 index 0000000000..8b756f7e9c --- /dev/null +++ b/src/core_modules/capture-core/components/Tooltips/TooltipOrgUnit/TooltipOrgUnit.component.js @@ -0,0 +1,18 @@ +// @flow +import React from 'react'; +import { Tooltip } from '@dhis2/ui'; + +type Props = { + orgUnitName: string, + ancestors?: Array, +}; + +export const TooltipOrgUnit = ({ orgUnitName, ancestors = [] }: Props) => { + const fullPath = [...ancestors, orgUnitName].join(' / '); + + return ( + + {orgUnitName} + + ); +}; diff --git a/src/core_modules/capture-core/components/Tooltips/TooltipOrgUnit/index.js b/src/core_modules/capture-core/components/Tooltips/TooltipOrgUnit/index.js new file mode 100644 index 0000000000..046747ddff --- /dev/null +++ b/src/core_modules/capture-core/components/Tooltips/TooltipOrgUnit/index.js @@ -0,0 +1 @@ +export { TooltipOrgUnit } from './TooltipOrgUnit.component'; diff --git a/src/core_modules/capture-core/components/Widget/WidgetCollapsible.component.js b/src/core_modules/capture-core/components/Widget/WidgetCollapsible.component.js index 6d794477aa..c3a2b39e20 100644 --- a/src/core_modules/capture-core/components/Widget/WidgetCollapsible.component.js +++ b/src/core_modules/capture-core/components/Widget/WidgetCollapsible.component.js @@ -1,8 +1,8 @@ // @flow -import React, { useState, useEffect, useRef, type ComponentType } from 'react'; +import React, { type ComponentType, useEffect, useRef, useState } from 'react'; import { withStyles } from '@material-ui/core'; import cx from 'classnames'; -import { colors, spacersNum, IconChevronUp24 } from '@dhis2/ui'; +import { colors, IconChevronUp24, spacersNum } from '@dhis2/ui'; import { IconButton } from 'capture-ui'; import type { WidgetCollapsibleProps, WidgetCollapsiblePropsPlain } from './widgetCollapsible.types'; @@ -127,7 +127,7 @@ const WidgetCollapsiblePlain = ({ }, [open, animationsReady]); return ( -
+
{ - const { displayName: ownerOrgUnitName } = useOrgUnitName(ownerOrgUnitId); - const { displayName: newOrgUnitName } = useOrgUnitName(validOrgUnitId); + const { displayName: ownerOrgUnitName } = useOrgUnitNameWithAncestors(ownerOrgUnitId); + const { displayName: newOrgUnitName } = useOrgUnitNameWithAncestors(validOrgUnitId); const showWarning = [ProgramAccessLevels.PROTECTED, ProgramAccessLevels.CLOSED].includes(programAccessLevel) && orgUnitScopes.destination === OrgUnitScopes.SEARCH; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js index 1bbeed7d79..3bec583e85 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js @@ -17,7 +17,8 @@ import { Widget } from '../Widget'; import type { PlainProps } from './enrollment.types'; import { Status } from './Status'; import { dataElementTypes } from '../../metaData'; -import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; +import { convertValue } from '../../converters/clientToView'; +import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName'; import { Date } from './Date'; import { Actions } from './Actions'; import { MiniMap } from './MiniMap'; @@ -69,11 +70,16 @@ export const WidgetEnrollmentPlain = ({ onUpdateEnrollmentStatusError, onUpdateEnrollmentStatusSuccess, onAccessLostFromTransfer, + type = dataElementTypes.ORGANISATION_UNIT, }: PlainProps) => { const [open, setOpenStatus] = useState(true); const { fromServerDate } = useTimeZoneConversion(); const geometryType = getGeometryType(enrollment?.geometry?.type); - const { displayName: orgUnitName } = useOrgUnitName(enrollment?.orgUnit); + const { displayName: orgUnitName, ancestors } = useOrgUnitNameWithAncestors(enrollment?.orgUnit); + const { displayName: ownerOrgUnitName, ancestors: ownerAncestors } = useOrgUnitNameWithAncestors(ownerOrgUnit?.id); + + const orgUnitClientValue = { name: orgUnitName, ancestors }; + const ownerOrgUnitClientValue = { name: ownerOrgUnitName, ancestors: ownerAncestors }; return (
@@ -130,19 +136,16 @@ export const WidgetEnrollmentPlain = ({ - {i18n.t('Started at {{orgUnitName}}', { - orgUnitName, - interpolation: { escapeValue: false }, - })} + {i18n.t('Started at: ')} + {convertValue(orgUnitClientValue, type)}
- {i18n.t('Owned by {{ownerOrgUnit}}', { - ownerOrgUnit: ownerOrgUnit.displayName, - })} + {i18n.t('Owned by: ')} + {convertValue(ownerOrgUnitClientValue, type)}
diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js index f46827f52b..dba0dc0954 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js @@ -3,7 +3,7 @@ import React, { useMemo } from 'react'; import { errorCreator } from 'capture-core-utils'; import log from 'loglevel'; import { WidgetEnrollment as WidgetEnrollmentNote } from './WidgetEnrollment.component'; -import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; +import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName'; import { useTrackedEntityInstances } from './hooks/useTrackedEntityInstances'; import { useEnrollment } from './hooks/useEnrollment'; import { useProgram } from './hooks/useProgram'; @@ -68,7 +68,7 @@ export const WidgetEnrollment = ({ enrollments, refetch: refetchTEI, } = useTrackedEntityInstances(teiId, programId); - const { error: errorOrgUnit, displayName } = useOrgUnitName( + const { error: errorOrgUnit, displayName } = useOrgUnitNameWithAncestors( typeof ownerOrgUnit === 'string' ? ownerOrgUnit : undefined, ); const { error: errorLocale, locale } = useUserLocale(); diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js index 24bfdc0016..843e40d96a 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js @@ -4,7 +4,7 @@ import i18n from '@dhis2/d2-i18n'; import { useDispatch } from 'react-redux'; import moment from 'moment'; import { getProgramAndStageForProgram, TrackerProgram, getProgramEventAccess, dataElementTypes } from '../../metaData'; -import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; +import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName'; import { useLocationQuery } from '../../utils/routing'; import type { ContainerProps } from './widgetEventSchedule.types'; import { WidgetEventScheduleComponent } from './WidgetEventSchedule.component'; @@ -37,7 +37,7 @@ export const WidgetEventSchedule = ({ }: ContainerProps) => { const { program, stage } = useMemo(() => getProgramAndStageForProgram(programId, stageId), [programId, stageId]); const dispatch = useDispatch(); - const orgUnit = { id: orgUnitId, name: useOrgUnitName(orgUnitId).displayName }; + const orgUnit = { id: orgUnitId, name: useOrgUnitNameWithAncestors(orgUnitId).displayName }; const { programStageScheduleConfig } = useScheduleConfigFromProgramStage(stageId); const { programConfig } = useScheduleConfigFromProgram(programId); const suggestedScheduleDate = useDetermineSuggestedScheduleDate({ 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 f14f5fb3ec..0204ac19a7 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js @@ -97,10 +97,10 @@ const WidgetProfilePlain = ({ const displayInListAttributes = useMemo(() => clientAttributesWithSubvalues .filter(item => item.displayInList) .map((clientAttribute) => { - const { attribute, key } = clientAttribute; + const { attribute, key, valueType } = clientAttribute; const value = convertClientToView(clientAttribute); return { - attribute, key, value, reactKey: attribute, + attribute, key, value, valueType, reactKey: attribute, }; }), [clientAttributesWithSubvalues]); diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/getSubValueForTei.js b/src/core_modules/capture-core/components/WidgetProfile/hooks/getSubValueForTei.js index 241316d4eb..89a9e819cf 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/getSubValueForTei.js +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/getSubValueForTei.js @@ -51,10 +51,17 @@ const getOrganisationUnitSubvalue = async ({ attribute, querySingleResource }: S resource: 'organisationUnits', id: attribute.value, params: { - fields: 'id,name', + fields: 'id,name,ancestors[displayName]', }, }); - return { ...organisationUnit }; + + const orgUnitClientValue = { + id: organisationUnit.id, + name: organisationUnit.name, + ancestors: organisationUnit.ancestors.map(ancestor => ancestor.displayName), + }; + + return orgUnitClientValue; }; export const subValueGetterByElementType = { diff --git a/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/FlatListOrgUnitField/FlatListOrgUnitField.js b/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/FlatListOrgUnitField/FlatListOrgUnitField.js index c8a1652a76..aaf3ffdf3c 100644 --- a/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/FlatListOrgUnitField/FlatListOrgUnitField.js +++ b/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/FlatListOrgUnitField/FlatListOrgUnitField.js @@ -1,6 +1,6 @@ // @flow import React from 'react'; -import { useOrgUnitName } from '../../../metadataRetrieval/orgUnitName'; +import { useOrgUnitNameWithAncestors } from '../../../metadataRetrieval/orgUnitName'; type Props = { orgUnitId: string, @@ -9,7 +9,7 @@ type Props = { export const FlatListOrgUnitField = ({ orgUnitId, }: Props) => { - const { displayName } = useOrgUnitName(orgUnitId); + const { displayName } = useOrgUnitNameWithAncestors(orgUnitId); return ( diff --git a/src/core_modules/capture-core/converters/clientToView.js b/src/core_modules/capture-core/converters/clientToView.js index a1301e6836..115bcbeb69 100644 --- a/src/core_modules/capture-core/converters/clientToView.js +++ b/src/core_modules/capture-core/converters/clientToView.js @@ -7,36 +7,39 @@ import { dataElementTypes, type DataElement } from '../metaData'; import { convertMomentToDateFormatString } from '../utils/converters/date'; import { stringifyNumber } from './common/stringifyNumber'; import { MinimalCoordinates } from '../components/MinimalCoordinates'; +import { TooltipOrgUnit } from '../components/Tooltips/TooltipOrgUnit'; function convertDateForView(rawValue: string): string { const momentDate = moment(rawValue); return convertMomentToDateFormatString(momentDate); } - function convertDateTimeForView(rawValue: string): string { const momentDate = moment(rawValue); const dateString = convertMomentToDateFormatString(momentDate); const timeString = momentDate.format('HH:mm'); return `${dateString} ${timeString}`; } - function convertTimeForView(rawValue: string): string { const momentDate = moment(rawValue, 'HH:mm', true); return momentDate.format('HH:mm'); } - type FileClientValue = { name: string, url: string, value: string, }; - type ImageClientValue = { ...FileClientValue, previewUrl: string, }; +type OrgUnitClientValue = { + name: string, + ancestors?: Array, + tooltip?: string, +}; + function convertFileForDisplay(clientValue: FileClientValue) { return ( ; } +function convertOrgUnitForDisplay(clientValue: OrgUnitClientValue) { + return ( + + ); +} + const valueConvertersForType = { [dataElementTypes.NUMBER]: stringifyNumber, [dataElementTypes.INTEGER]: stringifyNumber, @@ -70,7 +83,7 @@ const valueConvertersForType = { [dataElementTypes.AGE]: convertDateForView, [dataElementTypes.FILE_RESOURCE]: convertFileForDisplay, [dataElementTypes.IMAGE]: convertImageForDisplay, - [dataElementTypes.ORGANISATION_UNIT]: (rawValue: Object) => rawValue.name, + [dataElementTypes.ORGANISATION_UNIT]: convertOrgUnitForDisplay, [dataElementTypes.POLYGON]: () => 'Polygon', }; @@ -78,18 +91,15 @@ export function convertValue(value: any, type: $Keys, d if (!value && value !== 0 && value !== false) { return value; } - if (dataElement && dataElement.optionSet) { if (dataElement.type === dataElementTypes.MULTI_TEXT) { return dataElement.optionSet.getMultiOptionsText(value); } return dataElement.optionSet.getOptionText(value); } - // $FlowFixMe dataElementTypes flow error return valueConvertersForType[type] ? valueConvertersForType[type](value) : value; } - export function convertDateWithTimeForView(rawValue?: ?string): string { if (!rawValue) { return ''; } if (!moment(rawValue).hours() && !moment(rawValue).minutes()) { diff --git a/src/core_modules/capture-core/metadataRetrieval/orgUnitName/index.js b/src/core_modules/capture-core/metadataRetrieval/orgUnitName/index.js index f254f83a12..21e8b84212 100644 --- a/src/core_modules/capture-core/metadataRetrieval/orgUnitName/index.js +++ b/src/core_modules/capture-core/metadataRetrieval/orgUnitName/index.js @@ -1,6 +1,6 @@ // @flow export { - useOrgUnitName, + useOrgUnitNameWithAncestors, useOrgUnitNames, getOrgUnitNames, getCachedOrgUnitName, diff --git a/src/core_modules/capture-core/metadataRetrieval/orgUnitName/orgUnitName.js b/src/core_modules/capture-core/metadataRetrieval/orgUnitName/orgUnitName.js index ca45d9de40..56d222c119 100644 --- a/src/core_modules/capture-core/metadataRetrieval/orgUnitName/orgUnitName.js +++ b/src/core_modules/capture-core/metadataRetrieval/orgUnitName/orgUnitName.js @@ -14,13 +14,43 @@ const displayNamesQuery = { organisationUnits: { resource: 'organisationUnits', params: ({ filter }) => ({ - fields: 'id,displayName', + fields: 'id,displayName,ancestors[id,displayName]', filter: `id:in:[${filter}]`, pageSize: maxBatchSize, }), }, }; +const updateCacheWithOrgUnits = (organisationUnits) => { + organisationUnits.forEach(({ id, displayName, ancestors }) => { + if (ancestors.length > 0) { + displayNameCache[id] = { + displayName, + ancestor: ancestors[ancestors.length - 1].id, + }; + + ancestors.findLast((ancestor, index) => { + if (displayNameCache[ancestor.id]) { + // Ancestors already cached + return true; + } else if (index > 0) { + // Add orgunit WITH ancestor to cache + displayNameCache[ancestor.id] = { + displayName: ancestor.displayName, + ancestor: ancestors[index - 1].id, + }; + return false; + } + // Add orgunit WITHOUT ancestor to cache + displayNameCache[ancestor.id] = { displayName: ancestor.displayName }; + return true; + }); + } else { + displayNameCache[id] = { displayName }; + } + }); +}; + const createBatches = (orgUnitIds: Array): Array> => { const reducedOrgUnitIds = Array.from(orgUnitIds .filter(id => id) @@ -39,6 +69,17 @@ const createBatches = (orgUnitIds: Array): Array> => { return batches; }; +const getAncestors = (orgUnitId) => { + const orgUnit = displayNameCache[orgUnitId]; + + if (!orgUnit) return []; + + const ancestors = getAncestors(orgUnit.ancestor); + ancestors.push(orgUnit.displayName); + + return ancestors; +}; + // Works best with memoized input arrays. export const useOrgUnitNames = (orgUnitIds: Array): { loading: boolean, @@ -54,28 +95,19 @@ export const useOrgUnitNames = (orgUnitIds: Array): { const ready = !fetching && orgUnitIds === requestedArray; - const batches = useMemo( - () => createBatches(orgUnitIds), - [orgUnitIds], - ); - const filter = useMemo( - () => (fetching ? currentBatches[completedBatches].join(',') : ''), - [fetching, currentBatches, completedBatches], - ); - const result = useMemo( - () => (ready ? orgUnitIds.reduce((acc, id) => { - acc[id] = displayNameCache[id]; - return acc; - }, {}) : null), - [ready, orgUnitIds], - ); + const batches = useMemo(() => createBatches(orgUnitIds), [orgUnitIds]); + const filter = useMemo(() => (fetching ? currentBatches[completedBatches].join(',') : ''), [fetching, currentBatches, completedBatches]); + const result = useMemo(() => (ready ? orgUnitIds.reduce((acc, id) => { + acc[id] = displayNameCache[id] ? displayNameCache[id].displayName : null; + return acc; + }, {}) : null), [ready, orgUnitIds]); const onComplete = useCallback(({ organisationUnits }) => { - for (const { id, displayName } of organisationUnits.organisationUnits) { - displayNameCache[id] = displayName; - } + updateCacheWithOrgUnits(organisationUnits.organisationUnits); + const completeCount = completedBatches + 1; setCompletedBatches(completeCount); + if (completeCount === currentBatches.length) { setFetching(false); } else { @@ -83,22 +115,17 @@ export const useOrgUnitNames = (orgUnitIds: Array): { } }, [completedBatches, setCompletedBatches, currentBatches, setFetching, setFetchNextBatch]); - const onError = useCallback( - (fetchError) => { - setFetching(false); - setError(fetchError); - }, - [setFetching, setError], - ); + const onError = useCallback((fetchError) => { + setFetching(false); + setError(fetchError); + }, [setFetching, setError]); - const { refetch } = useDataQuery( - displayNamesQuery, { - variables: { filter }, - onComplete, - onError, - lazy: true, - }, - ); + const { refetch } = useDataQuery(displayNamesQuery, { + variables: { filter }, + onComplete, + onError, + lazy: true, + }); useEffect(() => { if (!fetching && orgUnitIds !== requestedArray) { @@ -111,7 +138,7 @@ export const useOrgUnitNames = (orgUnitIds: Array): { setCompletedBatches(0); } } - }, [fetching, orgUnitIds, requestedArray, batches, setRequestedArray, setCurrentBatches, setCompletedBatches, setFetching, setError]); + }, [fetching, orgUnitIds, requestedArray, batches]); useEffect(() => { if (fetchNextBatch) { @@ -120,7 +147,7 @@ export const useOrgUnitNames = (orgUnitIds: Array): { refetch({ filter }); } } - }, [fetchNextBatch, setFetchNextBatch, completedBatches, currentBatches, refetch, filter]); + }, [fetchNextBatch, completedBatches, currentBatches, refetch, filter]); return { loading: !ready && !error, @@ -130,44 +157,57 @@ export const useOrgUnitNames = (orgUnitIds: Array): { }; export async function getOrgUnitNames(orgUnitIds: Array, querySingleResource: QuerySingleResource): Promise<{| - [orgUnitId: string]: {| - id: string, +[orgUnitId: string]: {| + id: string, displayName: string, |} |}> { await Promise.all(createBatches(orgUnitIds) .map(batch => querySingleResource(displayNamesQuery.organisationUnits, { filter: batch.join(',') }) .then(({ organisationUnits }) => { - for (const { id, displayName } of organisationUnits) { - displayNameCache[id] = displayName; - } - }))); + updateCacheWithOrgUnits(organisationUnits); + }), + ), + ); return orgUnitIds.reduce((acc, orgUnitId) => { acc[orgUnitId] = { id: orgUnitId, - name: displayNameCache[orgUnitId], + name: displayNameCache[orgUnitId]?.displayName, }; return acc; }, {}); } -export const useOrgUnitName = (orgUnitId: ?string): { +export const useOrgUnitNameWithAncestors = (orgUnitId: ?string): { displayName?: string, - error?: any, + ancestors?: Array, + error: any, } => { - const cachedOrgUnitName = orgUnitId && displayNameCache[orgUnitId]; - const fetchId = cachedOrgUnitName ? undefined : orgUnitId; - const { orgUnit, error } = useOrganisationUnit(fetchId, 'displayName'); - if (cachedOrgUnitName) { - return { displayName: cachedOrgUnitName }; - } else if (orgUnit && fetchId) { - displayNameCache[orgUnit.id] = orgUnit.displayName; - if (orgUnit.id === fetchId) { - return { displayName: orgUnit.displayName, error }; - } + const cachedOrgUnit = orgUnitId && displayNameCache[orgUnitId]; + const fetchId = cachedOrgUnit ? undefined : orgUnitId; + const { orgUnit: fetchedOrgUnit, error } = useOrganisationUnit(fetchId, 'displayName,ancestors[id,displayName]'); + + if (orgUnitId && cachedOrgUnit) { + const ancestors = getAncestors(cachedOrgUnit.ancestor); + + return { + displayName: cachedOrgUnit.displayName, + ancestors, + error, + }; + } else if (fetchedOrgUnit && fetchId) { + updateCacheWithOrgUnits([fetchedOrgUnit]); + const ancestors = fetchedOrgUnit.ancestors.map(ancestor => ancestor.displayName); + + return { + displayName: fetchedOrgUnit.displayName, + ancestors, + error, + }; } + return { error }; }; -export const getCachedOrgUnitName = (orgUnitId: string): ?string => displayNameCache[orgUnitId]; +export const getCachedOrgUnitName = (orgUnitId: string): ?string => displayNameCache[orgUnitId]?.displayName; diff --git a/src/core_modules/capture-core/rules/converters/dateUtils.js b/src/core_modules/capture-core/rules/converters/dateUtils.js index 674d82bcfc..9d4398635f 100644 --- a/src/core_modules/capture-core/rules/converters/dateUtils.js +++ b/src/core_modules/capture-core/rules/converters/dateUtils.js @@ -51,16 +51,10 @@ export const dateUtils: IDateUtils = { return momentToRulesDate(newDateMoment); }, compareDates: (firstRulesDate: ?string, secondRulesDate: ?string): number => { - const diff = dateUtils.daysBetween(secondRulesDate, firstRulesDate); - if (!diff) { - return 0; - } - if (diff < 0) { - return -1; - } - if (diff > 0) { - return 1; - } - return 0; + // Empty input dates will be replaced by "MAX_SAFE_INTEGER" when creating the timestamp. + // This ensures empty input will be bigger than any actual date + const firstDateTimestamp = firstRulesDate ? moment(firstRulesDate).valueOf() : Number.MAX_SAFE_INTEGER; + const secondDateTimestamp = secondRulesDate ? moment(secondRulesDate).valueOf() : Number.MAX_SAFE_INTEGER; + return firstDateTimestamp - secondDateTimestamp; }, }; diff --git a/src/core_modules/capture-ui/FlatList/flatList.types.js b/src/core_modules/capture-ui/FlatList/flatList.types.js index 94dc5a5b47..4159c32e1d 100644 --- a/src/core_modules/capture-ui/FlatList/flatList.types.js +++ b/src/core_modules/capture-ui/FlatList/flatList.types.js @@ -1,8 +1,6 @@ // @flow -import { type Node } from 'react'; - export type Props = {| - list: { reactKey: string, key: string, value: Node }[], + list: { reactKey: string, key: string, value: Object, valueType?: string }[], dataTest?: string, ...CssClasses, |};