From e8348f7fe4b9a9d6349eddf9c86b11b5939564a2 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Tue, 22 Nov 2022 13:59:49 +0100 Subject: [PATCH 001/285] fix(OpenFileDialog): use case insensitive sorting on displayName (DHIS2-13554) (#1384) * fix(OpenFileDialog): use case insensitive sort on displayName * fix: remove console deprecation warning --- src/components/FileMenu/FileMenu.js | 2 +- .../OpenFileDialog/OpenFileDialog.js | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/components/FileMenu/FileMenu.js b/src/components/FileMenu/FileMenu.js index 18904ce87..588639a2d 100644 --- a/src/components/FileMenu/FileMenu.js +++ b/src/components/FileMenu/FileMenu.js @@ -164,7 +164,7 @@ export const FileMenu = ({ /> {menuIsOpen && ( ({ resource: AOTypeMap[type].apiEndpoint, params: ({ sortField = 'displayName', - sortDirection = 'asc', + sortDirection = 'iasc', page = 1, filters, }) => { @@ -134,6 +134,14 @@ export const OpenFileDialog = ({ return queryFilters }, [currentUser, filters]) + const formatSortDirection = useCallback(() => { + if (sortField === 'displayName' && sortDirection !== 'default') { + return `i${sortDirection}` + } + + return sortDirection + }, [sortField, sortDirection]) + const { loading, error, data, refetch } = useDataQuery(filesQuery, { lazy: true, }) @@ -161,11 +169,19 @@ export const OpenFileDialog = ({ refetch({ page, sortField, - sortDirection, + sortDirection: formatSortDirection(), filters: formatFilters(), }) } - }, [open, page, sortField, sortDirection, filters, refetch, formatFilters]) + }, [ + open, + page, + sortField, + filters, + refetch, + formatFilters, + formatSortDirection, + ]) const headers = [ { From b77fb4b61f8bec458dda963db651e884438a0b28 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 22 Nov 2022 13:04:59 +0000 Subject: [PATCH 002/285] chore(release): cut 24.3.6 [skip ci] ## [24.3.6](https://github.com/dhis2/analytics/compare/v24.3.5...v24.3.6) (2022-11-22) ### Bug Fixes * **OpenFileDialog:** use case insensitive sorting on displayName (DHIS2-13554) ([#1384](https://github.com/dhis2/analytics/issues/1384)) ([e8348f7](https://github.com/dhis2/analytics/commit/e8348f7fe4b9a9d6349eddf9c86b11b5939564a2)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd3877d79..6631324cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.3.6](https://github.com/dhis2/analytics/compare/v24.3.5...v24.3.6) (2022-11-22) + + +### Bug Fixes + +* **OpenFileDialog:** use case insensitive sorting on displayName (DHIS2-13554) ([#1384](https://github.com/dhis2/analytics/issues/1384)) ([e8348f7](https://github.com/dhis2/analytics/commit/e8348f7fe4b9a9d6349eddf9c86b11b5939564a2)) + ## [24.3.5](https://github.com/dhis2/analytics/compare/v24.3.4...v24.3.5) (2022-11-17) diff --git a/package.json b/package.json index 60957a294..975930e74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.3.5", + "version": "24.3.6", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 4838a1714a33a7fd06d12681b578701d6a735fd3 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 24 Nov 2022 02:46:52 +0100 Subject: [PATCH 003/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/fr.po | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/i18n/fr.po b/i18n/fr.po index 7adeff62c..c7ed5f977 100644 --- a/i18n/fr.po +++ b/i18n/fr.po @@ -7,15 +7,15 @@ # tx_e2f_fr r25 , 2021 # Karoline Tufte Lien , 2022 # Edem Kossi , 2022 -# Yao Selom Saka , 2022 # phil_dhis2, 2022 +# Yao Selom SAKA (HISP WCA) , 2022 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2022\n" +"Last-Translator: Yao Selom SAKA (HISP WCA) , 2022\n" "Language-Team: French (https://www.transifex.com/hisp-uio/teams/100509/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -189,7 +189,7 @@ msgid "Dimension recommended with selected data" msgstr "Dimension recommandée avec les données sélectionnées" msgid "All items" -msgstr "" +msgstr "Tous les items" msgid "Automatically include all items" msgstr "" @@ -272,6 +272,9 @@ msgstr "Description" msgid "Rename" msgstr "Renommer" +msgid "{{objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "Enregistrer {{fileType}} comme" @@ -385,6 +388,9 @@ msgstr "" msgid "No results found" msgstr "Aucun résultat trouvé" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "Créé par" @@ -851,6 +857,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" From e9353d9dff5d1ced03d2791e2f804c1d1445546b Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 24 Nov 2022 01:51:30 +0000 Subject: [PATCH 004/285] chore(release): cut 24.3.7 [skip ci] ## [24.3.7](https://github.com/dhis2/analytics/compare/v24.3.6...v24.3.7) (2022-11-24) ### Bug Fixes * **translations:** sync translations from transifex (master) ([4838a17](https://github.com/dhis2/analytics/commit/4838a1714a33a7fd06d12681b578701d6a735fd3)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6631324cc..46339d4cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.3.7](https://github.com/dhis2/analytics/compare/v24.3.6...v24.3.7) (2022-11-24) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([4838a17](https://github.com/dhis2/analytics/commit/4838a1714a33a7fd06d12681b578701d6a735fd3)) + ## [24.3.6](https://github.com/dhis2/analytics/compare/v24.3.5...v24.3.6) (2022-11-22) diff --git a/package.json b/package.json index 975930e74..92a54d273 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.3.6", + "version": "24.3.7", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From ade41a150e728f9ac80b74d90123a5dfed29b088 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 29 Nov 2022 02:46:25 +0100 Subject: [PATCH 005/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/fr.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/fr.po b/i18n/fr.po index c7ed5f977..a2a6c7b22 100644 --- a/i18n/fr.po +++ b/i18n/fr.po @@ -389,7 +389,7 @@ msgid "No results found" msgstr "Aucun résultat trouvé" msgid "Not available offline" -msgstr "" +msgstr "Non disponible hors ligne" msgid "Created by" msgstr "Créé par" From c70e9d76562a2ea4adb6e54ed462104d42556f1e Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 29 Nov 2022 01:52:09 +0000 Subject: [PATCH 006/285] chore(release): cut 24.3.8 [skip ci] ## [24.3.8](https://github.com/dhis2/analytics/compare/v24.3.7...v24.3.8) (2022-11-29) ### Bug Fixes * **translations:** sync translations from transifex (master) ([ade41a1](https://github.com/dhis2/analytics/commit/ade41a150e728f9ac80b74d90123a5dfed29b088)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46339d4cf..226ea133a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.3.8](https://github.com/dhis2/analytics/compare/v24.3.7...v24.3.8) (2022-11-29) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([ade41a1](https://github.com/dhis2/analytics/commit/ade41a150e728f9ac80b74d90123a5dfed29b088)) + ## [24.3.7](https://github.com/dhis2/analytics/compare/v24.3.6...v24.3.7) (2022-11-24) diff --git a/package.json b/package.json index 92a54d273..ea0b4b30d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.3.7", + "version": "24.3.8", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From a6d4648267a7ca69a7b4702c0fa07cf9f7ff8c2a Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 1 Dec 2022 02:48:12 +0100 Subject: [PATCH 007/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/fr.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/fr.po b/i18n/fr.po index a2a6c7b22..b1ec48ba8 100644 --- a/i18n/fr.po +++ b/i18n/fr.po @@ -87,7 +87,7 @@ msgid "This app could not retrieve required data." msgstr "" msgid "Network error" -msgstr "" +msgstr "Erreur réseau" msgid "Data Type" msgstr "Type de données" @@ -546,10 +546,10 @@ msgstr[1] "" msgstr[2] "" msgid "Selected: {{commaSeparatedListOfOrganisationUnits}}" -msgstr "" +msgstr "Sélectionné : {{commaSeparatedListOfOrganisationUnits}}" msgid "Nothing selected" -msgstr "" +msgstr "Rien n'a été sélectionné" msgid "User organisation unit" msgstr "Unités d'organisation de l'utilisateur" From 3ec92bd3ff80c3e8220c3c7eb0816f6757ddc5bb Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 1 Dec 2022 01:52:43 +0000 Subject: [PATCH 008/285] chore(release): cut 24.3.9 [skip ci] ## [24.3.9](https://github.com/dhis2/analytics/compare/v24.3.8...v24.3.9) (2022-12-01) ### Bug Fixes * **translations:** sync translations from transifex (master) ([a6d4648](https://github.com/dhis2/analytics/commit/a6d4648267a7ca69a7b4702c0fa07cf9f7ff8c2a)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 226ea133a..ea06e3871 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.3.9](https://github.com/dhis2/analytics/compare/v24.3.8...v24.3.9) (2022-12-01) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([a6d4648](https://github.com/dhis2/analytics/commit/a6d4648267a7ca69a7b4702c0fa07cf9f7ff8c2a)) + ## [24.3.8](https://github.com/dhis2/analytics/compare/v24.3.7...v24.3.8) (2022-11-29) diff --git a/package.json b/package.json index ea0b4b30d..8f2ad8680 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.3.8", + "version": "24.3.9", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 4b7cff4a182ae3f44fada600337ba477a2656ed5 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 2 Dec 2022 15:17:49 +0100 Subject: [PATCH 009/285] fix: items in sidebar not clickable full width (DHIS2-6626) (#1390) --- .../DimensionsPanel/List/DimensionItem.js | 33 +++++-- .../DimensionsPanel/List/DimensionLabel.js | 49 ---------- .../__snapshots__/DimensionItem.spec.js.snap | 90 ++++++++++++------- .../List/styles/DimensionItem.style.js | 8 +- .../List/styles/DimensionLabel.style.js | 6 -- 5 files changed, 92 insertions(+), 94 deletions(-) delete mode 100644 src/components/DimensionsPanel/List/DimensionLabel.js delete mode 100644 src/components/DimensionsPanel/List/styles/DimensionLabel.style.js diff --git a/src/components/DimensionsPanel/List/DimensionItem.js b/src/components/DimensionsPanel/List/DimensionItem.js index 5b3f58ec3..59fe47f96 100644 --- a/src/components/DimensionsPanel/List/DimensionItem.js +++ b/src/components/DimensionsPanel/List/DimensionItem.js @@ -2,8 +2,10 @@ import { IconLock16 } from '@dhis2/ui' import PropTypes from 'prop-types' import React, { Component, createRef } from 'react' import DynamicDimensionIcon from '../../../assets/DynamicDimensionIcon.js' -import { getPredefinedDimensionProp } from '../../../modules/predefinedDimensions.js' -import DimensionLabel from './DimensionLabel.js' +import { + DIMENSION_PROP_NO_ITEMS, + getPredefinedDimensionProp, +} from '../../../modules/predefinedDimensions.js' import OptionsButton from './OptionsButton.js' import RecommendedIcon from './RecommendedIcon.js' import { styles } from './styles/DimensionItem.style.js' @@ -71,19 +73,34 @@ class DimensionItem extends Component { const optionsRef = createRef() + const onLabelClick = () => { + if ( + !isDeactivated && + !getPredefinedDimensionProp(id, DIMENSION_PROP_NO_ITEMS) + ) { + onClick(id) + } + } + return (
  • -
    {Icon}
    @@ -99,7 +116,7 @@ class DimensionItem extends Component { )} -
    + {onOptionsClick ? (
    { - if ( - !this.props.isDeactivated && - !getPredefinedDimensionProp(this.props.id, DIMENSION_PROP_NO_ITEMS) - ) { - this.props.onClick(this.props.id) - } - } - - onKeyPress = (event) => { - if (event.key === 'Enter' && event.ctrlKey === false) { - this.onLabelClick() - } - } - - render() { - return ( -
    - {this.props.children} -
    - ) - } -} - -export default DimensionLabel diff --git a/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap b/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap index 1bc57e958..373c91cd0 100644 --- a/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap +++ b/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap @@ -3,12 +3,14 @@ exports[`DimensionItem matches the snapshot 1`] = `
  • -
    -
    +
  • `; exports[`DimensionItem matches the snapshot with locked 1`] = `
  • -
    -
    +
  • `; exports[`DimensionItem matches the snapshot with onOptionsClick 1`] = `
  • -
    -
    +
    -
    -
    +
  • `; exports[`DimensionItem matches the snapshot with selected 1`] = `
  • -
    -
    +
  • `; diff --git a/src/components/DimensionsPanel/List/styles/DimensionItem.style.js b/src/components/DimensionsPanel/List/styles/DimensionItem.style.js index 03f4d21a6..c0a7ebd33 100644 --- a/src/components/DimensionsPanel/List/styles/DimensionItem.style.js +++ b/src/components/DimensionsPanel/List/styles/DimensionItem.style.js @@ -7,7 +7,6 @@ export const styles = { text: { color: colors.grey900, userSelect: 'none', - cursor: 'pointer', wordBreak: 'break-word', fontSize: '14px', }, @@ -23,6 +22,9 @@ export const styles = { alignItems: 'center', borderRadius: '2px', }, + clickable: { + cursor: 'pointer', + }, selected: { backgroundColor: theme.secondary100, fontWeight: 500, @@ -58,4 +60,8 @@ export const styles = { outline: 'none', cursor: 'pointer', }, + label: { + display: 'flex', + outline: 'none', + }, } diff --git a/src/components/DimensionsPanel/List/styles/DimensionLabel.style.js b/src/components/DimensionsPanel/List/styles/DimensionLabel.style.js deleted file mode 100644 index 18d3d56f5..000000000 --- a/src/components/DimensionsPanel/List/styles/DimensionLabel.style.js +++ /dev/null @@ -1,6 +0,0 @@ -export const styles = { - label: { - display: 'flex', - outline: 'none', - }, -} From b2300228fcd6cc98b7113113af62489505d73f26 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 2 Dec 2022 14:22:39 +0000 Subject: [PATCH 010/285] chore(release): cut 24.3.10 [skip ci] ## [24.3.10](https://github.com/dhis2/analytics/compare/v24.3.9...v24.3.10) (2022-12-02) ### Bug Fixes * items in sidebar not clickable full width (DHIS2-6626) ([#1390](https://github.com/dhis2/analytics/issues/1390)) ([4b7cff4](https://github.com/dhis2/analytics/commit/4b7cff4a182ae3f44fada600337ba477a2656ed5)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea06e3871..9ed52c7e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.3.10](https://github.com/dhis2/analytics/compare/v24.3.9...v24.3.10) (2022-12-02) + + +### Bug Fixes + +* items in sidebar not clickable full width (DHIS2-6626) ([#1390](https://github.com/dhis2/analytics/issues/1390)) ([4b7cff4](https://github.com/dhis2/analytics/commit/4b7cff4a182ae3f44fada600337ba477a2656ed5)) + ## [24.3.9](https://github.com/dhis2/analytics/compare/v24.3.8...v24.3.9) (2022-12-01) diff --git a/package.json b/package.json index 8f2ad8680..213a7030a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.3.9", + "version": "24.3.10", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 03c6a2ee98b437fed2a44233e4a750d17bd3c5af Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Wed, 21 Dec 2022 11:47:17 +0100 Subject: [PATCH 011/285] fix: do not pass translation object when value is empty (#1401) This causes a 409 response from the translations API. If the input field is cleared, assuming the user wants to remove a translation for a language/string entirely, do not pass the object with an empty value, but remove it entirely from the list of translations. We still need to pass the full list of translations because the API does not support PATCH requests so the logic is a bit more complex. --- .../TranslationModal/TranslationForm.js | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/components/TranslationDialog/TranslationModal/TranslationForm.js b/src/components/TranslationDialog/TranslationModal/TranslationForm.js index cb5504a93..1152acb33 100644 --- a/src/components/TranslationDialog/TranslationModal/TranslationForm.js +++ b/src/components/TranslationDialog/TranslationModal/TranslationForm.js @@ -64,13 +64,33 @@ export const TranslationForm = ({ const translationIndex = getTranslationIndexForField(field) - setNewTranslations( - translationIndex === -1 - ? [...newTranslations, newTranslation] - : newTranslations.map((translation, index) => - index === translationIndex ? newTranslation : translation - ) - ) + if (translationIndex === -1) { + // non existing translation, adding new + setNewTranslations([...newTranslations, newTranslation]) + } else { + // cleared existing translation, remove it from the list + if (!translation) { + setNewTranslations( + newTranslations.reduce((tmp, translation, index) => { + if (index !== translationIndex) { + tmp.push(translation) + } + + return tmp + }, []) + ) + } + // replace existing translation with new one + else { + setNewTranslations( + newTranslations.map((translation, index) => + index === translationIndex + ? newTranslation + : translation + ) + ) + } + } } const i18nMutationRef = useRef({ From db71604707d24ec4db4dc40665403f4c98183e1d Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 21 Dec 2022 10:52:00 +0000 Subject: [PATCH 012/285] chore(release): cut 24.3.11 [skip ci] ## [24.3.11](https://github.com/dhis2/analytics/compare/v24.3.10...v24.3.11) (2022-12-21) ### Bug Fixes * do not pass translation object when value is empty ([#1401](https://github.com/dhis2/analytics/issues/1401)) ([03c6a2e](https://github.com/dhis2/analytics/commit/03c6a2ee98b437fed2a44233e4a750d17bd3c5af)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ed52c7e7..313f55b23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.3.11](https://github.com/dhis2/analytics/compare/v24.3.10...v24.3.11) (2022-12-21) + + +### Bug Fixes + +* do not pass translation object when value is empty ([#1401](https://github.com/dhis2/analytics/issues/1401)) ([03c6a2e](https://github.com/dhis2/analytics/commit/03c6a2ee98b437fed2a44233e4a750d17bd3c5af)) + ## [24.3.10](https://github.com/dhis2/analytics/compare/v24.3.9...v24.3.10) (2022-12-02) diff --git a/package.json b/package.json index 213a7030a..f19ca54bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.3.10", + "version": "24.3.11", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 55a94cabbb11e5ed91f59c55ebee3053f83e7fa7 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 22 Dec 2022 09:55:42 +0100 Subject: [PATCH 013/285] fix: typo in dataTest (#1404) --- src/components/DimensionsPanel/List/DimensionItem.js | 2 +- .../__tests__/__snapshots__/DimensionItem.spec.js.snap | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/DimensionsPanel/List/DimensionItem.js b/src/components/DimensionsPanel/List/DimensionItem.js index 59fe47f96..7b3bd648b 100644 --- a/src/components/DimensionsPanel/List/DimensionItem.js +++ b/src/components/DimensionsPanel/List/DimensionItem.js @@ -101,7 +101,7 @@ class DimensionItem extends Component { className="label" tabIndex={0} style={styles.label} - dataTest={`${dataTest}-button-${id}`} + data-test={`${dataTest}-button-${id}`} >
    {Icon}
    diff --git a/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap b/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap index 373c91cd0..1c33a04bf 100644 --- a/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap +++ b/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap @@ -20,7 +20,7 @@ exports[`DimensionItem matches the snapshot 1`] = ` >
    Date: Thu, 22 Dec 2022 09:00:06 +0000 Subject: [PATCH 014/285] chore(release): cut 24.3.12 [skip ci] ## [24.3.12](https://github.com/dhis2/analytics/compare/v24.3.11...v24.3.12) (2022-12-22) ### Bug Fixes * typo in dataTest ([#1404](https://github.com/dhis2/analytics/issues/1404)) ([55a94ca](https://github.com/dhis2/analytics/commit/55a94cabbb11e5ed91f59c55ebee3053f83e7fa7)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 313f55b23..bd5239b66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.3.12](https://github.com/dhis2/analytics/compare/v24.3.11...v24.3.12) (2022-12-22) + + +### Bug Fixes + +* typo in dataTest ([#1404](https://github.com/dhis2/analytics/issues/1404)) ([55a94ca](https://github.com/dhis2/analytics/commit/55a94cabbb11e5ed91f59c55ebee3053f83e7fa7)) + ## [24.3.11](https://github.com/dhis2/analytics/compare/v24.3.10...v24.3.11) (2022-12-21) diff --git a/package.json b/package.json index f19ca54bf..4e481f7a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.3.11", + "version": "24.3.12", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 639dc45001d0d8b9a0784ffefc96bdbc45271ae3 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 29 Dec 2022 11:58:59 +0100 Subject: [PATCH 015/285] fix: prevent escaping old name on save as (#1405) --- i18n/en.pot | 8 ++++---- src/components/FileMenu/SaveAsDialog.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 5212a4374..3e2ad820f 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: 2022-09-22T13:25:32.620Z\n" -"PO-Revision-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2022-12-29T10:50:42.111Z\n" +"PO-Revision-Date: 2022-12-29T10:50:42.111Z\n" msgid "view only" msgstr "view only" @@ -257,8 +257,8 @@ msgstr "Description" msgid "Rename" msgstr "Rename" -msgid "{{objectName}} (copy)" -msgstr "{{objectName}} (copy)" +msgid "{{- objectName}} (copy)" +msgstr "{{- objectName}} (copy)" msgid "Save {{fileType}} as" msgstr "Save {{fileType}} as" diff --git a/src/components/FileMenu/SaveAsDialog.js b/src/components/FileMenu/SaveAsDialog.js index 2a05a7e0c..3bdd7f7f6 100644 --- a/src/components/FileMenu/SaveAsDialog.js +++ b/src/components/FileMenu/SaveAsDialog.js @@ -18,7 +18,7 @@ const NAME_MAXLENGTH = 230 export const SaveAsDialog = ({ type, object, onClose, onSaveAs }) => { const [name, setName] = useState( object?.displayName || object?.name - ? i18n.t('{{objectName}} (copy)', { + ? i18n.t('{{- objectName}} (copy)', { objectName: object.name, }) : '' From 883886654e54373c892a12c2860cb9927c7e3338 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 29 Dec 2022 11:04:33 +0000 Subject: [PATCH 016/285] chore(release): cut 24.3.13 [skip ci] ## [24.3.13](https://github.com/dhis2/analytics/compare/v24.3.12...v24.3.13) (2022-12-29) ### Bug Fixes * prevent escaping old name on save as ([#1405](https://github.com/dhis2/analytics/issues/1405)) ([639dc45](https://github.com/dhis2/analytics/commit/639dc45001d0d8b9a0784ffefc96bdbc45271ae3)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd5239b66..6c72068e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.3.13](https://github.com/dhis2/analytics/compare/v24.3.12...v24.3.13) (2022-12-29) + + +### Bug Fixes + +* prevent escaping old name on save as ([#1405](https://github.com/dhis2/analytics/issues/1405)) ([639dc45](https://github.com/dhis2/analytics/commit/639dc45001d0d8b9a0784ffefc96bdbc45271ae3)) + ## [24.3.12](https://github.com/dhis2/analytics/compare/v24.3.11...v24.3.12) (2022-12-22) diff --git a/package.json b/package.json index 4e481f7a0..c97d2f765 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.3.12", + "version": "24.3.13", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From f7972b5b3f56fd02a5baa3f32070b4938b81d0cc Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 2 Jan 2023 10:43:40 +0100 Subject: [PATCH 017/285] fix: remove required prop from save as dialog (#1406) --- src/components/FileMenu/SaveAsDialog.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/FileMenu/SaveAsDialog.js b/src/components/FileMenu/SaveAsDialog.js index 3bdd7f7f6..f6eddff7d 100644 --- a/src/components/FileMenu/SaveAsDialog.js +++ b/src/components/FileMenu/SaveAsDialog.js @@ -45,7 +45,6 @@ export const SaveAsDialog = ({ type, object, onClose, onSaveAs }) => { setName(value.substring(0, NAME_MAXLENGTH)) From b956a402ccbbd9a011f97b7ec06ac2750c06f329 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 2 Jan 2023 09:50:48 +0000 Subject: [PATCH 018/285] chore(release): cut 24.3.14 [skip ci] ## [24.3.14](https://github.com/dhis2/analytics/compare/v24.3.13...v24.3.14) (2023-01-02) ### Bug Fixes * remove required prop from save as dialog ([#1406](https://github.com/dhis2/analytics/issues/1406)) ([f7972b5](https://github.com/dhis2/analytics/commit/f7972b5b3f56fd02a5baa3f32070b4938b81d0cc)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c72068e5..d0654ee03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.3.14](https://github.com/dhis2/analytics/compare/v24.3.13...v24.3.14) (2023-01-02) + + +### Bug Fixes + +* remove required prop from save as dialog ([#1406](https://github.com/dhis2/analytics/issues/1406)) ([f7972b5](https://github.com/dhis2/analytics/commit/f7972b5b3f56fd02a5baa3f32070b4938b81d0cc)) + ## [24.3.13](https://github.com/dhis2/analytics/compare/v24.3.12...v24.3.13) (2022-12-29) diff --git a/package.json b/package.json index c97d2f765..7204e2b06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.3.13", + "version": "24.3.14", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 667db6a04072911f8c3923dbfc33b7baecb955e2 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 3 Jan 2023 17:05:45 +0100 Subject: [PATCH 019/285] feat: enable legends for stacked column/bar (#1388) --- src/modules/visTypes.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/visTypes.js b/src/modules/visTypes.js index c3e3477f7..8150b43f2 100644 --- a/src/modules/visTypes.js +++ b/src/modules/visTypes.js @@ -133,6 +133,8 @@ const legendSetTypes = [ VIS_TYPE_GAUGE, VIS_TYPE_SINGLE_VALUE, VIS_TYPE_PIVOT_TABLE, + VIS_TYPE_STACKED_COLUMN, + VIS_TYPE_STACKED_BAR, ] export const defaultVisType = VIS_TYPE_COLUMN From 9c2ab3719da3dc8205e1a25551f2fee3859c8e8e Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 3 Jan 2023 16:10:14 +0000 Subject: [PATCH 020/285] chore(release): cut 24.4.0 [skip ci] # [24.4.0](https://github.com/dhis2/analytics/compare/v24.3.14...v24.4.0) (2023-01-03) ### Features * enable legends for stacked column/bar ([#1388](https://github.com/dhis2/analytics/issues/1388)) ([667db6a](https://github.com/dhis2/analytics/commit/667db6a04072911f8c3923dbfc33b7baecb955e2)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0654ee03..1d05b0a9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [24.4.0](https://github.com/dhis2/analytics/compare/v24.3.14...v24.4.0) (2023-01-03) + + +### Features + +* enable legends for stacked column/bar ([#1388](https://github.com/dhis2/analytics/issues/1388)) ([667db6a](https://github.com/dhis2/analytics/commit/667db6a04072911f8c3923dbfc33b7baecb955e2)) + ## [24.3.14](https://github.com/dhis2/analytics/compare/v24.3.13...v24.3.14) (2023-01-02) diff --git a/package.json b/package.json index 7204e2b06..0e943de3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.3.14", + "version": "24.4.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 169976cdde28880785ff836f2e0acba263e7bb53 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Fri, 13 Jan 2023 11:01:38 +0100 Subject: [PATCH 021/285] fix: make icon prop not required (#1410) There are 2 instances of this component where there is no icon: "All types" and "All charts". The required prop caused a warning in the console. --- src/components/OpenFileDialog/CustomSelectOption.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/OpenFileDialog/CustomSelectOption.js b/src/components/OpenFileDialog/CustomSelectOption.js index c920b26ce..9e7f7b0ef 100644 --- a/src/components/OpenFileDialog/CustomSelectOption.js +++ b/src/components/OpenFileDialog/CustomSelectOption.js @@ -39,11 +39,11 @@ export const CustomSelectOption = (props) => ) CustomSelectOption.propTypes = { - icon: PropTypes.element.isRequired, label: PropTypes.string.isRequired, value: PropTypes.string.isRequired, active: PropTypes.bool, disabled: PropTypes.bool, + icon: PropTypes.element, onClick: PropTypes.func, } From 34f3708b87e21d858848c7245ab1b4df4ef8afce Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 13 Jan 2023 10:07:37 +0000 Subject: [PATCH 022/285] chore(release): cut 24.4.1 [skip ci] ## [24.4.1](https://github.com/dhis2/analytics/compare/v24.4.0...v24.4.1) (2023-01-13) ### Bug Fixes * make icon prop not required ([#1410](https://github.com/dhis2/analytics/issues/1410)) ([169976c](https://github.com/dhis2/analytics/commit/169976cdde28880785ff836f2e0acba263e7bb53)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d05b0a9f..812f5bbe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.4.1](https://github.com/dhis2/analytics/compare/v24.4.0...v24.4.1) (2023-01-13) + + +### Bug Fixes + +* make icon prop not required ([#1410](https://github.com/dhis2/analytics/issues/1410)) ([169976c](https://github.com/dhis2/analytics/commit/169976cdde28880785ff836f2e0acba263e7bb53)) + # [24.4.0](https://github.com/dhis2/analytics/compare/v24.3.14...v24.4.0) (2023-01-03) diff --git a/package.json b/package.json index 0e943de3c..bbacc5403 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.4.0", + "version": "24.4.1", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From b5523bf9701198645a7029ea48e568ab7d4859c5 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Wed, 25 Jan 2023 15:36:32 +0100 Subject: [PATCH 023/285] fix: use page number from response (#1413) This is to avoid the edge case where a page does not exist anymore after deleting the visualization that was listed in it. The page in the component state needs to be set to the page returned in the response when it differs. Unfortunately there is an extra request with the not existing page, because until we get the response from the api we don't know if a page has been removed or not as a consequence of deleting visualizations. --- src/components/OpenFileDialog/OpenFileDialog.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/OpenFileDialog/OpenFileDialog.js b/src/components/OpenFileDialog/OpenFileDialog.js index fe4cab604..13e121d10 100644 --- a/src/components/OpenFileDialog/OpenFileDialog.js +++ b/src/components/OpenFileDialog/OpenFileDialog.js @@ -144,6 +144,11 @@ export const OpenFileDialog = ({ const { loading, error, data, refetch } = useDataQuery(filesQuery, { lazy: true, + onComplete: (response) => { + if (page !== response.files.pager.page) { + setPage(response.files.pager.page) + } + }, }) const resetFilters = () => { @@ -420,7 +425,7 @@ export const OpenFileDialog = ({ 0 && (
    From 766e10fde36fda4a8ad77ba5203c761aa37db260 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 25 Jan 2023 14:41:36 +0000 Subject: [PATCH 024/285] chore(release): cut 24.4.2 [skip ci] ## [24.4.2](https://github.com/dhis2/analytics/compare/v24.4.1...v24.4.2) (2023-01-25) ### Bug Fixes * use page number from response ([#1413](https://github.com/dhis2/analytics/issues/1413)) ([b5523bf](https://github.com/dhis2/analytics/commit/b5523bf9701198645a7029ea48e568ab7d4859c5)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 812f5bbe0..42be58e48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.4.2](https://github.com/dhis2/analytics/compare/v24.4.1...v24.4.2) (2023-01-25) + + +### Bug Fixes + +* use page number from response ([#1413](https://github.com/dhis2/analytics/issues/1413)) ([b5523bf](https://github.com/dhis2/analytics/commit/b5523bf9701198645a7029ea48e568ab7d4859c5)) + ## [24.4.1](https://github.com/dhis2/analytics/compare/v24.4.0...v24.4.1) (2023-01-13) diff --git a/package.json b/package.json index bbacc5403..099318663 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.4.1", + "version": "24.4.2", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 1b81e973f809e04b179189207a7527034667b0a0 Mon Sep 17 00:00:00 2001 From: Mozafar Date: Tue, 7 Feb 2023 11:04:54 +0000 Subject: [PATCH 025/285] chore: merge next branch to master (#1422) * feat: use multi-calendar-dates library to generate fixed periods * chore(release): cut 24.5.0 [skip ci] # [24.5.0](https://github.com/dhis2/analytics/compare/v24.4.2...v24.5.0) (2023-02-06) ### Features * use multi-calendar-dates library to generate fixed periods ([acc3801](https://github.com/dhis2/analytics/commit/acc380156092f888cfa55f70b4bade1c6516d034)) * Revert "Merge branch 'use-multicalendar-periods' into next" This reverts commit 0082726c72bf6d199b2a5102890dd7dabd5bb02e, reversing changes made to 766e10fde36fda4a8ad77ba5203c761aa37db260. * chore(release): cut 24.5.0 [skip ci] # [24.5.0](https://github.com/dhis2/analytics/compare/v24.4.2...v24.5.0) (2023-02-06) ### Features * use multi-calendar-dates library to generate fixed periods ([acc3801](https://github.com/dhis2/analytics/commit/acc380156092f888cfa55f70b4bade1c6516d034)) * chore: trigger ci * docs: update changelog to remove erroneous entry entry was added because of issues with publishing on next branch --------- Co-authored-by: @dhis2-bot --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42be58e48..5bcd56fdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [24.5.0](https://github.com/dhis2/analytics/compare/v24.4.2...v24.5.0) (2023-02-06) + + +### Features + +* [reverted] use multi-calendar-dates library to generate fixed periods ([acc3801](https://github.com/dhis2/analytics/commit/acc380156092f888cfa55f70b4bade1c6516d034)). This work was added to "next" release channel, but was reverted afterwards because of the restrictions "next" channel puts on what can be released from master afterwards. It will be added to "alpha" channel instead until it's ready for the main channel. + ## [24.4.2](https://github.com/dhis2/analytics/compare/v24.4.1...v24.4.2) (2023-01-25) diff --git a/package.json b/package.json index 099318663..43ea01c41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.4.2", + "version": "24.5.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From ad437c6a1fcbcb71cee7b6945c6bc83ed2bc3bb3 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 13 Feb 2023 08:41:29 +0100 Subject: [PATCH 026/285] feat: single value background color change based upon legend (DHIS2-13702) (#1402) Adds support for background color from legend for SV --- .../config/generators/dhis/singleValue.js | 113 ++++++++++++++---- src/visualizations/config/index.js | 1 + 2 files changed, 91 insertions(+), 23 deletions(-) diff --git a/src/visualizations/config/generators/dhis/singleValue.js b/src/visualizations/config/generators/dhis/singleValue.js index ff1bf11a2..1aaa57ada 100644 --- a/src/visualizations/config/generators/dhis/singleValue.js +++ b/src/visualizations/config/generators/dhis/singleValue.js @@ -1,4 +1,4 @@ -import { colors } from '@dhis2/ui' +import { colors, spacers } from '@dhis2/ui' import { FONT_STYLE_VISUALIZATION_TITLE, FONT_STYLE_VISUALIZATION_SUBTITLE, @@ -11,16 +11,19 @@ import { TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER, mergeFontStyleWithDefault, + defaultFontStyle, } from '../../../../modules/fontStyle.js' -import { getColorByValueFromLegendSet } from '../../../../modules/legends.js' +import { + getColorByValueFromLegendSet, + LEGEND_DISPLAY_STYLE_FILL, +} from '../../../../modules/legends.js' const svgNS = 'http://www.w3.org/2000/svg' const generateValueSVG = ({ - value, formattedValue, subText, - legendSet, + valueColor, noData, y, }) => { @@ -41,8 +44,8 @@ const generateValueSVG = ({ let fillColor = colors.grey900 - if (legendSet) { - fillColor = getColorByValueFromLegendSet(legendSet, value) + if (valueColor) { + fillColor = valueColor } else if (formattedValue === noData.text) { fillColor = colors.grey600 } @@ -87,7 +90,7 @@ const generateValueSVG = ({ return svgValue } -const generateDashboardItem = (config, { legendSet, noData }) => { +const generateDashboardItem = (config, { valueColor, noData }) => { const container = document.createElement('div') container.setAttribute( 'style', @@ -114,10 +117,9 @@ const generateDashboardItem = (config, { legendSet, noData }) => { container.appendChild( generateValueSVG({ - value: config.value, formattedValue: config.formattedValue, subText: config.subText, - legendSet, + valueColor, noData, y: 40, }) @@ -150,7 +152,10 @@ const getXFromTextAlign = (textAlign) => { } } -const generateDVItem = (config, { legendSet, parentEl, fontStyle, noData }) => { +const generateDVItem = ( + config, + { valueColor, titleColor, parentEl, fontStyle, noData } +) => { const parentElBBox = parentEl.getBoundingClientRect() const width = parentElBBox.width @@ -195,7 +200,17 @@ const generateDVItem = (config, { legendSet, parentEl, fontStyle, noData }) => { ? FONT_STYLE_OPTION_ITALIC : 'normal' ) - title.setAttribute('fill', titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR]) + if ( + titleColor && + titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === + defaultFontStyle[FONT_STYLE_VISUALIZATION_TITLE][ + FONT_STYLE_OPTION_TEXT_COLOR + ] + ) { + title.setAttribute('fill', titleColor) + } else { + title.setAttribute('fill', titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR]) + } title.setAttribute('data-test', 'visualization-title') @@ -238,10 +253,21 @@ const generateDVItem = (config, { legendSet, parentEl, fontStyle, noData }) => { ? FONT_STYLE_OPTION_ITALIC : 'normal' ) - subtitle.setAttribute( - 'fill', - subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] - ) + + if ( + titleColor && + subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === + defaultFontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE][ + FONT_STYLE_OPTION_TEXT_COLOR + ] + ) { + subtitle.setAttribute('fill', titleColor) + } else { + subtitle.setAttribute( + 'fill', + subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] + ) + } subtitle.setAttribute('data-test', 'visualization-subtitle') @@ -253,10 +279,9 @@ const generateDVItem = (config, { legendSet, parentEl, fontStyle, noData }) => { svg.appendChild( generateValueSVG({ - value: config.value, formattedValue: config.formattedValue, subText: config.subText, - legendSet, + valueColor, noData, y: 20, }) @@ -265,17 +290,59 @@ const generateDVItem = (config, { legendSet, parentEl, fontStyle, noData }) => { return svg } +const shouldUseContrastColor = (inputColor) => { + // based on https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color + var color = + inputColor.charAt(0) === '#' ? inputColor.substring(1, 7) : inputColor + var r = parseInt(color.substring(0, 2), 16) // hexToR + var g = parseInt(color.substring(2, 4), 16) // hexToG + var b = parseInt(color.substring(4, 6), 16) // hexToB + var uicolors = [r / 255, g / 255, b / 255] + var c = uicolors.map((col) => { + if (col <= 0.03928) { + return col / 12.92 + } + return Math.pow((col + 0.055) / 1.055, 2.4) + }) + var L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2] + return L <= 0.179 +} + export default function ( config, parentEl, - { dashboard, legendSets, fontStyle, noData } + { dashboard, legendSets, fontStyle, noData, legendOptions } ) { - const legendSet = legendSets[0] + const legendSet = legendOptions && legendSets[0] + const legendColor = + legendSet && getColorByValueFromLegendSet(legendSet, config.value) + let valueColor, titleColor + if (legendColor) { + if (legendOptions.style === LEGEND_DISPLAY_STYLE_FILL) { + parentEl.style.background = legendColor + valueColor = titleColor = + shouldUseContrastColor(legendColor) && colors.white + } else { + valueColor = legendColor + } + } + parentEl.style.overflow = 'hidden' parentEl.style.display = 'flex' parentEl.style.justifyContent = 'center' - - return dashboard - ? generateDashboardItem(config, { legendSet, noData }) - : generateDVItem(config, { legendSet, parentEl, fontStyle, noData }) + parentEl.style.borderRadius = spacers.dp8 + + if (dashboard) { + return generateDashboardItem(config, { valueColor, noData }) + } else { + parentEl.style.margin = spacers.dp8 + parentEl.style.height = `calc(100% - (${spacers.dp8} * 2))` + return generateDVItem(config, { + valueColor, + titleColor, + parentEl, + fontStyle, + noData, + }) + } } diff --git a/src/visualizations/config/index.js b/src/visualizations/config/index.js index 68e0286d6..28ca603d9 100644 --- a/src/visualizations/config/index.js +++ b/src/visualizations/config/index.js @@ -69,5 +69,6 @@ export default function ({ ...extraOptions, noData: DEFAULT_EXTRA_OPTIONS.noData, fontStyle: layout.fontStyle, + legendOptions: layout.legend, }) } From 2d7183201a41b388dc8d88e2b78773a47d8527c0 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 13 Feb 2023 09:54:21 +0000 Subject: [PATCH 027/285] chore(release): cut 24.6.0 [skip ci] # [24.6.0](https://github.com/dhis2/analytics/compare/v24.5.0...v24.6.0) (2023-02-13) ### Features * single value background color change based upon legend (DHIS2-13702) ([#1402](https://github.com/dhis2/analytics/issues/1402)) ([ad437c6](https://github.com/dhis2/analytics/commit/ad437c6a1fcbcb71cee7b6945c6bc83ed2bc3bb3)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bcd56fdf..511299330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [24.6.0](https://github.com/dhis2/analytics/compare/v24.5.0...v24.6.0) (2023-02-13) + + +### Features + +* single value background color change based upon legend (DHIS2-13702) ([#1402](https://github.com/dhis2/analytics/issues/1402)) ([ad437c6](https://github.com/dhis2/analytics/commit/ad437c6a1fcbcb71cee7b6945c6bc83ed2bc3bb3)) + # [24.5.0](https://github.com/dhis2/analytics/compare/v24.4.2...v24.5.0) (2023-02-06) diff --git a/package.json b/package.json index 43ea01c41..3605428b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.5.0", + "version": "24.6.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From ae2f886167a487271bf42c2f4c694e45ea4b25b3 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Wed, 15 Feb 2023 11:32:52 +0100 Subject: [PATCH 028/285] feat: new props for customising OrgUnitDimension DHIS2-14744 (#1427) New props are: * hideLevelSelect: controls wether the level select is shown or not (default false) * hideGroupSelect: controls wether the group select is shown or not (default false) * warning: displays a custom error/warning message (default not set) --- src/__demo__/OrgUnitDimension.stories.js | 68 +++++++- .../OrgUnitDimension/OrgUnitDimension.js | 148 +++++++++++------- .../styles/OrgUnitDimension.style.js | 18 +++ 3 files changed, 174 insertions(+), 60 deletions(-) diff --git a/src/__demo__/OrgUnitDimension.stories.js b/src/__demo__/OrgUnitDimension.stories.js index 82586c35e..3fc796351 100644 --- a/src/__demo__/OrgUnitDimension.stories.js +++ b/src/__demo__/OrgUnitDimension.stories.js @@ -4,7 +4,10 @@ import React, { useState } from 'react' import OrgUnitDimension from '../components/OrgUnitDimension/OrgUnitDimension.js' const Wrapper = (story) => ( - + {story()} ) @@ -134,3 +137,66 @@ storiesOf('OrgUnitDimension', module) /> ) }) + +storiesOf('OrgUnitDimension', module) + .addDecorator(Wrapper) + .add('Without level selector', () => { + const [selected, setSelected] = useState([]) + + return ( + setSelected(response.items)} + roots={defaultRootOrgUnits} + /> + ) + }) + +storiesOf('OrgUnitDimension', module) + .addDecorator(Wrapper) + .add('Without group selector', () => { + const [selected, setSelected] = useState([]) + + return ( + setSelected(response.items)} + roots={defaultRootOrgUnits} + /> + ) + }) + +storiesOf('OrgUnitDimension', module) + .addDecorator(Wrapper) + .add('Without level and group selector', () => { + const [selected, setSelected] = useState([]) + + return ( + setSelected(response.items)} + roots={defaultRootOrgUnits} + /> + ) + }) + +storiesOf('OrgUnitDimension', module) + .addDecorator(Wrapper) + .add('Without level and group selector, with warning text', () => { + const [selected, setSelected] = useState([]) + + return ( + setSelected(response.items)} + roots={defaultRootOrgUnits} + warning={'No org. units selected'} + /> + ) + }) diff --git a/src/components/OrgUnitDimension/OrgUnitDimension.js b/src/components/OrgUnitDimension/OrgUnitDimension.js index e72372256..f0433070a 100644 --- a/src/components/OrgUnitDimension/OrgUnitDimension.js +++ b/src/components/OrgUnitDimension/OrgUnitDimension.js @@ -5,6 +5,8 @@ import { MultiSelect, MultiSelectOption, Button, + IconWarningFilled16, + colors, } from '@dhis2/ui' import cx from 'classnames' import PropTypes from 'prop-types' @@ -30,7 +32,14 @@ const DYNAMIC_ORG_UNITS = [ USER_ORG_UNIT_GRANDCHILDREN, ] -const OrgUnitDimension = ({ roots, selected, onSelect }) => { +const OrgUnitDimension = ({ + roots, + selected, + onSelect, + hideGroupSelect, + hideLevelSelect, + warning, +}) => { const [ouLevels, setOuLevels] = useState([]) const [ouGroups, setOuGroups] = useState([]) const dataEngine = useDataEngine() @@ -73,9 +82,9 @@ const OrgUnitDimension = ({ roots, selected, onSelect }) => { setOuGroups(result) } - doFetchOuLevels() - doFetchOuGroups() - }, [dataEngine]) + !hideLevelSelect && doFetchOuLevels() + !hideGroupSelect && doFetchOuGroups() + }, [dataEngine, hideLevelSelect, hideGroupSelect]) const onLevelChange = (ids) => { const items = ids.map((id) => ({ @@ -261,65 +270,77 @@ const OrgUnitDimension = ({ roots, selected, onSelect }) => { disabled: selected.some((item) => DYNAMIC_ORG_UNITS.includes(item.id) ), + hidden: hideLevelSelect && hideGroupSelect, })} > - - ouIdHelper.hasLevelPrefix(item.id) - ) - .map((item) => - ouIdHelper.removePrefix(item.id) - ) - : [] - } - onChange={({ selected }) => onLevelChange(selected)} - placeholder={i18n.t('Select a level')} - loading={!ouLevels.length} - dense - dataTest={'org-unit-level-select'} - > - {ouLevels.map((level) => ( - - ))} - - - ouIdHelper.hasGroupPrefix(item.id) - ) - .map((item) => - ouIdHelper.removePrefix(item.id) - ) - : [] - } - onChange={({ selected }) => onGroupChange(selected)} - placeholder={i18n.t('Select a group')} - loading={!ouGroups.length} - dense - dataTest={'org-unit-group-select'} - > - {ouGroups.map((group) => ( - - ))} - + {!hideLevelSelect && ( + + ouIdHelper.hasLevelPrefix(item.id) + ) + .map((item) => + ouIdHelper.removePrefix(item.id) + ) + : [] + } + onChange={({ selected }) => onLevelChange(selected)} + placeholder={i18n.t('Select a level')} + loading={!ouLevels.length} + dense + dataTest={'org-unit-level-select'} + > + {ouLevels.map((level) => ( + + ))} + + )} + {!hideGroupSelect && ( + + ouIdHelper.hasGroupPrefix(item.id) + ) + .map((item) => + ouIdHelper.removePrefix(item.id) + ) + : [] + } + onChange={({ selected }) => onGroupChange(selected)} + placeholder={i18n.t('Select a group')} + loading={!ouGroups.length} + dense + dataTest={'org-unit-group-select'} + > + {ouGroups.map((group) => ( + + ))} + + )}
    - {getSummary()} + {warning ? ( +
    + + {warning} +
    + ) : ( + {getSummary()} + )}
    ) } + +OrgUnitDimension.defaultProps = { + hideGroupSelect: false, + hideLevelSelect: false, +} + OrgUnitDimension.propTypes = { + hideGroupSelect: PropTypes.bool, + hideLevelSelect: PropTypes.bool, roots: PropTypes.arrayOf(PropTypes.string), selected: PropTypes.arrayOf( PropTypes.shape({ @@ -344,6 +373,7 @@ OrgUnitDimension.propTypes = { path: PropTypes.string, }) ), + warning: PropTypes.string, onSelect: PropTypes.func, } diff --git a/src/components/OrgUnitDimension/styles/OrgUnitDimension.style.js b/src/components/OrgUnitDimension/styles/OrgUnitDimension.style.js index b9a31f8e6..99f8e8a00 100644 --- a/src/components/OrgUnitDimension/styles/OrgUnitDimension.style.js +++ b/src/components/OrgUnitDimension/styles/OrgUnitDimension.style.js @@ -45,14 +45,32 @@ export default css` margin-top: ${spacers.dp12}; } + .selectsWrapper.hidden { + display: none; + } + .selectsWrapper > :global(*) { width: 50%; } .summaryWrapper { + display: inline-flex; + align-items: center; margin-top: ${spacers.dp8}; } + .warningWrapper { + display: inline-flex; + align-items: center; + } + + .warningText { + margin-left: ${spacers.dp8}; + font-size: 14px; + line-height: 18px; + color: ${colors.red600}; + } + .summaryText { font-size: 14px; line-height: 18px; From 8ef07a6ba46c8a3445ddf3938b9437ce09befc44 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 15 Feb 2023 10:37:14 +0000 Subject: [PATCH 029/285] chore(release): cut 24.7.0 [skip ci] # [24.7.0](https://github.com/dhis2/analytics/compare/v24.6.0...v24.7.0) (2023-02-15) ### Features * new props for customising OrgUnitDimension DHIS2-14744 ([#1427](https://github.com/dhis2/analytics/issues/1427)) ([ae2f886](https://github.com/dhis2/analytics/commit/ae2f886167a487271bf42c2f4c694e45ea4b25b3)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 511299330..30072a098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [24.7.0](https://github.com/dhis2/analytics/compare/v24.6.0...v24.7.0) (2023-02-15) + + +### Features + +* new props for customising OrgUnitDimension DHIS2-14744 ([#1427](https://github.com/dhis2/analytics/issues/1427)) ([ae2f886](https://github.com/dhis2/analytics/commit/ae2f886167a487271bf42c2f4c694e45ea4b25b3)) + # [24.6.0](https://github.com/dhis2/analytics/compare/v24.5.0...v24.6.0) (2023-02-13) diff --git a/package.json b/package.json index 3605428b1..e4360fff1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.6.0", + "version": "24.7.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 02d3057c7c090e293ba7c3057720f491e71457ec Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Thu, 16 Feb 2023 15:11:10 +0100 Subject: [PATCH 030/285] feat: add prop for controlling user org unit selector DHIS2-14744 (#1430) --- src/__demo__/OrgUnitDimension.stories.js | 15 +++ .../OrgUnitDimension/OrgUnitDimension.js | 91 ++++++++++--------- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/src/__demo__/OrgUnitDimension.stories.js b/src/__demo__/OrgUnitDimension.stories.js index 3fc796351..533eb356b 100644 --- a/src/__demo__/OrgUnitDimension.stories.js +++ b/src/__demo__/OrgUnitDimension.stories.js @@ -138,6 +138,21 @@ storiesOf('OrgUnitDimension', module) ) }) +storiesOf('OrgUnitDimension', module) + .addDecorator(Wrapper) + .add('Without user org units selection', () => { + const [selected, setSelected] = useState([]) + + return ( + setSelected(response.items)} + roots={defaultRootOrgUnits} + /> + ) + }) + storiesOf('OrgUnitDimension', module) .addDecorator(Wrapper) .add('Without level selector', () => { diff --git a/src/components/OrgUnitDimension/OrgUnitDimension.js b/src/components/OrgUnitDimension/OrgUnitDimension.js index f0433070a..3516daf46 100644 --- a/src/components/OrgUnitDimension/OrgUnitDimension.js +++ b/src/components/OrgUnitDimension/OrgUnitDimension.js @@ -38,6 +38,7 @@ const OrgUnitDimension = ({ onSelect, hideGroupSelect, hideLevelSelect, + hideUserOrgUnits, warning, }) => { const [ouLevels, setOuLevels] = useState([]) @@ -185,48 +186,52 @@ const OrgUnitDimension = ({ return (
    -
    - item.id === USER_ORG_UNIT)} - onChange={({ checked }) => - onSelectItems({ - id: USER_ORG_UNIT, - checked, - displayName: i18n.t('User organisation unit'), - }) - } - dense - /> - item.id === USER_ORG_UNIT_CHILDREN - )} - onChange={({ checked }) => - onSelectItems({ - id: USER_ORG_UNIT_CHILDREN, - checked, - displayName: i18n.t('User sub-units'), - }) - } - dense - /> - item.id === USER_ORG_UNIT_GRANDCHILDREN - )} - onChange={({ checked }) => - onSelectItems({ - id: USER_ORG_UNIT_GRANDCHILDREN, - checked, - displayName: i18n.t('User sub-x2-units'), - }) - } - dense - /> -
    + {!hideUserOrgUnits && ( +
    + item.id === USER_ORG_UNIT + )} + onChange={({ checked }) => + onSelectItems({ + id: USER_ORG_UNIT, + checked, + displayName: i18n.t('User organisation unit'), + }) + } + dense + /> + item.id === USER_ORG_UNIT_CHILDREN + )} + onChange={({ checked }) => + onSelectItems({ + id: USER_ORG_UNIT_CHILDREN, + checked, + displayName: i18n.t('User sub-units'), + }) + } + dense + /> + item.id === USER_ORG_UNIT_GRANDCHILDREN + )} + onChange={({ checked }) => + onSelectItems({ + id: USER_ORG_UNIT_GRANDCHILDREN, + checked, + displayName: i18n.t('User sub-x2-units'), + }) + } + dense + /> +
    + )}
    @@ -360,11 +365,13 @@ const OrgUnitDimension = ({ OrgUnitDimension.defaultProps = { hideGroupSelect: false, hideLevelSelect: false, + hideUserOrgUnits: false, } OrgUnitDimension.propTypes = { hideGroupSelect: PropTypes.bool, hideLevelSelect: PropTypes.bool, + hideUserOrgUnits: PropTypes.bool, roots: PropTypes.arrayOf(PropTypes.string), selected: PropTypes.arrayOf( PropTypes.shape({ From aad71f665e8c84729e4cc27a5707f6b9430dc970 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 16 Feb 2023 14:15:36 +0000 Subject: [PATCH 031/285] chore(release): cut 24.8.0 [skip ci] # [24.8.0](https://github.com/dhis2/analytics/compare/v24.7.0...v24.8.0) (2023-02-16) ### Features * add prop for controlling user org unit selector DHIS2-14744 ([#1430](https://github.com/dhis2/analytics/issues/1430)) ([02d3057](https://github.com/dhis2/analytics/commit/02d3057c7c090e293ba7c3057720f491e71457ec)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30072a098..c4dde9668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [24.8.0](https://github.com/dhis2/analytics/compare/v24.7.0...v24.8.0) (2023-02-16) + + +### Features + +* add prop for controlling user org unit selector DHIS2-14744 ([#1430](https://github.com/dhis2/analytics/issues/1430)) ([02d3057](https://github.com/dhis2/analytics/commit/02d3057c7c090e293ba7c3057720f491e71457ec)) + # [24.7.0](https://github.com/dhis2/analytics/compare/v24.6.0...v24.7.0) (2023-02-15) diff --git a/package.json b/package.json index e4360fff1..8bfaf9421 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.7.0", + "version": "24.8.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 3cdac8fdefc7a15a3fef0940183ca92f03b0f57c Mon Sep 17 00:00:00 2001 From: Mozafar Date: Thu, 23 Feb 2023 13:07:30 +0000 Subject: [PATCH 032/285] feat: merge multi-calendar support from alpha to master (#1434) * feat: use multi-calendar-dates library to generate fixed periods * fix: ensure date falls back to year if no eraYear present * refactor: remove unnecessary default value for config * test: fix failing test * chore(release): cut 24.5.0-alpha.1 [skip ci] # [24.5.0-alpha.1](https://github.com/dhis2/analytics/compare/v24.4.2...v24.5.0-alpha.1) (2023-02-07) ### Bug Fixes * ensure date falls back to year if no eraYear present ([2a197d5](https://github.com/dhis2/analytics/commit/2a197d57be8ac2af6dbe331dd8f11ab9069afa5d)) ### Features * use multi-calendar-dates library to generate fixed periods ([acc3801](https://github.com/dhis2/analytics/commit/acc380156092f888cfa55f70b4bade1c6516d034)) * fix: bump multi-calendar-dates to avoid jest config change * chore(release): cut 24.5.0-alpha.2 [skip ci] # [24.5.0-alpha.2](https://github.com/dhis2/analytics/compare/v24.5.0-alpha.1...v24.5.0-alpha.2) (2023-02-14) ### Bug Fixes * bump multi-calendar-dates to avoid jest config change ([3e5892a](https://github.com/dhis2/analytics/commit/3e5892aa88a087dde3405a518625f4c6e637d17e)) * feat: localise fixed periods according to user settings * chore: bump @dhis2/multi-calendar-dates to main version * chore(release): cut 24.5.0-alpha.3 [skip ci] # [24.5.0-alpha.3](https://github.com/dhis2/analytics/compare/v24.5.0-alpha.2...v24.5.0-alpha.3) (2023-02-23) ### Features * localise fixed periods according to user settings ([c640c90](https://github.com/dhis2/analytics/commit/c640c90ce2d1944a14706507284af43c7480fa2a)) * chore(release): cut 24.9.0-alpha.1 [skip ci] # [24.9.0-alpha.1](https://github.com/dhis2/analytics/compare/v24.8.0...v24.9.0-alpha.1) (2023-02-23) ### Bug Fixes * bump multi-calendar-dates to avoid jest config change ([3e5892a](https://github.com/dhis2/analytics/commit/3e5892aa88a087dde3405a518625f4c6e637d17e)) * ensure date falls back to year if no eraYear present ([2a197d5](https://github.com/dhis2/analytics/commit/2a197d57be8ac2af6dbe331dd8f11ab9069afa5d)) ### Features * localise fixed periods according to user settings ([c640c90](https://github.com/dhis2/analytics/commit/c640c90ce2d1944a14706507284af43c7480fa2a)) * use multi-calendar-dates library to generate fixed periods ([acc3801](https://github.com/dhis2/analytics/commit/acc380156092f888cfa55f70b4bade1c6516d034)) * refactor: move period settings to parent container component --------- Co-authored-by: @dhis2-bot --- CHANGELOG.md | 35 +- package.json | 3 +- .../PeriodDimension/PeriodDimension.js | 19 + .../PeriodDimension/PeriodTransfer.js | 32 +- .../__tests__/PeriodDimension.spec.js | 7 + .../PeriodDimension.spec.js.snap | 6 + .../PeriodDimension/utils/fixedPeriods.js | 841 ++++++------------ yarn.lock | 31 + 8 files changed, 393 insertions(+), 581 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4dde9668..54dd093c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,41 @@ -# [24.8.0](https://github.com/dhis2/analytics/compare/v24.7.0...v24.8.0) (2023-02-16) +# [24.9.0-alpha.1](https://github.com/dhis2/analytics/compare/v24.8.0...v24.9.0-alpha.1) (2023-02-23) + + +### Bug Fixes + +* bump multi-calendar-dates to avoid jest config change ([3e5892a](https://github.com/dhis2/analytics/commit/3e5892aa88a087dde3405a518625f4c6e637d17e)) +* ensure date falls back to year if no eraYear present ([2a197d5](https://github.com/dhis2/analytics/commit/2a197d57be8ac2af6dbe331dd8f11ab9069afa5d)) + + +### Features + +* localise fixed periods according to user settings ([c640c90](https://github.com/dhis2/analytics/commit/c640c90ce2d1944a14706507284af43c7480fa2a)) +* use multi-calendar-dates library to generate fixed periods ([acc3801](https://github.com/dhis2/analytics/commit/acc380156092f888cfa55f70b4bade1c6516d034)) + +# [24.5.0-alpha.3](https://github.com/dhis2/analytics/compare/v24.5.0-alpha.2...v24.5.0-alpha.3) (2023-02-23) ### Features +* localise fixed periods according to user settings ([c640c90](https://github.com/dhis2/analytics/commit/c640c90ce2d1944a14706507284af43c7480fa2a)) + +# [24.5.0-alpha.2](https://github.com/dhis2/analytics/compare/v24.5.0-alpha.1...v24.5.0-alpha.2) (2023-02-14) + + +### Bug Fixes + +* bump multi-calendar-dates to avoid jest config change ([3e5892a](https://github.com/dhis2/analytics/commit/3e5892aa88a087dde3405a518625f4c6e637d17e)) + +# [24.5.0-alpha.1](https://github.com/dhis2/analytics/compare/v24.4.2...v24.5.0-alpha.1) (2023-02-07) + + +### Bug Fixes + +* ensure date falls back to year if no eraYear present ([2a197d5](https://github.com/dhis2/analytics/commit/2a197d57be8ac2af6dbe331dd8f11ab9069afa5d)) + +# [24.8.0](https://github.com/dhis2/analytics/compare/v24.7.0...v24.8.0) (2023-02-16) + + * add prop for controlling user org unit selector DHIS2-14744 ([#1430](https://github.com/dhis2/analytics/issues/1430)) ([02d3057](https://github.com/dhis2/analytics/commit/02d3057c7c090e293ba7c3057720f491e71457ec)) # [24.7.0](https://github.com/dhis2/analytics/compare/v24.6.0...v24.7.0) (2023-02-15) diff --git a/package.json b/package.json index 8bfaf9421..628ace6a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.8.0", + "version": "24.9.0-alpha.1", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { @@ -59,6 +59,7 @@ }, "dependencies": { "@dhis2/d2-ui-rich-text": "^7.4.0", + "@dhis2/multi-calendar-dates": "1.0.0", "classnames": "^2.3.1", "d2-utilizr": "^0.2.16", "d3-color": "^1.2.3", diff --git a/src/components/PeriodDimension/PeriodDimension.js b/src/components/PeriodDimension/PeriodDimension.js index 09592ccd8..e60cdf029 100644 --- a/src/components/PeriodDimension/PeriodDimension.js +++ b/src/components/PeriodDimension/PeriodDimension.js @@ -1,14 +1,32 @@ +import { useConfig, useDataQuery } from '@dhis2/app-runtime' import PropTypes from 'prop-types' import React from 'react' import { DIMENSION_ID_PERIOD } from '../../modules/predefinedDimensions.js' import PeriodTransfer from './PeriodTransfer.js' +const userSettingsQuery = { + userSettings: { + resource: 'userSettings', + params: { + key: ['keyUiLocale'], + }, + }, +} + const PeriodDimension = ({ onSelect, selectedPeriods, rightFooter, excludedPeriodTypes, }) => { + const { systemInfo } = useConfig() + const result = useDataQuery(userSettingsQuery) + + const { calendar = 'gregory' } = systemInfo + const { data: { userSettings: { keyUiLocale: locale } = {} } = {} } = result + + const periodsSettings = { calendar, locale } + const selectPeriods = (periods) => { onSelect({ dimensionId: DIMENSION_ID_PERIOD, @@ -22,6 +40,7 @@ const PeriodDimension = ({ rightFooter={rightFooter} dataTest={'period-dimension'} excludedPeriodTypes={excludedPeriodTypes} + periodsSettings={periodsSettings} /> ) } diff --git a/src/components/PeriodDimension/PeriodTransfer.js b/src/components/PeriodDimension/PeriodTransfer.js index 4f105df83..36d352783 100644 --- a/src/components/PeriodDimension/PeriodTransfer.js +++ b/src/components/PeriodDimension/PeriodTransfer.js @@ -1,3 +1,4 @@ +import { getNowInCalendar } from '@dhis2/multi-calendar-dates' import { TabBar, Tab, Transfer } from '@dhis2/ui' import PropTypes from 'prop-types' import React, { useState } from 'react' @@ -22,14 +23,20 @@ const PeriodTransfer = ({ initialSelectedPeriods, rightFooter, excludedPeriodTypes, + periodsSettings, }) => { const defaultRelativePeriodType = excludedPeriodTypes.includes(MONTHLY) ? getRelativePeriodsOptionsById(QUARTERLY) : getRelativePeriodsOptionsById(MONTHLY) const defaultFixedPeriodType = excludedPeriodTypes.includes(MONTHLY) - ? getFixedPeriodsOptionsById(QUARTERLY) - : getFixedPeriodsOptionsById(MONTHLY) - const defaultFixedPeriodYear = new Date().getFullYear() + ? getFixedPeriodsOptionsById(QUARTERLY, periodsSettings) + : getFixedPeriodsOptionsById(MONTHLY, periodsSettings) + + const now = getNowInCalendar(periodsSettings.calendar) + // use ".eraYear" rather than ".year" because in Ethiopian calendar, eraYear is what our users expect to see (for other calendars, it doesn't matter) + // there is still a pending decision in Temporal regarding which era to use by default: https://github.com/js-temporal/temporal-polyfill/blob/9350ee7dd0d29f329fc097debf923a517c32f813/lib/calendar.ts#L1964 + const defaultFixedPeriodYear = now.eraYear || now.year + const fixedPeriodConfig = (year) => ({ offset: year - defaultFixedPeriodYear, filterFuturePeriods: false, @@ -60,7 +67,8 @@ const PeriodTransfer = ({ relativeFilter.periodType ).getPeriods() : getFixedPeriodsOptionsById( - fixedFilter.periodType + fixedFilter.periodType, + periodsSettings ).getPeriods(fixedPeriodConfig(Number(fixedFilter.year))) ) } @@ -134,8 +142,12 @@ const PeriodTransfer = ({ const onSelectFixedPeriods = (filter) => { setFixedFilter(filter) setAllPeriods( - getFixedPeriodsOptionsById(filter.periodType).getPeriods( - fixedPeriodConfig(Number(filter.year)) + getFixedPeriodsOptionsById( + filter.periodType, + periodsSettings + ).getPeriods( + fixedPeriodConfig(Number(filter.year)), + periodsSettings ) ) } @@ -189,6 +201,10 @@ const PeriodTransfer = ({ PeriodTransfer.defaultProps = { initialSelectedPeriods: [], excludedPeriodTypes: [], + periodsSettings: { + calendar: 'gregory', + locale: 'en', + }, } PeriodTransfer.propTypes = { @@ -201,6 +217,10 @@ PeriodTransfer.propTypes = { name: PropTypes.string, }) ), + periodsSettings: PropTypes.shape({ + calendar: PropTypes.string, + locale: PropTypes.string, + }), rightFooter: PropTypes.node, } diff --git a/src/components/PeriodDimension/__tests__/PeriodDimension.spec.js b/src/components/PeriodDimension/__tests__/PeriodDimension.spec.js index a6081018c..8a10c9518 100644 --- a/src/components/PeriodDimension/__tests__/PeriodDimension.spec.js +++ b/src/components/PeriodDimension/__tests__/PeriodDimension.spec.js @@ -2,6 +2,13 @@ import { shallow } from 'enzyme' import React from 'react' import PeriodDimension from '../PeriodDimension.js' +jest.mock('@dhis2/app-runtime', () => ({ + useConfig: () => ({ systemInfo: {} }), + useDataQuery: () => ({ data: { userSettings: { keyUiLocale: 'en' } } }), +})) + +afterEach(jest.clearAllMocks) + describe('The Period Dimension component', () => { let props let shallowPeriodDimension diff --git a/src/components/PeriodDimension/__tests__/__snapshots__/PeriodDimension.spec.js.snap b/src/components/PeriodDimension/__tests__/__snapshots__/PeriodDimension.spec.js.snap index c7c95c7f7..1bf93ba94 100644 --- a/src/components/PeriodDimension/__tests__/__snapshots__/PeriodDimension.spec.js.snap +++ b/src/components/PeriodDimension/__tests__/__snapshots__/PeriodDimension.spec.js.snap @@ -6,6 +6,12 @@ exports[`The Period Dimension component matches the snapshot 1`] = ` excludedPeriodTypes={Array []} initialSelectedPeriods={Array []} onSelect={[Function]} + periodsSettings={ + Object { + "calendar": "gregory", + "locale": "en", + } + } rightFooter={} /> `; diff --git a/src/components/PeriodDimension/utils/fixedPeriods.js b/src/components/PeriodDimension/utils/fixedPeriods.js index ad0459d6a..d3e1fc1a9 100644 --- a/src/components/PeriodDimension/utils/fixedPeriods.js +++ b/src/components/PeriodDimension/utils/fixedPeriods.js @@ -1,3 +1,7 @@ +import { + generateFixedPeriods, + getNowInCalendar, +} from '@dhis2/multi-calendar-dates' import i18n from '../../../locales/index.js' import { DAILY, @@ -40,510 +44,177 @@ const PERIOD_TYPE_REGEX = { [FYAPR]: /^([0-9]{4})April$/, // YYYY"April" } -const getMonthName = (key) => { - const monthNames = [ - i18n.t('January'), - i18n.t('February'), - i18n.t('March'), - i18n.t('April'), - i18n.t('May'), - i18n.t('June'), - i18n.t('July'), - i18n.t('August'), - i18n.t('September'), - i18n.t('October'), - i18n.t('November'), - i18n.t('December'), - ] - - return monthNames[key] -} +const getPeriods = ({ periodType, config, fnFilter, periodSettings = {} }) => { + const offset = parseInt(config.offset, 10) + const isFilter = config.filterFuturePeriods + const isReverse = periodType.match(/^FY|YEARLY/) + ? true + : config.reversePeriods + + const { calendar = 'gregory', locale = 'en' } = periodSettings + const now = getNowInCalendar(calendar) + const year = (now.eraYear || now.year) + offset + + const params = { + periodType, + year, + calendar, + locale, + startingDay: config.startDay, + } -const getDailyPeriodType = (formatYyyyMmDd, fnFilter) => { - return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = new Date(`01 Jan ${year}`) - - while (date.getFullYear() === year) { - const period = {} - period.startDate = formatYyyyMmDd(date) - period.endDate = period.startDate - period.name = period.startDate - period.iso = period.startDate.replace(/-/g, '') - period.id = period.iso - periods.push(period) - date.setDate(date.getDate() + 1) - } + let periods = generateFixedPeriods(params) - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods.reverse() : periods + periods = isFilter ? fnFilter(periods) : periods + periods = !isReverse ? periods : periods.reverse() - return periods - } + return periods } -const getWeeklyPeriodType = (formatYyyyMmDd, weekObj, fnFilter) => { - // Calculate the first date of an EPI year base on ISO standard ( first week always contains 4th Jan ) - const getEpiWeekStartDay = (year, startDayOfWeek) => { - const jan4 = new Date(year, 0, 4) - const jan4DayOfWeek = jan4.getDay() - const startDate = jan4 - const dayDiff = jan4DayOfWeek - startDayOfWeek - - if (dayDiff > 0) { - startDate.setDate(jan4.getDate() - dayDiff) - } else if (dayDiff < 0) { - startDate.setDate(jan4.getDate() - dayDiff) - startDate.setDate(startDate.getDate() - 7) - } - - return startDate - } - +const getDailyPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = getEpiWeekStartDay(year, weekObj.startDay) - let week = 1 - - while (date.getFullYear() <= year) { - const period = {} - period.startDate = formatYyyyMmDd(date) - period.iso = `${year}${weekObj.shortName}W${week}` - period.id = period.iso - date.setDate(date.getDate() + 6) - period.endDate = formatYyyyMmDd(date) - - const weekNumber = week - period.name = `${i18n.t('Week {{weekNumber}}', { weekNumber })} - ${ - period.startDate - } - ${period.endDate}` - - // if end date is Jan 4th or later, week belongs to next year - if (date.getFullYear() > year && date.getDate() >= 4) { - break - } - - periods.push(period) - date.setDate(date.getDate() + 1) - - week += 1 - } - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods.reverse() : periods - - return periods + return getPeriods({ + periodType: 'DAILY', + config, + fnFilter, + periodSettings, + }) } } -const getBiWeeklyPeriodType = (formatYyyyMmDd, fnFilter) => { +const getWeeklyPeriodType = (weekObj, fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = new Date(`01 Jan ${year}`) - const day = date.getDay() - let biWeek = 1 - - if (day <= 4) { - date.setDate(date.getDate() - (day - 1)) - } else { - date.setDate(date.getDate() + (8 - day)) - } - - while (date.getFullYear() <= year) { - const period = {} - - period.iso = `${year}BiW${biWeek}` - period.id = period.iso - period.startDate = formatYyyyMmDd(date) - date.setDate(date.getDate() + 13) - - period.endDate = formatYyyyMmDd(date) - const biWeekNumber = biWeek - period.name = `${i18n.t('Bi-Week {{biWeekNumber}}', { - biWeekNumber, - })} - ${period.startDate} - ${period.endDate}` - - // if end date is Jan 4th or later, biweek belongs to next year - if (date.getFullYear() > year && date.getDate() >= 4) { - break - } - - periods.push(period) - - date.setDate(date.getDate() + 1) - - biWeek += 1 - } - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods.reverse() : periods - - return periods + return getPeriods({ + periodType: 'WEEKLY', + config: { + ...config, + startDay: weekObj.startDay, + }, + fnFilter, + periodSettings, + }) } } -const getMonthlyPeriodType = (formatYyyyMmDd, fnFilter) => { - const formatIso = (date) => { - const y = date.getFullYear() - let m = String(date.getMonth() + 1) - - m = m.length < 2 ? `0${m}` : m - - return y + m - } - +const getBiWeeklyPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = new Date(`31 Dec ${year}`) - - while (date.getFullYear() === year) { - const period = {} - - period.endDate = formatYyyyMmDd(date) - date.setDate(1) - period.startDate = formatYyyyMmDd(date) - const monthName = getMonthName(date.getMonth()) - period.name = `${monthName} ${year}` - period.iso = formatIso(date) - period.id = period.iso - - periods.push(period) - date.setDate(0) - } - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods : periods.reverse() - // Months are collected backwards. If isReverse is true, then do nothing. Else reverse to correct order and return. - - return periods + return getPeriods({ + periodType: 'BIWEEKLY', + config, + fnFilter, + periodSettings, + }) } } -const getBiMonthlyPeriodType = (formatYyyyMmDd, fnFilter) => { +const getMonthlyPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = new Date(`31 Dec ${year}`) - let index = 6 - - while (date.getFullYear() === year) { - const period = {} - - period.endDate = formatYyyyMmDd(date) - date.setDate(0) - date.setDate(1) - period.startDate = formatYyyyMmDd(date) - const monthStart = getMonthName(date.getMonth()) - const monthEnd = getMonthName(date.getMonth() + 1) - const fullYear = date.getFullYear() - period.name = `${monthStart} - ${monthEnd} ${fullYear}` - period.iso = `${year}0${index}B` - period.id = period.iso - periods.push(period) - date.setDate(0) - - index-- - } - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods : periods.reverse() - // Bi-months are collected backwards. If isReverse is true, then do nothing. Else reverse to correct order and return. - - return periods + return getPeriods({ + periodType: 'MONTHLY', + config, + fnFilter, + periodSettings, + }) } } -const getQuarterlyPeriodType = (formatYyyyMmDd, fnFilter) => { +const getBiMonthlyPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = new Date(`31 Dec ${year}`) - let quarter = 4 - - while (date.getFullYear() === year) { - const period = {} - period.endDate = formatYyyyMmDd(date) - date.setDate(0) - date.setDate(0) - date.setDate(1) - period.startDate = formatYyyyMmDd(date) - const monthStart = getMonthName(date.getMonth()) - const monthEnd = getMonthName(date.getMonth() + 2) - const fullYear = date.getFullYear() - period.name = `${monthStart} - ${monthEnd} ${fullYear}` - period.iso = `${year}Q${quarter}` - period.id = period.iso - periods.push(period) - date.setDate(0) - quarter -= 1 - } - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods : periods.reverse() - // Quarters are collected backwards. If isReverse is true, then do nothing. Else reverse to correct order and return. - - return periods + return getPeriods({ + periodType: 'BIMONTHLY', + config, + fnFilter, + periodSettings, + }) } } -const getSixMonthlyPeriodType = (fnFilter) => { +const getQuarterlyPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - - let period = {} - period.startDate = `${year}-01-01` - period.endDate = `${year}-06-30` - period.name = `${getMonthName(0)} - ${getMonthName(5)} ${year}` - period.iso = `${year}S1` - period.id = period.iso - periods.push(period) - - period = {} - period.startDate = `${year}-07-01` - period.endDate = `${year}-12-31` - period.name = `${getMonthName(6)} - ${getMonthName(11)} ${year}` - period.iso = `${year}S2` - period.id = period.iso - periods.push(period) - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods.reverse() : periods - - return periods + return getPeriods({ + periodType: 'QUARTERLY', + config, + fnFilter, + periodSettings, + }) } } -const getSixMonthlyAprilPeriodType = (fnFilter) => { +const getSixMonthlyPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - - let period = {} - period.startDate = `${year}-04-01` - period.endDate = `${year}-09-30` - period.name = `${getMonthName(3)} - ${getMonthName(8)} ${year}` - period.iso = `${year}AprilS1` - period.id = period.iso - periods.push(period) - - period = {} - period.startDate = `${year}-10-01` - period.endDate = `${year + 1}-03-31` - period.name = `${getMonthName(9)} ${year} - ${getMonthName(2)} ${ - year + 1 - }` - period.iso = `${year}AprilS2` - period.id = period.iso - periods.push(period) - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods.reverse() : periods - - return periods + return getPeriods({ + periodType: 'SIXMONTHLY', + config, + fnFilter, + periodSettings, + }) } } -const getYearlyPeriodType = (formatYyyyMmDd, fnFilter) => { +const getSixMonthlyAprilPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = new Date(`31 Dec ${year}`) - - while (year - date.getFullYear() < 10) { - const period = {} - period.endDate = formatYyyyMmDd(date) - date.setMonth(0, 1) - period.startDate = formatYyyyMmDd(date) - const dateString = date.getFullYear().toString() - period.name = dateString - period.iso = date.getFullYear().toString() - period.id = period.iso.toString() - periods.push(period) - date.setDate(0) - } - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods : periods.reverse() - // Years are collected backwards. If isReverse is true, then do nothing. Else reverse to correct order and return. - - return periods + return getPeriods({ + periodType: 'SIXMONTHLYAPR', + config, + fnFilter, + periodSettings, + }) } } -const getFinancialOctoberPeriodType = (formatYyyyMmDd, fnFilter) => { +const getYearlyPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = new Date(`30 Sep ${year + 1}`) - - for (let i = 0; i < 10; i++) { - const period = {} - period.endDate = formatYyyyMmDd(date) - date.setYear(date.getFullYear() - 1) - date.setDate(date.getDate() + 1) - period.startDate = formatYyyyMmDd(date) - const yearStart = date.getFullYear() - const yearEnd = date.getFullYear() + 1 - period.name = `${getMonthName(9)} ${yearStart} - ${getMonthName( - 8 - )} ${yearEnd}` - period.id = `${date.getFullYear()}Oct` - periods.push(period) - date.setDate(date.getDate() - 1) - } - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods : periods.reverse() - // FinancialOctober periods are collected backwards. If isReverse is true, then do nothing. Else reverse to correct order and return. - - return periods + return getPeriods({ + periodType: 'YEARLY', + config, + fnFilter, + periodSettings, + }) } } -const getFinancialNovemberPeriodType = (formatYyyyMmDd, fnFilter) => { +const getFinancialOctoberPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = new Date(`31 Oct ${year + 1}`) - - for (let i = 0; i < 10; i++) { - const period = {} - period.endDate = formatYyyyMmDd(date) - date.setYear(date.getFullYear() - 1) - date.setDate(date.getDate() + 1) - period.startDate = formatYyyyMmDd(date) - const yearStart = date.getFullYear() - const yearEnd = date.getFullYear() + 1 - period.name = `${getMonthName(10)} ${yearStart} - ${getMonthName( - 9 - )} ${yearEnd}` - period.id = `${date.getFullYear()}Nov` - periods.push(period) - date.setDate(date.getDate() - 1) - } - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods : periods.reverse() - // FinancialNovember periods are collected backwards. If isReverse is true, then do nothing. Else reverse to correct order and return. - - return periods + return getPeriods({ + periodType: 'FYOCT', + config, + fnFilter, + periodSettings, + }) } } -const getFinancialJulyPeriodType = (formatYyyyMmDd, fnFilter) => { +const getFinancialNovemberPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = new Date(`30 Jun ${year + 1}`) - - for (let i = 0; i < 10; i++) { - const period = {} - period.endDate = formatYyyyMmDd(date) - date.setYear(date.getFullYear() - 1) - date.setDate(date.getDate() + 1) - period.startDate = formatYyyyMmDd(date) - const yearStart = date.getFullYear() - const yearEnd = date.getFullYear() + 1 - period.name = `${getMonthName(6)} ${yearStart} - ${getMonthName( - 5 - )} ${yearEnd}` - period.id = `${date.getFullYear()}July` - periods.push(period) - date.setDate(date.getDate() - 1) - } - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods : periods.reverse() - // FinancialJuly periods are collected backwards. If isReverse is true, then do nothing. Else reverse to correct order and return. - - return periods + return getPeriods({ + periodType: 'FYNOV', + config, + fnFilter, + periodSettings, + }) } } -const getFinancialAprilPeriodType = (formatYyyyMmDd, fnFilter) => { +const getFinancialJulyPeriodType = (fnFilter, periodSettings) => { return (config) => { - let periods = [] - const offset = parseInt(config.offset, 10) - const isFilter = config.filterFuturePeriods - const isReverse = config.reversePeriods - const year = new Date(Date.now()).getFullYear() + offset - const date = new Date(`31 Mar ${year + 1}`) - - for (let i = 0; i < 10; i++) { - const period = {} - period.endDate = formatYyyyMmDd(date) - date.setYear(date.getFullYear() - 1) - date.setDate(date.getDate() + 1) - period.startDate = formatYyyyMmDd(date) - const yearStart = date.getFullYear() - const yearEnd = date.getFullYear() + 1 - period.name = `${getMonthName(3)} ${yearStart} - ${getMonthName( - 2 - )} ${yearEnd}` - period.id = `${date.getFullYear()}April` - periods.push(period) - date.setDate(date.getDate() - 1) - } - - periods = isFilter ? fnFilter(periods) : periods - periods = isReverse ? periods : periods.reverse() - // FinancialApril periods are collected backwards. If isReverse is true, then do nothing. Else reverse to correct order and return. - - return periods + return getPeriods({ + periodType: 'FYJUL', + config, + fnFilter, + periodSettings, + }) } } -const formatYyyyMmDd = (date) => { - const y = date.getFullYear() - let m = String(date.getMonth() + 1) - let d = String(date.getDate()) - - m = m.length < 2 ? `0${m}` : m - d = d.length < 2 ? `0${d}` : d - - return `${y}-${m}-${d}` +const getFinancialAprilPeriodType = (fnFilter, periodSettings) => { + return (config) => { + return getPeriods({ + periodType: 'FYAPR', + config, + fnFilter, + periodSettings, + }) + } } const filterFuturePeriods = (periods) => { @@ -559,128 +230,152 @@ const filterFuturePeriods = (periods) => { return array } -const getOptions = () => [ - { - id: DAILY, - getPeriods: getDailyPeriodType(formatYyyyMmDd, filterFuturePeriods), - name: i18n.t('Daily'), - }, - { - id: WEEKLY, - getPeriods: getWeeklyPeriodType( - formatYyyyMmDd, - { shortName: '', startDay: 1 }, - filterFuturePeriods - ), - name: i18n.t('Weekly'), - }, - { - id: WEEKLYWED, - getPeriods: getWeeklyPeriodType( - formatYyyyMmDd, - { shortName: 'Wed', startDay: 3 }, - filterFuturePeriods - ), - name: i18n.t('Weekly (Start Wednesday)'), - }, - { - id: WEEKLYTHU, - getPeriods: getWeeklyPeriodType( - formatYyyyMmDd, - { shortName: 'Thu', startDay: 4 }, - filterFuturePeriods - ), - name: i18n.t('Weekly (Start Thursday)'), - }, - { - id: WEEKLYSAT, - getPeriods: getWeeklyPeriodType( - formatYyyyMmDd, - { shortName: 'Sat', startDay: 6 }, - filterFuturePeriods - ), - name: i18n.t('Weekly (Start Saturday)'), - }, - { - id: WEEKLYSUN, - getPeriods: getWeeklyPeriodType( - formatYyyyMmDd, - { shortName: 'Sun', startDay: 7 }, - filterFuturePeriods - ), - name: i18n.t('Weekly (Start Sunday)'), - }, - { - id: BIWEEKLY, - getPeriods: getBiWeeklyPeriodType(formatYyyyMmDd, filterFuturePeriods), - name: i18n.t('Bi-weekly'), - }, - { - id: MONTHLY, - getPeriods: getMonthlyPeriodType(formatYyyyMmDd, filterFuturePeriods), - name: i18n.t('Monthly'), - }, - { - id: BIMONTHLY, - getPeriods: getBiMonthlyPeriodType(formatYyyyMmDd, filterFuturePeriods), - name: i18n.t('Bi-monthly'), - }, - { - id: QUARTERLY, - getPeriods: getQuarterlyPeriodType(formatYyyyMmDd, filterFuturePeriods), - name: i18n.t('Quarterly'), - }, - { - id: SIXMONTHLY, - getPeriods: getSixMonthlyPeriodType(filterFuturePeriods), - name: i18n.t('Six-monthly'), - }, - { - id: SIXMONTHLYAPR, - getPeriods: getSixMonthlyAprilPeriodType(filterFuturePeriods), - name: i18n.t('Six-monthly April'), - }, - { - id: YEARLY, - getPeriods: getYearlyPeriodType(formatYyyyMmDd, filterFuturePeriods), - name: i18n.t('Yearly'), - }, - { - id: FYNOV, - getPeriods: getFinancialNovemberPeriodType( - formatYyyyMmDd, - filterFuturePeriods - ), - name: i18n.t('Financial year (Start November)'), - }, - { - id: FYOCT, - getPeriods: getFinancialOctoberPeriodType( - formatYyyyMmDd, - filterFuturePeriods - ), - name: i18n.t('Financial year (Start October)'), - }, - { - id: FYJUL, - getPeriods: getFinancialJulyPeriodType( - formatYyyyMmDd, - filterFuturePeriods - ), - name: i18n.t('Financial year (Start July)'), - }, - { - id: FYAPR, - getPeriods: getFinancialAprilPeriodType( - formatYyyyMmDd, - filterFuturePeriods - ), - name: i18n.t('Financial year (Start April)'), - }, -] - -export const getFixedPeriodsOptionsById = (id) => - getOptions().find((option) => option.id === id) +const getOptions = (periodSettings) => { + return [ + { + id: DAILY, + getPeriods: getDailyPeriodType(filterFuturePeriods, periodSettings), + name: i18n.t('Daily'), + }, + { + id: WEEKLY, + getPeriods: getWeeklyPeriodType( + { startDay: 1 }, + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Weekly'), + }, + { + id: WEEKLYWED, + getPeriods: getWeeklyPeriodType( + { startDay: 3 }, + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Weekly (Start Wednesday)'), + }, + { + id: WEEKLYTHU, + getPeriods: getWeeklyPeriodType( + { startDay: 4 }, + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Weekly (Start Thursday)'), + }, + { + id: WEEKLYSAT, + getPeriods: getWeeklyPeriodType( + { startDay: 6 }, + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Weekly (Start Saturday)'), + }, + { + id: WEEKLYSUN, + getPeriods: getWeeklyPeriodType( + { startDay: 7 }, + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Weekly (Start Sunday)'), + }, + { + id: BIWEEKLY, + getPeriods: getBiWeeklyPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Bi-weekly'), + }, + { + id: MONTHLY, + getPeriods: getMonthlyPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Monthly'), + }, + { + id: BIMONTHLY, + getPeriods: getBiMonthlyPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Bi-monthly'), + }, + { + id: QUARTERLY, + getPeriods: getQuarterlyPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Quarterly'), + }, + { + id: SIXMONTHLY, + getPeriods: getSixMonthlyPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Six-monthly'), + }, + { + id: SIXMONTHLYAPR, + getPeriods: getSixMonthlyAprilPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Six-monthly April'), + }, + { + id: YEARLY, + getPeriods: getYearlyPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Yearly'), + }, + { + id: FYNOV, + getPeriods: getFinancialNovemberPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Financial year (Start November)'), + }, + { + id: FYOCT, + getPeriods: getFinancialOctoberPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Financial year (Start October)'), + }, + { + id: FYJUL, + getPeriods: getFinancialJulyPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Financial year (Start July)'), + }, + { + id: FYAPR, + getPeriods: getFinancialAprilPeriodType( + filterFuturePeriods, + periodSettings + ), + name: i18n.t('Financial year (Start April)'), + }, + ] +} + +export const getFixedPeriodsOptionsById = (id, periodSettings) => { + return getOptions(periodSettings).find((option) => option.id === id) +} export const getFixedPeriodsOptions = () => getOptions() diff --git a/yarn.lock b/yarn.lock index 989427829..317dc6d62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2202,6 +2202,14 @@ markdown-it "^8.4.2" prop-types "^15.6.2" +"@dhis2/multi-calendar-dates@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@dhis2/multi-calendar-dates/-/multi-calendar-dates-1.0.0.tgz#bf7f49aecdffa9781837a5d60d56a094b74ab4df" + integrity sha512-IB9a+feuS6yE4lpZj/eZ9uBmpYI7Hxitl2Op0JjoRL4tP+p6uw4ns9cjoSdUeIU9sOAxVZV7oQqSyIw+9P6YjQ== + dependencies: + "@js-temporal/polyfill" "^0.4.2" + classnames "^2.3.2" + "@dhis2/prop-types@^3.1.2": version "3.1.2" resolved "https://registry.yarnpkg.com/@dhis2/prop-types/-/prop-types-3.1.2.tgz#65b8ad2da8cd2f72bc8b951049a6c9d1b97af3e9" @@ -2936,6 +2944,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@js-temporal/polyfill@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@js-temporal/polyfill/-/polyfill-0.4.3.tgz#e8f8cf86745eb5050679c46a5ebedb9a9cc1f09b" + integrity sha512-6Fmjo/HlkyVCmJzAPnvtEWlcbQUSRhi8qlN9EtJA/wP7FqXsevLLrlojR44kzNzrRkpf7eDJ+z7b4xQD/Ycypw== + dependencies: + jsbi "^4.1.0" + tslib "^2.3.1" + "@juggle/resize-observer@^3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0" @@ -6980,6 +6996,11 @@ classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== +classnames@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" + integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== + clean-css@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" @@ -12656,6 +12677,11 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +jsbi@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-4.3.0.tgz#b54ee074fb6fcbc00619559305c8f7e912b04741" + integrity sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g== + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -18702,6 +18728,11 @@ tslib@^2.0.0, tslib@^2.0.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== +tslib@^2.3.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" From 21fb60d6bb713b9a8e1be0533677530a31e0fa17 Mon Sep 17 00:00:00 2001 From: Mozafar Date: Fri, 24 Feb 2023 12:09:46 +0000 Subject: [PATCH 033/285] chore: remove duplicate from yarn.lock (#1436) --- yarn.lock | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 317dc6d62..39f9ed179 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6991,12 +6991,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" - integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== - -classnames@^2.3.2: +classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== From b7aaec6f47bcd18ec3964af49b4739bc0ec79d5a Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 24 Feb 2023 12:14:21 +0000 Subject: [PATCH 034/285] chore(release): cut 24.9.0 [skip ci] # [24.9.0](https://github.com/dhis2/analytics/compare/v24.8.0...v24.9.0) (2023-02-24) ### Features * merge multi-calendar support from alpha to master ([#1434](https://github.com/dhis2/analytics/issues/1434)) ([3cdac8f](https://github.com/dhis2/analytics/commit/3cdac8fdefc7a15a3fef0940183ca92f03b0f57c)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54dd093c9..8f65ac0e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [24.9.0](https://github.com/dhis2/analytics/compare/v24.8.0...v24.9.0) (2023-02-24) + + +### Features + +* merge multi-calendar support from alpha to master ([#1434](https://github.com/dhis2/analytics/issues/1434)) ([3cdac8f](https://github.com/dhis2/analytics/commit/3cdac8fdefc7a15a3fef0940183ca92f03b0f57c)) + # [24.9.0-alpha.1](https://github.com/dhis2/analytics/compare/v24.8.0...v24.9.0-alpha.1) (2023-02-23) diff --git a/package.json b/package.json index 628ace6a7..7c22746d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.9.0-alpha.1", + "version": "24.9.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 35e18724ff88a09d13deedb54e897ad2506c4f08 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Fri, 10 Mar 2023 15:42:23 +0100 Subject: [PATCH 035/285] fix: spacing in Rename and SaveAs dialogs (#1447) --- i18n/en.pot | 46 +--------------------- src/components/FileMenu/FileMenu.styles.js | 8 ++++ src/components/FileMenu/RenameDialog.js | 34 +++++++++------- src/components/FileMenu/SaveAsDialog.js | 34 +++++++++------- 4 files changed, 48 insertions(+), 74 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 3e2ad820f..9971e3065 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: 2022-12-29T10:50:42.111Z\n" -"PO-Revision-Date: 2022-12-29T10:50:42.111Z\n" +"POT-Creation-Date: 2023-03-10T10:41:22.719Z\n" +"PO-Revision-Date: 2023-03-10T10:41:22.719Z\n" msgid "view only" msgstr "view only" @@ -593,48 +593,6 @@ msgstr "Selected Periods" msgid "No periods selected" msgstr "No periods selected" -msgid "January" -msgstr "January" - -msgid "February" -msgstr "February" - -msgid "March" -msgstr "March" - -msgid "April" -msgstr "April" - -msgid "May" -msgstr "May" - -msgid "June" -msgstr "June" - -msgid "July" -msgstr "July" - -msgid "August" -msgstr "August" - -msgid "September" -msgstr "September" - -msgid "October" -msgstr "October" - -msgid "November" -msgstr "November" - -msgid "December" -msgstr "December" - -msgid "Week {{weekNumber}}" -msgstr "Week {{weekNumber}}" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "Bi-Week {{biWeekNumber}}" - msgid "Daily" msgstr "Daily" diff --git a/src/components/FileMenu/FileMenu.styles.js b/src/components/FileMenu/FileMenu.styles.js index de46487c4..68d3929ad 100644 --- a/src/components/FileMenu/FileMenu.styles.js +++ b/src/components/FileMenu/FileMenu.styles.js @@ -42,3 +42,11 @@ export const fileMenuStyles = css` outline: none; } ` + +export const modalStyles = css` + .modal-content { + display: flex; + flex-direction: column; + gap: ${spacers.dp12}; + } +` diff --git a/src/components/FileMenu/RenameDialog.js b/src/components/FileMenu/RenameDialog.js index 10e0b19f7..9692e5838 100644 --- a/src/components/FileMenu/RenameDialog.js +++ b/src/components/FileMenu/RenameDialog.js @@ -12,6 +12,7 @@ import { import PropTypes from 'prop-types' import React, { useMemo, useState } from 'react' import i18n from '../../locales/index.js' +import { modalStyles } from './FileMenu.styles.js' import { supportedFileTypes, endpointFromFileType, @@ -30,7 +31,7 @@ export const RenameDialog = ({ type, object, onClose, onRename, onError }) => { const [name, setName] = useState(object.name) const [description, setDescription] = useState(object.description) - const mutation = useMemo(() => getMutation(type), []) + const mutation = useMemo(() => getMutation(type), [type]) const [mutate, { loading }] = useDataMutation(mutation, { onError: (error) => { onError(error) @@ -52,26 +53,29 @@ export const RenameDialog = ({ type, object, onClose, onRename, onError }) => { return ( + {i18n.t('Rename {{fileType}}', { fileType: labelForFileType(type), })} - setName(value)} - /> - setDescription(value)} - /> +
    + setName(value)} + /> + setDescription(value)} + /> +
    diff --git a/src/components/FileMenu/SaveAsDialog.js b/src/components/FileMenu/SaveAsDialog.js index f6eddff7d..26a7f2894 100644 --- a/src/components/FileMenu/SaveAsDialog.js +++ b/src/components/FileMenu/SaveAsDialog.js @@ -11,6 +11,7 @@ import { import PropTypes from 'prop-types' import React, { useState } from 'react' import i18n from '../../locales/index.js' +import { modalStyles } from './FileMenu.styles.js' import { supportedFileTypes, labelForFileType } from './utils.js' const NAME_MAXLENGTH = 230 @@ -37,27 +38,30 @@ export const SaveAsDialog = ({ type, object, onClose, onSaveAs }) => { return ( + {i18n.t('Save {{fileType}} as', { fileType: labelForFileType(type), })} - - setName(value.substring(0, NAME_MAXLENGTH)) - } - dataTest="file-menu-saveas-modal-name" - /> - setDescription(value)} - dataTest="file-menu-saveas-modal-description" - /> +
    + + setName(value.substring(0, NAME_MAXLENGTH)) + } + dataTest="file-menu-saveas-modal-name" + /> + setDescription(value)} + dataTest="file-menu-saveas-modal-description" + /> +
    From d92f8765a3f0811071c84523df9f941d54e84d26 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 10 Mar 2023 14:46:51 +0000 Subject: [PATCH 036/285] chore(release): cut 24.9.1 [skip ci] ## [24.9.1](https://github.com/dhis2/analytics/compare/v24.9.0...v24.9.1) (2023-03-10) ### Bug Fixes * spacing in Rename and SaveAs dialogs ([#1447](https://github.com/dhis2/analytics/issues/1447)) ([35e1872](https://github.com/dhis2/analytics/commit/35e18724ff88a09d13deedb54e897ad2506c4f08)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f65ac0e5..0d7885ee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.9.1](https://github.com/dhis2/analytics/compare/v24.9.0...v24.9.1) (2023-03-10) + + +### Bug Fixes + +* spacing in Rename and SaveAs dialogs ([#1447](https://github.com/dhis2/analytics/issues/1447)) ([35e1872](https://github.com/dhis2/analytics/commit/35e18724ff88a09d13deedb54e897ad2506c4f08)) + # [24.9.0](https://github.com/dhis2/analytics/compare/v24.8.0...v24.9.0) (2023-02-24) diff --git a/package.json b/package.json index 7c22746d6..f77e637e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.9.0", + "version": "24.9.1", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From df4bb97f52d9bc03555621beadfe929721500e31 Mon Sep 17 00:00:00 2001 From: Kai Vandivier <49666798+KaiVandivier@users.noreply.github.com> Date: Tue, 14 Mar 2023 12:04:58 +0100 Subject: [PATCH 037/285] fix: use connection status to handle local offline conditions (#1443) --- package.json | 2 +- src/components/OfflineTooltip.js | 4 +-- .../TranslationModalActions.js | 4 +-- yarn.lock | 36 ++++++++++++++++++- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index f77e637e2..bf927ead9 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "validate-push": "yarn test" }, "devDependencies": { - "@dhis2/app-runtime": "^3.4.4", + "@dhis2/app-runtime": "^3.9.0", "@dhis2/cli-app-scripts": "^9.0.1", "@dhis2/cli-style": "^10.4.1", "@dhis2/d2-i18n": "^1.1.0", diff --git a/src/components/OfflineTooltip.js b/src/components/OfflineTooltip.js index 633e2011e..85b548532 100644 --- a/src/components/OfflineTooltip.js +++ b/src/components/OfflineTooltip.js @@ -1,4 +1,4 @@ -import { useOnlineStatus } from '@dhis2/app-runtime' +import { useDhis2ConnectionStatus } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' import { Tooltip } from '@dhis2/ui' import cx from 'classnames' @@ -12,7 +12,7 @@ const OfflineTooltip = ({ content, children, }) => { - const { offline } = useOnlineStatus() + const { isDisconnected: offline } = useDhis2ConnectionStatus() const notAllowed = disabled || (disabledWhenOffline && offline) diff --git a/src/components/TranslationDialog/TranslationModal/TranslationModalActions.js b/src/components/TranslationDialog/TranslationModal/TranslationModalActions.js index 8c864f2dc..71d2cd582 100644 --- a/src/components/TranslationDialog/TranslationModal/TranslationModalActions.js +++ b/src/components/TranslationDialog/TranslationModal/TranslationModalActions.js @@ -1,4 +1,4 @@ -import { useOnlineStatus } from '@dhis2/app-runtime' +import { useDhis2ConnectionStatus } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' import { Button, ButtonStrip, ModalActions } from '@dhis2/ui' import PropTypes from 'prop-types' @@ -29,7 +29,7 @@ export const TranslationModalActions = ({ saveInProgress, saveButtonDisabled, }) => { - const { offline } = useOnlineStatus() + const { isDisconnected: offline } = useDhis2ConnectionStatus() return ( diff --git a/yarn.lock b/yarn.lock index 39f9ed179..fc1996f75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2051,7 +2051,7 @@ "@dhis2/pwa" "9.0.1" moment "^2.24.0" -"@dhis2/app-runtime@^3.2.3", "@dhis2/app-runtime@^3.4.4": +"@dhis2/app-runtime@^3.2.3": version "3.4.4" resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.4.4.tgz#87202b82babe6e4b3f29f784e02a3b295960359c" integrity sha512-GTj0H6TLVcw6zo0Vf9aDuPJbsjFfbNWN/SSC9/GF2a0foXdKVSCoc4H5+gW/RhBgYvaSZYwQBA6L+qBaQj7c0Q== @@ -2061,16 +2061,36 @@ "@dhis2/app-service-data" "3.4.4" "@dhis2/app-service-offline" "3.4.4" +"@dhis2/app-runtime@^3.9.0": + version "3.9.0" + resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.9.0.tgz#c7e295fd0a68fac976a930bc77105206ded0b61a" + integrity sha512-n0S4pbyvK7FnBQFMONGrhR9YYavBQI+mQLHfCX/vtvOyeoioBUNIinuQlGysuLMEkSVaK5OjV40rvTMzdxF2kQ== + dependencies: + "@dhis2/app-service-alerts" "3.9.0" + "@dhis2/app-service-config" "3.9.0" + "@dhis2/app-service-data" "3.9.0" + "@dhis2/app-service-offline" "3.9.0" + "@dhis2/app-service-alerts@3.4.4": version "3.4.4" resolved "https://registry.yarnpkg.com/@dhis2/app-service-alerts/-/app-service-alerts-3.4.4.tgz#ccd8848f416e976a01ea11bbbd6e1ae873136777" integrity sha512-o2DHlZIHxJQ4XwJ9uIr0UpMTi3ZK0W6u+FkSnPGwrvF+MCFsxA0+pjN7PfBo4acCQHPBwTZzcyq0WYiEAJM7OA== +"@dhis2/app-service-alerts@3.9.0": + version "3.9.0" + resolved "https://registry.yarnpkg.com/@dhis2/app-service-alerts/-/app-service-alerts-3.9.0.tgz#48d3805676e75ee58104fea4f76cfa779335444e" + integrity sha512-z2eZxm/pxrmFbisbK7/qJKtif2CNWJjaaAH5rfrs5OIajlHy3rO37vSaTQHWv+xWvZFQrs2Op2InxzG0qh5ncA== + "@dhis2/app-service-config@3.4.4": version "3.4.4" resolved "https://registry.yarnpkg.com/@dhis2/app-service-config/-/app-service-config-3.4.4.tgz#b5773138183bf339055957bdd1d24b54fecc404e" integrity sha512-QCUfptfmwh3Xs16rr7DDTgMBuQIcJpt2CZEc6XROPYMgBBvN3sncA9Rzb7Jf+xblZwUWi0xuZ7PKs49FFS+5kQ== +"@dhis2/app-service-config@3.9.0": + version "3.9.0" + resolved "https://registry.yarnpkg.com/@dhis2/app-service-config/-/app-service-config-3.9.0.tgz#8dc59d8de246f54057c0c685d5f94b4cbade6f73" + integrity sha512-OuRn2mJGrQQ8QIC+oIVYYpclB4LErRK2wtsuy/cXLfRbeUti1qWIh110rgd1hnTx+BgRCs5s3NWdIQxS4hYGIQ== + "@dhis2/app-service-data@3.4.4": version "3.4.4" resolved "https://registry.yarnpkg.com/@dhis2/app-service-data/-/app-service-data-3.4.4.tgz#028236463b82f998ff7bd44e811153f6c1408507" @@ -2078,6 +2098,13 @@ dependencies: react-query "^3.13.11" +"@dhis2/app-service-data@3.9.0": + version "3.9.0" + resolved "https://registry.yarnpkg.com/@dhis2/app-service-data/-/app-service-data-3.9.0.tgz#37f528b5f7f589cbab8dcc7f997c1668bc6566a9" + integrity sha512-/FJgJhL6YGtIVNX5oaNmavkGmimrVHQsS8ueeUO4FvTjYXGlnnN3IuxypQcy/x4yiUyigbPgFJRnbC1J2af2fg== + dependencies: + react-query "^3.13.11" + "@dhis2/app-service-offline@3.4.4": version "3.4.4" resolved "https://registry.yarnpkg.com/@dhis2/app-service-offline/-/app-service-offline-3.4.4.tgz#49d460b22c8eb8357aeef05aee8ed6f8a5c3a953" @@ -2085,6 +2112,13 @@ dependencies: lodash "^4.17.21" +"@dhis2/app-service-offline@3.9.0": + version "3.9.0" + resolved "https://registry.yarnpkg.com/@dhis2/app-service-offline/-/app-service-offline-3.9.0.tgz#fe4f4a91a1da77554965f6a5fe6f6951d4c467f4" + integrity sha512-0q5zl0vw+a47Ab2qgu6hsZY5ybnH/ea43Vkk4aXYdgcf57xB8ck9DkIcNbc2e1+k9FhvimipxsgTZSbEA/8hJA== + dependencies: + lodash "^4.17.21" + "@dhis2/app-shell@9.0.1": version "9.0.1" resolved "https://registry.yarnpkg.com/@dhis2/app-shell/-/app-shell-9.0.1.tgz#55cf0e21bd9c6f9156592bd8ccbc5e368f562425" From 7c5c2a6870f14f9a18c155ee7f7817a3055b6205 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Tue, 14 Mar 2023 13:01:26 +0100 Subject: [PATCH 038/285] fix: prevent wrong URL with duplicated path parts DHIS2-14789 (#1449) Also added a copy to clipboard button. --- src/components/FileMenu/GetLinkDialog.js | 22 ++++++++++++++----- .../FileMenu/GetLinkDialog.styles.js | 10 +++++++++ .../FileMenu/__tests__/GetLinkDialog.spec.js | 12 ++++++++-- 3 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/components/FileMenu/GetLinkDialog.styles.js diff --git a/src/components/FileMenu/GetLinkDialog.js b/src/components/FileMenu/GetLinkDialog.js index d78212456..6b96f4a39 100644 --- a/src/components/FileMenu/GetLinkDialog.js +++ b/src/components/FileMenu/GetLinkDialog.js @@ -1,27 +1,39 @@ +import { useConfig } from '@dhis2/app-runtime' import { Modal, ModalContent, ModalActions, ButtonStrip, Button, + IconCopy24, } from '@dhis2/ui' import PropTypes from 'prop-types' import React from 'react' import i18n from '../../locales/index.js' +import { styles } from './GetLinkDialog.styles.js' import { supportedFileTypes, appPathFor } from './utils.js' export const GetLinkDialog = ({ type, id, onClose }) => { + const { baseUrl } = useConfig() + // TODO simply use href from the visualization object? - const appUrl = new URL( - appPathFor(type, id), - `${window.location.origin}${window.location.pathname}` - ) + const appUrl = new URL(appPathFor(type, id), baseUrl) return ( +

    {i18n.t('Open in this app')}

    - {appUrl.href} +
    + {appUrl.href} +
    diff --git a/src/components/FileMenu/GetLinkDialog.styles.js b/src/components/FileMenu/GetLinkDialog.styles.js new file mode 100644 index 000000000..65bfd8932 --- /dev/null +++ b/src/components/FileMenu/GetLinkDialog.styles.js @@ -0,0 +1,10 @@ +import { spacers } from '@dhis2/ui' +import css from 'styled-jsx/css' + +export const styles = css` + .link-container { + display: flex; + align-items: center; + gap: ${spacers.dp12}; + } +` diff --git a/src/components/FileMenu/__tests__/GetLinkDialog.spec.js b/src/components/FileMenu/__tests__/GetLinkDialog.spec.js index 3192492f0..2435b866e 100644 --- a/src/components/FileMenu/__tests__/GetLinkDialog.spec.js +++ b/src/components/FileMenu/__tests__/GetLinkDialog.spec.js @@ -4,6 +4,12 @@ import React from 'react' import { GetLinkDialog } from '../GetLinkDialog.js' import { appPathFor } from '../utils.js' +const testBaseUrl = 'http://test.tld/test' + +jest.mock('@dhis2/app-runtime', () => ({ + useConfig: () => ({ baseUrl: testBaseUrl }), +})) + describe('The FileMenu - GetLinkDialog component', () => { let shallowGetLinkDialog let props @@ -33,11 +39,13 @@ describe('The FileMenu - GetLinkDialog component', () => { it('renders a tag containing the type and id props', () => { const href = getGetLinkDialogComponent(props).find('a').prop('href') - expect(href).toMatch(appPathFor(props.type, props.id)) + expect(href).toMatch( + new URL(appPathFor(props.type, props.id), testBaseUrl).href + ) }) it('calls the onClose callback when the Close button is clicked', () => { - getGetLinkDialogComponent(props).find(Button).first().simulate('click') + getGetLinkDialogComponent(props).find(Button).at(1).simulate('click') expect(onClose).toHaveBeenCalled() }) From 85781efeb3f347eabb70ff0db327f888dfcb3da3 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Tue, 14 Mar 2023 13:36:54 +0100 Subject: [PATCH 039/285] fix: deduplicate yarn.lock (#1450) --- yarn.lock | 43 ++----------------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/yarn.lock b/yarn.lock index fc1996f75..6d7f4cbab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2051,17 +2051,7 @@ "@dhis2/pwa" "9.0.1" moment "^2.24.0" -"@dhis2/app-runtime@^3.2.3": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.4.4.tgz#87202b82babe6e4b3f29f784e02a3b295960359c" - integrity sha512-GTj0H6TLVcw6zo0Vf9aDuPJbsjFfbNWN/SSC9/GF2a0foXdKVSCoc4H5+gW/RhBgYvaSZYwQBA6L+qBaQj7c0Q== - dependencies: - "@dhis2/app-service-alerts" "3.4.4" - "@dhis2/app-service-config" "3.4.4" - "@dhis2/app-service-data" "3.4.4" - "@dhis2/app-service-offline" "3.4.4" - -"@dhis2/app-runtime@^3.9.0": +"@dhis2/app-runtime@^3.2.3", "@dhis2/app-runtime@^3.9.0": version "3.9.0" resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.9.0.tgz#c7e295fd0a68fac976a930bc77105206ded0b61a" integrity sha512-n0S4pbyvK7FnBQFMONGrhR9YYavBQI+mQLHfCX/vtvOyeoioBUNIinuQlGysuLMEkSVaK5OjV40rvTMzdxF2kQ== @@ -2071,33 +2061,16 @@ "@dhis2/app-service-data" "3.9.0" "@dhis2/app-service-offline" "3.9.0" -"@dhis2/app-service-alerts@3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@dhis2/app-service-alerts/-/app-service-alerts-3.4.4.tgz#ccd8848f416e976a01ea11bbbd6e1ae873136777" - integrity sha512-o2DHlZIHxJQ4XwJ9uIr0UpMTi3ZK0W6u+FkSnPGwrvF+MCFsxA0+pjN7PfBo4acCQHPBwTZzcyq0WYiEAJM7OA== - "@dhis2/app-service-alerts@3.9.0": version "3.9.0" resolved "https://registry.yarnpkg.com/@dhis2/app-service-alerts/-/app-service-alerts-3.9.0.tgz#48d3805676e75ee58104fea4f76cfa779335444e" integrity sha512-z2eZxm/pxrmFbisbK7/qJKtif2CNWJjaaAH5rfrs5OIajlHy3rO37vSaTQHWv+xWvZFQrs2Op2InxzG0qh5ncA== -"@dhis2/app-service-config@3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@dhis2/app-service-config/-/app-service-config-3.4.4.tgz#b5773138183bf339055957bdd1d24b54fecc404e" - integrity sha512-QCUfptfmwh3Xs16rr7DDTgMBuQIcJpt2CZEc6XROPYMgBBvN3sncA9Rzb7Jf+xblZwUWi0xuZ7PKs49FFS+5kQ== - "@dhis2/app-service-config@3.9.0": version "3.9.0" resolved "https://registry.yarnpkg.com/@dhis2/app-service-config/-/app-service-config-3.9.0.tgz#8dc59d8de246f54057c0c685d5f94b4cbade6f73" integrity sha512-OuRn2mJGrQQ8QIC+oIVYYpclB4LErRK2wtsuy/cXLfRbeUti1qWIh110rgd1hnTx+BgRCs5s3NWdIQxS4hYGIQ== -"@dhis2/app-service-data@3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@dhis2/app-service-data/-/app-service-data-3.4.4.tgz#028236463b82f998ff7bd44e811153f6c1408507" - integrity sha512-mJyaSRikY5noXHdpSm3pTPM/ga+6Jp7XgG62J0mSXsVbwYSOJSLQqcponXsTNwaX3IuQHLir83dXe06YjZq70Q== - dependencies: - react-query "^3.13.11" - "@dhis2/app-service-data@3.9.0": version "3.9.0" resolved "https://registry.yarnpkg.com/@dhis2/app-service-data/-/app-service-data-3.9.0.tgz#37f528b5f7f589cbab8dcc7f997c1668bc6566a9" @@ -2105,13 +2078,6 @@ dependencies: react-query "^3.13.11" -"@dhis2/app-service-offline@3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@dhis2/app-service-offline/-/app-service-offline-3.4.4.tgz#49d460b22c8eb8357aeef05aee8ed6f8a5c3a953" - integrity sha512-wBlaMh7rdlr9RQ02q5vADtk9ixFtfI5ywXmxCPiXPBRftahMqwUpGpD88KPIUpoSPRI0V2LYGv0XQm33+gmKnw== - dependencies: - lodash "^4.17.21" - "@dhis2/app-service-offline@3.9.0": version "3.9.0" resolved "https://registry.yarnpkg.com/@dhis2/app-service-offline/-/app-service-offline-3.9.0.tgz#fe4f4a91a1da77554965f6a5fe6f6951d4c467f4" @@ -18752,12 +18718,7 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== -tslib@^2.0.0, tslib@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" - integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== - -tslib@^2.3.1: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.3.1: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== From 7c804282c47f1f02ad7cd880f151121d9b30df75 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 14 Mar 2023 12:41:16 +0000 Subject: [PATCH 040/285] chore(release): cut 24.9.2 [skip ci] ## [24.9.2](https://github.com/dhis2/analytics/compare/v24.9.1...v24.9.2) (2023-03-14) ### Bug Fixes * deduplicate yarn.lock ([#1450](https://github.com/dhis2/analytics/issues/1450)) ([85781ef](https://github.com/dhis2/analytics/commit/85781efeb3f347eabb70ff0db327f888dfcb3da3)) * prevent wrong URL with duplicated path parts DHIS2-14789 ([#1449](https://github.com/dhis2/analytics/issues/1449)) ([7c5c2a6](https://github.com/dhis2/analytics/commit/7c5c2a6870f14f9a18c155ee7f7817a3055b6205)) * use connection status to handle local offline conditions ([#1443](https://github.com/dhis2/analytics/issues/1443)) ([df4bb97](https://github.com/dhis2/analytics/commit/df4bb97f52d9bc03555621beadfe929721500e31)) --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d7885ee4..6d08bc946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [24.9.2](https://github.com/dhis2/analytics/compare/v24.9.1...v24.9.2) (2023-03-14) + + +### Bug Fixes + +* deduplicate yarn.lock ([#1450](https://github.com/dhis2/analytics/issues/1450)) ([85781ef](https://github.com/dhis2/analytics/commit/85781efeb3f347eabb70ff0db327f888dfcb3da3)) +* prevent wrong URL with duplicated path parts DHIS2-14789 ([#1449](https://github.com/dhis2/analytics/issues/1449)) ([7c5c2a6](https://github.com/dhis2/analytics/commit/7c5c2a6870f14f9a18c155ee7f7817a3055b6205)) +* use connection status to handle local offline conditions ([#1443](https://github.com/dhis2/analytics/issues/1443)) ([df4bb97](https://github.com/dhis2/analytics/commit/df4bb97f52d9bc03555621beadfe929721500e31)) + ## [24.9.1](https://github.com/dhis2/analytics/compare/v24.9.0...v24.9.1) (2023-03-10) diff --git a/package.json b/package.json index bf927ead9..97cc9dd9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.9.1", + "version": "24.9.2", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 4d45abb72b9b6c3e2d82e54a3a05abe0fd8b87b7 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Wed, 15 Mar 2023 15:15:14 +0100 Subject: [PATCH 041/285] fix: ensure the base passed to URL is absolute (#1452) This should account for the case where in production baseUrl is ".." --- src/components/FileMenu/GetLinkDialog.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/FileMenu/GetLinkDialog.js b/src/components/FileMenu/GetLinkDialog.js index 6b96f4a39..c1c1a0cf1 100644 --- a/src/components/FileMenu/GetLinkDialog.js +++ b/src/components/FileMenu/GetLinkDialog.js @@ -17,7 +17,9 @@ export const GetLinkDialog = ({ type, id, onClose }) => { const { baseUrl } = useConfig() // TODO simply use href from the visualization object? - const appUrl = new URL(appPathFor(type, id), baseUrl) + const appBaseUrl = new URL(baseUrl, self.location.href) + + const appUrl = new URL(appPathFor(type, id), appBaseUrl) return ( From 86af2725fc454790d7e925b72c732205ffc01659 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 15 Mar 2023 14:21:04 +0000 Subject: [PATCH 042/285] chore(release): cut 24.9.3 [skip ci] ## [24.9.3](https://github.com/dhis2/analytics/compare/v24.9.2...v24.9.3) (2023-03-15) ### Bug Fixes * ensure the base passed to URL is absolute ([#1452](https://github.com/dhis2/analytics/issues/1452)) ([4d45abb](https://github.com/dhis2/analytics/commit/4d45abb72b9b6c3e2d82e54a3a05abe0fd8b87b7)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d08bc946..066db88f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.9.3](https://github.com/dhis2/analytics/compare/v24.9.2...v24.9.3) (2023-03-15) + + +### Bug Fixes + +* ensure the base passed to URL is absolute ([#1452](https://github.com/dhis2/analytics/issues/1452)) ([4d45abb](https://github.com/dhis2/analytics/commit/4d45abb72b9b6c3e2d82e54a3a05abe0fd8b87b7)) + ## [24.9.2](https://github.com/dhis2/analytics/compare/v24.9.1...v24.9.2) (2023-03-14) diff --git a/package.json b/package.json index 97cc9dd9a..3a956da4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.9.2", + "version": "24.9.3", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From d32c7b8b8cce03e634caeb036e3412b8b7bcbf19 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 19 Apr 2023 12:07:53 +0200 Subject: [PATCH 043/285] feat: single value background color change based upon legend (DHIS2-13702) part 2 (#1453) --- .../config/generators/dhis/singleValue.js | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/visualizations/config/generators/dhis/singleValue.js b/src/visualizations/config/generators/dhis/singleValue.js index 1aaa57ada..a9c570ca6 100644 --- a/src/visualizations/config/generators/dhis/singleValue.js +++ b/src/visualizations/config/generators/dhis/singleValue.js @@ -90,14 +90,17 @@ const generateValueSVG = ({ return svgValue } -const generateDashboardItem = (config, { valueColor, noData }) => { +const generateDashboardItem = ( + config, + { valueColor, titleColor, backgroundColor, noData } +) => { const container = document.createElement('div') container.setAttribute( 'style', - 'display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%' + `display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; background-color:${backgroundColor};` ) - const titleStyle = 'font-size: 12px; color: #666;' + const titleStyle = `font-size: 12px; color: ${titleColor || '#666'};` const title = document.createElement('span') title.setAttribute('style', titleStyle) @@ -107,9 +110,13 @@ const generateDashboardItem = (config, { valueColor, noData }) => { container.appendChild(title) } - const subtitle = document.createElement('span') - subtitle.setAttribute('style', titleStyle + ' margin-top: 4px') if (config.subtitle) { + const subtitle = document.createElement('span') + subtitle.setAttribute( + 'style', + titleStyle + ' margin-top: 4px; padding: 0 8px' + ) + subtitle.appendChild(document.createTextNode(config.subtitle)) container.appendChild(subtitle) @@ -154,7 +161,7 @@ const getXFromTextAlign = (textAlign) => { const generateDVItem = ( config, - { valueColor, titleColor, parentEl, fontStyle, noData } + { valueColor, backgroundColor, titleColor, parentEl, fontStyle, noData } ) => { const parentElBBox = parentEl.getBoundingClientRect() @@ -170,6 +177,16 @@ const generateDVItem = ( svg.setAttribute('height', '100%') svg.setAttribute('data-test', 'visualization-container') + if (backgroundColor) { + svg.setAttribute('style', `background-color: ${backgroundColor};`) + + const background = document.createElementNS(svgNS, 'rect') + background.setAttribute('width', '100%') + background.setAttribute('height', '100%') + background.setAttribute('fill', backgroundColor) + svg.appendChild(background) + } + const title = document.createElementNS(svgNS, 'text') const titleFontStyle = mergeFontStyleWithDefault( fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_TITLE], @@ -316,10 +333,10 @@ export default function ( const legendSet = legendOptions && legendSets[0] const legendColor = legendSet && getColorByValueFromLegendSet(legendSet, config.value) - let valueColor, titleColor + let valueColor, titleColor, backgroundColor if (legendColor) { if (legendOptions.style === LEGEND_DISPLAY_STYLE_FILL) { - parentEl.style.background = legendColor + backgroundColor = legendColor valueColor = titleColor = shouldUseContrastColor(legendColor) && colors.white } else { @@ -330,15 +347,22 @@ export default function ( parentEl.style.overflow = 'hidden' parentEl.style.display = 'flex' parentEl.style.justifyContent = 'center' - parentEl.style.borderRadius = spacers.dp8 if (dashboard) { - return generateDashboardItem(config, { valueColor, noData }) + parentEl.style.borderRadius = spacers.dp8 + return generateDashboardItem(config, { + valueColor, + backgroundColor, + noData, + ...(shouldUseContrastColor(legendColor) + ? { titleColor: colors.white } + : {}), + }) } else { - parentEl.style.margin = spacers.dp8 - parentEl.style.height = `calc(100% - (${spacers.dp8} * 2))` + parentEl.style.height = `100%` return generateDVItem(config, { valueColor, + backgroundColor, titleColor, parentEl, fontStyle, From 61b2f626d6261ee690cd5a2a03bfbf6480e7dc7d Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 19 Apr 2023 10:12:57 +0000 Subject: [PATCH 044/285] chore(release): cut 24.10.0 [skip ci] # [24.10.0](https://github.com/dhis2/analytics/compare/v24.9.3...v24.10.0) (2023-04-19) ### Features * single value background color change based upon legend (DHIS2-13702) part 2 ([#1453](https://github.com/dhis2/analytics/issues/1453)) ([d32c7b8](https://github.com/dhis2/analytics/commit/d32c7b8b8cce03e634caeb036e3412b8b7bcbf19)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 066db88f6..3ded2903a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [24.10.0](https://github.com/dhis2/analytics/compare/v24.9.3...v24.10.0) (2023-04-19) + + +### Features + +* single value background color change based upon legend (DHIS2-13702) part 2 ([#1453](https://github.com/dhis2/analytics/issues/1453)) ([d32c7b8](https://github.com/dhis2/analytics/commit/d32c7b8b8cce03e634caeb036e3412b8b7bcbf19)) + ## [24.9.3](https://github.com/dhis2/analytics/compare/v24.9.2...v24.9.3) (2023-03-15) diff --git a/package.json b/package.json index 3a956da4b..93278637c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.9.3", + "version": "24.10.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 245d799d9513e0407e4a6b6ba24c3da15754618d Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 24 Apr 2023 11:11:13 +0200 Subject: [PATCH 045/285] fix: sv size when downloading is a small square (DHIS2-15178) --- src/visualizations/config/generators/dhis/singleValue.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/visualizations/config/generators/dhis/singleValue.js b/src/visualizations/config/generators/dhis/singleValue.js index a9c570ca6..669a046d0 100644 --- a/src/visualizations/config/generators/dhis/singleValue.js +++ b/src/visualizations/config/generators/dhis/singleValue.js @@ -173,8 +173,8 @@ const generateDVItem = ( const svg = document.createElementNS(svgNS, 'svg') svg.setAttribute('xmlns', svgNS) svg.setAttribute('viewBox', `0 0 ${width} ${height}`) - svg.setAttribute('width', '100%') - svg.setAttribute('height', '100%') + svg.setAttribute('width', width) + svg.setAttribute('height', height) svg.setAttribute('data-test', 'visualization-container') if (backgroundColor) { From 16ef1f6eb0edf1757399f7aae994df0de439eb81 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 24 Apr 2023 09:15:43 +0000 Subject: [PATCH 046/285] chore(release): cut 24.10.1 [skip ci] ## [24.10.1](https://github.com/dhis2/analytics/compare/v24.10.0...v24.10.1) (2023-04-24) ### Bug Fixes * sv size when downloading is a small square (DHIS2-15178) ([245d799](https://github.com/dhis2/analytics/commit/245d799d9513e0407e4a6b6ba24c3da15754618d)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ded2903a..cecea0508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [24.10.1](https://github.com/dhis2/analytics/compare/v24.10.0...v24.10.1) (2023-04-24) + + +### Bug Fixes + +* sv size when downloading is a small square (DHIS2-15178) ([245d799](https://github.com/dhis2/analytics/commit/245d799d9513e0407e4a6b6ba24c3da15754618d)) + # [24.10.0](https://github.com/dhis2/analytics/compare/v24.9.3...v24.10.0) (2023-04-19) diff --git a/package.json b/package.json index 93278637c..45bcb4313 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.10.0", + "version": "24.10.1", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From d174e3e8a1dd1ea756cf42eaf7748b2135151e57 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 24 Apr 2023 13:59:14 +0200 Subject: [PATCH 047/285] feat: custom calculations (DHIS2-13871) (#1370) BREAKING CHANGE: requires metadata to be provided for the new EDI dimension type --- i18n/en.pot | 125 ++++- package.json | 5 + src/__demo__/CalculationModal.stories.js | 322 ++++++++++++ src/api/analytics/AnalyticsRequest.js | 8 + src/api/dimensions.js | 2 +- src/api/expression.js | 25 + .../DimensionItemIcons/CalculationIcon.js | 18 + src/assets/FormulaIcon.js | 30 ++ .../Calculation/CalculationModal.js | 492 ++++++++++++++++++ .../Calculation/DataElementOption.js | 47 ++ .../Calculation/DataElementSelector.js | 321 ++++++++++++ .../DataDimension/Calculation/DndContext.js | 192 +++++++ .../Calculation/DragHandleIcon.js | 16 + .../DataDimension/Calculation/DraggingItem.js | 46 ++ .../DataDimension/Calculation/DropZone.js | 38 ++ .../DataDimension/Calculation/FormulaField.js | 106 ++++ .../DataDimension/Calculation/FormulaItem.js | 223 ++++++++ .../Calculation/MathOperatorSelector.js | 33 ++ .../DataDimension/Calculation/Operator.js | 48 ++ .../styles/CalculationModal.style.js | 68 +++ .../styles/DataElementOption.style.js | 41 ++ .../styles/DataElementSelector.style.js | 79 +++ .../Calculation/styles/DraggingItem.style.js | 13 + .../Calculation/styles/DropZone.style.js | 52 ++ .../Calculation/styles/FormulaField.style.js | 51 ++ .../Calculation/styles/FormulaItem.style.js | 149 ++++++ .../styles/MathOperatorSelector.style.js | 23 + .../Calculation/styles/Operator.style.js | 13 + src/components/DataDimension/DataDimension.js | 17 + .../DataDimension/DataTypeSelector.js | 31 +- src/components/DataDimension/ItemSelector.js | 312 ++++++----- src/components/TransferOption.js | 19 +- .../styles/DimensionSelector.style.js | 3 + src/components/styles/TransferOption.style.js | 18 + src/index.js | 1 + src/modules/__tests__/expressions.spec.js | 166 ++++++ src/modules/__tests__/hash.spec.js | 125 +++++ src/modules/__tests__/parseExpression.spec.js | 139 +++++ src/modules/dataTypes.js | 7 + src/modules/dimensionListItem.js | 61 +++ src/modules/expressions.js | 119 +++++ src/modules/hash.js | 22 + yarn.lock | 45 ++ 43 files changed, 3504 insertions(+), 167 deletions(-) create mode 100644 src/__demo__/CalculationModal.stories.js create mode 100644 src/api/expression.js create mode 100644 src/assets/DimensionItemIcons/CalculationIcon.js create mode 100644 src/assets/FormulaIcon.js create mode 100644 src/components/DataDimension/Calculation/CalculationModal.js create mode 100644 src/components/DataDimension/Calculation/DataElementOption.js create mode 100644 src/components/DataDimension/Calculation/DataElementSelector.js create mode 100644 src/components/DataDimension/Calculation/DndContext.js create mode 100644 src/components/DataDimension/Calculation/DragHandleIcon.js create mode 100644 src/components/DataDimension/Calculation/DraggingItem.js create mode 100644 src/components/DataDimension/Calculation/DropZone.js create mode 100644 src/components/DataDimension/Calculation/FormulaField.js create mode 100644 src/components/DataDimension/Calculation/FormulaItem.js create mode 100644 src/components/DataDimension/Calculation/MathOperatorSelector.js create mode 100644 src/components/DataDimension/Calculation/Operator.js create mode 100644 src/components/DataDimension/Calculation/styles/CalculationModal.style.js create mode 100644 src/components/DataDimension/Calculation/styles/DataElementOption.style.js create mode 100644 src/components/DataDimension/Calculation/styles/DataElementSelector.style.js create mode 100644 src/components/DataDimension/Calculation/styles/DraggingItem.style.js create mode 100644 src/components/DataDimension/Calculation/styles/DropZone.style.js create mode 100644 src/components/DataDimension/Calculation/styles/FormulaField.style.js create mode 100644 src/components/DataDimension/Calculation/styles/FormulaItem.style.js create mode 100644 src/components/DataDimension/Calculation/styles/MathOperatorSelector.style.js create mode 100644 src/components/DataDimension/Calculation/styles/Operator.style.js create mode 100644 src/modules/__tests__/expressions.spec.js create mode 100644 src/modules/__tests__/hash.spec.js create mode 100644 src/modules/__tests__/parseExpression.spec.js create mode 100644 src/modules/dimensionListItem.js create mode 100644 src/modules/expressions.js create mode 100644 src/modules/hash.js diff --git a/i18n/en.pot b/i18n/en.pot index 9971e3065..549b42735 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: 2023-03-10T10:41:22.719Z\n" -"PO-Revision-Date: 2023-03-10T10:41:22.719Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" +"PO-Revision-Date: 2023-04-18T08:41:27.838Z\n" msgid "view only" msgstr "view only" @@ -73,11 +73,48 @@ msgstr "This app could not retrieve required data." msgid "Network error" msgstr "Network error" -msgid "Data Type" -msgstr "Data Type" +msgid "Data / Edit calculation" +msgstr "Data / Edit calculation" -msgid "All types" -msgstr "All types" +msgid "Data / New calculation" +msgstr "Data / New calculation" + +msgid "Remove item" +msgstr "Remove item" + +msgid "Check formula" +msgstr "Check formula" + +msgid "Calculation name" +msgstr "Calculation name" + +msgid "Shown in table headers and chart axes/legends" +msgstr "Shown in table headers and chart axes/legends" + +msgid "Delete calculation" +msgstr "Delete calculation" + +msgid "Cancel" +msgstr "Cancel" + +msgid "The calculation can only be saved with a valid formula" +msgstr "The calculation can only be saved with a valid formula" + +msgid "Add a name to save this calculation" +msgstr "Add a name to save this calculation" + +msgid "Save calculation" +msgstr "Save calculation" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." + +msgid "Yes, delete" +msgstr "Yes, delete" msgid "Totals only" msgstr "Totals only" @@ -85,15 +122,43 @@ msgstr "Totals only" msgid "Details only" msgstr "Details only" +msgid "Loading" +msgstr "Loading" + +msgid "Data elements" +msgstr "Data elements" + +msgid "Search by data element name" +msgstr "Search by data element name" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "No data elements found for \"{{- searchTerm}}\"" + +msgid "No data elements found" +msgstr "No data elements found" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" + +msgid "Math operators" +msgstr "Math operators" + +msgid "Data Type" +msgstr "Data Type" + +msgid "All types" +msgstr "All types" + msgid "Disaggregation" msgstr "Disaggregation" msgid "No data" msgstr "No data" -msgid "Loading" -msgstr "Loading" - msgid "Search by data item name" msgstr "Search by data item name" @@ -106,9 +171,6 @@ msgstr "Selected Items" msgid "No indicators found" msgstr "No indicators found" -msgid "No data elements found" -msgstr "No data elements found" - msgid "No data sets found" msgstr "No data sets found" @@ -121,9 +183,6 @@ msgstr "No program indicators found" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "No indicators found for \"{{- searchTerm}}\"" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "No data elements found for \"{{- searchTerm}}\"" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "No data sets found for \"{{- searchTerm}}\"" @@ -136,6 +195,9 @@ msgstr "No program indicators found for \"{{- searchTerm}}\"" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "Nothing found for \"{{- searchTerm}}\"" +msgid "Calculation" +msgstr "Calculation" + msgid "Metric type" msgstr "Metric type" @@ -203,9 +265,6 @@ msgstr "Delete {{fileType}}" msgid "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "This {{fileType}} and related interpretations will be deleted. Continue?" -msgid "Cancel" -msgstr "Cancel" - msgid "Delete" msgstr "Delete" @@ -881,9 +940,6 @@ msgstr "No indicator groups found" msgid "Loading indicator groups" msgstr "Loading indicator groups" -msgid "Data elements" -msgstr "Data elements" - msgid "Data element group" msgstr "Data element group" @@ -929,6 +985,33 @@ msgstr "Program indicators" msgid "Program indicator" msgstr "Program indicator" +msgid "Calculations" +msgstr "Calculations" + +msgid "Number" +msgstr "Number" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "Formula is empty. Add items to the formula from the lists on the left." + +msgid "Consecutive math operators" +msgstr "Consecutive math operators" + +msgid "Consecutive data elements" +msgstr "Consecutive data elements" + +msgid "Starts or ends with a math operator" +msgstr "Starts or ends with a math operator" + +msgid "Empty parentheses" +msgstr "Empty parentheses" + +msgid "Missing right parenthesis )" +msgstr "Missing right parenthesis )" + +msgid "Missing left parenthesis (" +msgstr "Missing left parenthesis (" + msgid "Extra Small" msgstr "Extra Small" diff --git a/package.json b/package.json index 45bcb4313..a644a5b5c 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,12 @@ "dependencies": { "@dhis2/d2-ui-rich-text": "^7.4.0", "@dhis2/multi-calendar-dates": "1.0.0", + "@dnd-kit/core": "^6.0.7", + "@dnd-kit/sortable": "^7.0.2", + "@dnd-kit/utilities": "^3.2.1", + "@react-hook/debounce": "^4.0.0", "classnames": "^2.3.1", + "crypto-js": "^4.1.1", "d2-utilizr": "^0.2.16", "d3-color": "^1.2.3", "highcharts": "^10.2.0", diff --git a/src/__demo__/CalculationModal.stories.js b/src/__demo__/CalculationModal.stories.js new file mode 100644 index 000000000..7c4debf4c --- /dev/null +++ b/src/__demo__/CalculationModal.stories.js @@ -0,0 +1,322 @@ +import { CustomDataProvider } from '@dhis2/app-runtime' +import { storiesOf } from '@storybook/react' +import React from 'react' +import CalculationModal from '../components/DataDimension/Calculation/CalculationModal.js' + +const DATA_ELEMENTS = { + pager: { + page: 1, + total: 622, + pageSize: 50, + nextPage: + 'http://localhost:8080/api/39/dataElements?page=2&filter=domainType%3Aeq%3AAGGREGATE&paging=true&fields=dimensionItem%7Erename%28id%29%2CdisplayName%7Erename%28name%29%2CdimensionItemType&order=displayName%3Aasc', + pageCount: 13, + }, + dataElements: [ + { + dimensionItemType: 'DATA_ELEMENT', + id: 'fbfJHSPpUQD', + name: 'ANC 1st visit', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'cYeuwXTCPkU', + name: 'ANC 2nd visit', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'Jtf34kNZhzP', + name: 'ANC 3rd visit', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'hfdmMSPBgLG', + name: 'ANC 4th or more visits', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'FHD3wiSM7Sn', + name: 'ARI treated with antibiotics (pneumonia) follow-up', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'oLfWYAJhZb2', + name: 'ARI treated without antibiotics (cough) referrals', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'GMd99K8gVut', + name: 'ART No clients who stopped TRT due to TRT failure', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'wfKKFhBn0Q0', + name: 'ART No clients who stopped TRT due to adverse clinical status/event', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'aIJZ2d2QgVV', + name: 'ART defaulters', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'BOSZApCrBni', + name: 'ART enrollment stage 1', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'dGdeotKpRed', + name: 'ART enrollment stage 2', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'LVaUdM3CERi', + name: 'ART entry point: No old patients', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'M62VHgYT2n0', + name: 'Acute Flaccid Paralysis (AFP) referrals', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'uF1DLnZNlWe', + name: 'Additional notes related to facility', + }, + + { + dimensionItemType: 'DATA_ELEMENT', + id: 'jmWyJFtE7Af', + name: 'Anaemia follow-up', + }, + { + dimensionItemType: 'DATA_ELEMENT', + id: 'HLPuaFB7Frw', + name: 'Anaemia new', + }, + ], +} + +const DATA_ELEMENT_OPERANDS = { + pager: { + page: 1, + pageCount: 43, + total: 2122, + pageSize: 50, + nextPage: + 'https://debug.dhis2.org/dev/api/dataElementOperands.json?page=2', + }, + dataElementOperands: [ + { + dimensionItemType: 'DATA_ELEMENT_OPERAND', + id: 'fbfJHSPpUQD.pq2XI5kz2BY', + name: 'ANC 1st visit Fixed', + }, + { + dimensionItemType: 'DATA_ELEMENT_OPERAND', + id: 'fbfJHSPpUQD.PT59n8BQbqM', + name: 'ANC 1st visit Outreach', + }, + ], +} + +const DATA_ELEMENT_GROUPS = { + dataElementGroups: [ + { id: 'qfxEYY9xAl6', name: 'ANC' }, + { id: 'yhg8oYU9ekY', name: 'ARI Treated Without Antibiotics (Cough)' }, + { id: 'M2cth8EmrlT', name: 'ARI treated with antibiotics (Pneumonia)' }, + { id: 'k1M0nuodfhN', name: 'ART' }, + { id: 'bdiyMm9qZl5', name: 'ART enrollment' }, + { id: 's8FiXqB2DhB', name: 'ART entry points' }, + { id: 'TcxHxMlYzpv', name: 'ART pediatric 1st line' }, + { id: 'vxIWSNeEcf7', name: 'ART staging' }, + { id: 'zz1lNBgRKWU', name: 'ART treatment' }, + { id: 'oDkJh5Ddh7d', name: 'Acute Flaccid Paralysis (AFP) ' }, + { id: 'GBHN1a1Jddh', name: 'All Others' }, + { id: 'KmwPVkjp7yl', name: 'Anaemia' }, + { id: 'oGktdmYkRNo', name: 'Burns' }, + { id: 'KUSvwZQsMSN', name: 'Cholera' }, + { id: 'Euvh58hLl61', name: 'Clinical Malnutrition' }, + { id: 'Svac1cNQhRS', name: 'Commodities' }, + { id: 'KJKWrWBcJdf', name: 'Commodities Child Health' }, + { id: 'idD1wcvBISQ', name: 'Commodities Maternal Health' }, + { id: 'rioWDAi1S7z', name: 'Commodities Newborn Health' }, + { id: 'IyIa0h8CbCZ', name: 'Commodities Reproductive Health' }, + { id: 'PfJGQacYpjn', name: 'Deaths' }, + { id: 't5W0AAqvK5b', name: 'Delivery' }, + { id: 'mcjC3qZgIkO', name: 'Diarrhoea With Blood (Dysentery)' }, + { id: 'RSFc8ADyKTw', name: 'Diarrhoea With Severe Dehydration' }, + { id: 'kE8lP5t0b5R', name: 'Diarrhoea Without Severe Dehydration' }, + { id: 'qiF051Ue9Ei', name: 'Emergency Response' }, + { id: 'GbSz3TobZcc', name: 'Expenditures' }, + { id: 'lLKpwhjd1dM', name: 'Eye Infection' }, + { id: 'Sp1jJqzsiOi', name: 'Facility infrastructure' }, + { id: 'g50BzGAsrvu', name: 'Follow-up' }, + { id: 'URmi41e0SFH', name: 'HIV Care' }, + { id: 'ID4BbhF7Eli', name: 'HIV Peadriatics' }, + { id: 'HKU7L73im5r', name: 'HIV/AIDS' }, + { id: 'jWgEsdH87Jk', name: 'Hypertension' }, + { id: 'nb3rBNvVHtp', name: 'ICS Children' }, + { id: 'dMyLpSQn6hu', name: 'ICS mother' }, + { id: 'b3gDdvmrSFc', name: 'IDSR' }, + { id: 'h9cuJOkOwY2', name: 'Immunization' }, + { id: 'OP4dLqk0JTH', name: 'Inpatient morbidity/mortality aggregates' }, + { id: 'SriP0jBXMr6', name: 'Lassa Fever' }, + { id: 'U0uJG4kydwE', name: 'Leprosy' }, + { id: 'eeQCyjnMyGY', name: 'Low birth' }, + { id: 'LEet4tb49IP', name: 'MNCH Aggregates' }, + { id: 'TzwKbcw1nUK', name: 'Malaria' }, + { id: 'qk2KOBMX4Mf', name: 'Measles' }, + { id: 'lnLbEej0gwe', name: 'Meningitis / Severe Bacterial Infection' }, + { id: 'SLsJy3zqUbD', name: 'Morbidity' }, + { id: 'QAc5FhbeFwl', name: 'Mortality' }, + { id: 'rPGfUFYbcfJ', name: 'Mortality < 5 years' }, + { id: 'KU0wDurtWDM', name: 'Mortality Narrative' }, + { id: 'UAEhIWpoQFN', name: 'Neonatal Tetanus' }, + { id: 'weRMUzBs8T7', name: 'New cases' }, + { id: 'XGSHYf5uOlJ', name: 'New on ART' }, + { id: 'u1ilfnoYafG', name: 'Nutrition' }, + { id: 'HDdnX6XqxIn', name: 'Onchocerciasis' }, + { id: 'JZ3usxLEcc9', name: 'Otitis Media' }, + { id: 'WS3MniopkOQ', name: 'PMTCT' }, + { id: 'e5NGCRQR8Yo', name: 'PMTCT ANC' }, + { id: 'bXpe2ByvlFR', name: 'PMTCT Maternity/Delivery' }, + { id: 'sGLusXgmaOT', name: 'PMTCT Postnatal' }, + { id: 'sP7jTt3YGBb', name: 'Population Estimates' }, + { id: 'ubJrVb4v5xy', name: 'Postnatal' }, + { id: 'qkrZMU4Y2h5', name: 'Pregnancy complications and deaths' }, + { id: 'ZPtRFVLY40u', name: 'Pregnancy-related (PHUF5)' }, + { id: 'AiytigJkHP6', name: 'Prev month on ART' }, + { id: 'UWaMbg9h7vF', name: 'Referrals' }, + { id: 'OJxi4vkcTBS', name: 'Reproductive health' }, + { id: 'xNrDrDbJgnm', name: 'STI - Genital Discharge' }, + { id: 'UmyRWILcoed', name: 'STI - Genital Ulcer' }, + { id: 'LqG1FnAUhyb', name: 'Schistosomiasis' }, + { id: 'rwG73cCi66Z', name: 'Shift from ART reg.' }, + { id: 'rgTZ4mKjZza', name: 'Shift to ART reg.' }, + { id: 'VCoSeRRVS1n', name: 'Skin Infection' }, + { id: 'rVEe5QNBgDX', name: 'Staffing' }, + { id: 'pxWhf42tCIs', name: 'Stock PHU' }, + { id: 'OnAQ2lsilN9', name: 'TB' }, + { id: 'LgtuBcNaMB3', name: 'Tetanus' }, + { id: 'yHtsPZqpAxm', name: 'Tuberculosis' }, + { id: 'dUK38PhdUdV', name: 'Typhoid Fever' }, + { id: 'U9wcARyKSzx', name: 'VCCT' }, + { id: 'LzDaTmQYWcj', name: 'Worm Infestation' }, + { id: 'IUZ0GidX0jh', name: 'Wounds/Trauma' }, + { id: 'zmWJAEjfv59', name: 'Yaws' }, + { id: 'HAraPb0v7ex', name: 'Yellow Fever' }, + ], +} + +const calculation = { + id: 'calculationid', + name: 'My calculation', + expression: '#{fbfJHSPpUQD}/10*#{hfdmMSPBgLG}', +} + +const calculationWithOperand = { + id: 'calcid2', + name: 'Calculation with operand', + expression: '#{cYeuwXTCPkU}*10-#{fbfJHSPpUQD.pq2XI5kz2BY}', +} + +storiesOf('CalculationModal', module) + .add('Default', () => { + return ( + + + + ) + }) + .add('With calculation', () => { + return ( + + + + ) + }) + .add('With calculation containing operand', () => { + return ( + + + + ) + }) + .add('No available data', () => { + return ( + + + + ) + }) diff --git a/src/api/analytics/AnalyticsRequest.js b/src/api/analytics/AnalyticsRequest.js index 27bf20da5..5b7d4b70e 100644 --- a/src/api/analytics/AnalyticsRequest.js +++ b/src/api/analytics/AnalyticsRequest.js @@ -1,3 +1,4 @@ +import { getExpressionHashFromVisualization } from '../../modules/hash.js' import { getFixedDimensions } from '../../modules/predefinedDimensions.js' import AnalyticsRequestBase from './AnalyticsRequestBase.js' import AnalyticsRequestDimensionsMixin from './AnalyticsRequestDimensionsMixin.js' @@ -113,6 +114,13 @@ class AnalyticsRequest extends AnalyticsRequestDimensionsMixin( } }) + // add cache param for expression dimension items + const expressionHash = getExpressionHashFromVisualization(visualization) + + if (expressionHash) { + request.withParameters({ edi_cache: expressionHash }) + } + return request } } diff --git a/src/api/dimensions.js b/src/api/dimensions.js index 1ca3b334f..3a657fc43 100644 --- a/src/api/dimensions.js +++ b/src/api/dimensions.js @@ -71,7 +71,7 @@ export const dataItemsQuery = { } return objectClean({ - fields: `id,${nameProp}~rename(name),dimensionItemType`, + fields: `id,${nameProp}~rename(name),dimensionItemType,expression`, order: `${nameProp}:asc`, filter: filters, paging: true, diff --git a/src/api/expression.js b/src/api/expression.js new file mode 100644 index 000000000..2b7e85bb6 --- /dev/null +++ b/src/api/expression.js @@ -0,0 +1,25 @@ +export const validateExpressionMutation = { + type: 'create', + resource: 'indicators/expression/description', + data: ({ expression }) => expression, +} + +export const createCalculationMutation = { + type: 'create', + resource: 'expressionDimensionItems', + data: ({ name, expression }) => ({ name, shortName: name, expression }), +} + +export const updateCalculationMutation = { + type: 'update', + resource: 'expressionDimensionItems', + partial: true, + id: ({ id }) => id, + data: ({ name, expression }) => ({ name, shortName: name, expression }), +} + +export const deleteCalculationMutation = { + type: 'delete', + resource: 'expressionDimensionItems', + id: ({ id }) => id, +} diff --git a/src/assets/DimensionItemIcons/CalculationIcon.js b/src/assets/DimensionItemIcons/CalculationIcon.js new file mode 100644 index 000000000..a7b124ed0 --- /dev/null +++ b/src/assets/DimensionItemIcons/CalculationIcon.js @@ -0,0 +1,18 @@ +import React from 'react' + +export default ( + + + +) diff --git a/src/assets/FormulaIcon.js b/src/assets/FormulaIcon.js new file mode 100644 index 000000000..7c71c02ad --- /dev/null +++ b/src/assets/FormulaIcon.js @@ -0,0 +1,30 @@ +import React from 'react' + +const FormulaIcon = () => { + return ( + + + + + + + + + + + + ) +} + +export default FormulaIcon diff --git a/src/components/DataDimension/Calculation/CalculationModal.js b/src/components/DataDimension/Calculation/CalculationModal.js new file mode 100644 index 000000000..4d3c23146 --- /dev/null +++ b/src/components/DataDimension/Calculation/CalculationModal.js @@ -0,0 +1,492 @@ +import { useAlert, useDataMutation, useDataQuery } from '@dhis2/app-runtime' +import { + Button, + Modal, + ModalTitle, + ModalContent, + ModalActions, + ButtonStrip, + InputField, +} from '@dhis2/ui' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React, { useEffect, useState } from 'react' +import { + createCalculationMutation, + deleteCalculationMutation, + updateCalculationMutation, + validateExpressionMutation, +} from '../../../api/expression.js' +import i18n from '../../../locales/index.js' +import { + parseExpressionToArray, + parseArrayToExpression, + validateExpression, + EXPRESSION_TYPE_DATA, + EXPRESSION_TYPE_NUMBER, + INVALID_EXPRESSION, + VALID_EXPRESSION, + getItemIdsFromExpression, +} from '../../../modules/expressions.js' +import { OfflineTooltip as Tooltip } from '../../OfflineTooltip.js' +import DataElementSelector from './DataElementSelector.js' +import DndContext, { OPTIONS_PANEL } from './DndContext.js' +import FormulaField, { + LAST_DROPZONE_ID, + FORMULA_BOX_ID, +} from './FormulaField.js' +import MathOperatorSelector from './MathOperatorSelector.js' +import styles from './styles/CalculationModal.style.js' + +const FIRST_POSITION = 0 +const LAST_POSITION = -1 + +const CalculationModal = ({ + calculation, + onSave, + onClose, + onDelete, + displayNameProp, +}) => { + const { show: showError } = useAlert((error) => error, { critical: true }) + const mutationParams = { onError: (error) => showError(error) } + const [createCalculation, { loading: isCreatingCalculation }] = + useDataMutation(createCalculationMutation, mutationParams) + const [updateCalculation, { loading: isUpdatingCalculation }] = + useDataMutation(updateCalculationMutation, mutationParams) + const [deleteCalculation, { loading: isDeletingCalculation }] = + useDataMutation(deleteCalculationMutation, mutationParams) + const [doBackendValidation, { loading: isValidating }] = useDataMutation( + validateExpressionMutation, + { + onError: (error) => showError(error), + } + ) + + const query = { + dataElements: { + resource: 'dataElements', + params: ({ ids = [] }) => ({ + fields: `id,${displayNameProp}~rename(name)`, + filter: `id:in:[${ids.join(',')}]`, + paging: false, + }), + }, + dataElementOperands: { + resource: 'dataElementOperands', + params: ({ ids = [] }) => ({ + fields: `id,${displayNameProp}~rename(name)`, + filter: `id:in:[${ids.join(',')}]`, + paging: false, + }), + }, + } + + const { data, refetch } = useDataQuery(query, { + lazy: true, + }) + + useEffect(() => { + const ids = getItemIdsFromExpression(calculation.expression) + + // only fetch data if there are ids + if (ids?.length) { + refetch({ ids }) + } else { + setExpressionArray( + parseExpressionToArray(calculation.expression).map( + (item, i) => ({ + ...item, + id: `${item.type}-${-i}`, + }) + ) + ) + } + }, [refetch, calculation.expression]) + + useEffect(() => { + if (data) { + const metadata = [ + ...(data.dataElements?.dataElements || []), + ...(data.dataElementOperands?.dataElementOperands || []), + ] + + setExpressionArray( + parseExpressionToArray(calculation.expression, metadata).map( + (item, i) => ({ + ...item, + id: `${item.type}-${-i}`, + }) + ) + ) + } + }, [data, calculation.expression]) + + const [newIdCount, setNewIdCount] = useState(1) + + const [validationOutput, setValidationOutput] = useState(null) + const [expressionArray, setExpressionArray] = useState() + const [name, setName] = useState(calculation.name) + const [showDeletePrompt, setShowDeletePrompt] = useState(false) + const [isSavingCalculation, setIsSavingCalculation] = useState() + + const [focusItemId, setFocusItemId] = useState(null) + const [selectedItemId, setSelectedItemId] = useState(null) + + const expressionStatus = validationOutput?.status + + const selectItem = (itemId) => + setSelectedItemId((prevSelected) => + prevSelected !== itemId ? itemId : null + ) + + const isLoading = + isCreatingCalculation || + isUpdatingCalculation || + isDeletingCalculation || + isSavingCalculation || + isValidating + + const addItem = ({ label, value, type, destIndex = LAST_POSITION }) => { + if (isLoading) { + return null + } + setValidationOutput() + + const newItem = { + id: `${type}-${newIdCount}`, + value: type === EXPRESSION_TYPE_DATA ? `#{${value}}` : value, + label, + type, + } + + setNewIdCount(newIdCount + 1) + + if (destIndex === LAST_POSITION) { + setExpressionArray((prevArray) => prevArray.concat([newItem])) + } else if (destIndex === FIRST_POSITION) { + setExpressionArray((prevArray) => [newItem].concat(prevArray)) + } else { + const items = Array.from(expressionArray) + const newFormulaItems = [ + ...items.slice(0, destIndex), + newItem, + ...items.slice(destIndex), + ] + setExpressionArray(newFormulaItems) + } + + if (newItem.type === EXPRESSION_TYPE_NUMBER) { + setFocusItemId(newItem.id) + } + } + + const moveItem = ({ sourceIndex, destIndex }) => { + if (isLoading) { + return null + } + setValidationOutput() + const sourceList = Array.from(expressionArray) + const [moved] = sourceList.splice(sourceIndex, 1) + sourceList.splice(destIndex, 0, moved) + setExpressionArray(sourceList) + } + + const setItemValue = ({ itemId, value }) => { + const updatedItems = expressionArray.map((item) => + item.id === itemId ? Object.assign({}, item, { value }) : item + ) + setExpressionArray(updatedItems) + } + + const removeItem = (itemId) => { + if (!isLoading && itemId !== null) { + setValidationOutput() + const index = expressionArray.findIndex( + (item) => item.id === itemId + ) + const sourceList = Array.from(expressionArray) + sourceList.splice(index, 1) + setExpressionArray(sourceList) + setSelectedItemId(null) + } + } + + const addOrMoveDraggedItem = ({ item, destination }) => { + const destContainerId = destination.containerId + + let destIndex = FIRST_POSITION + if (item.sourceContainerId === OPTIONS_PANEL) { + if (destContainerId === LAST_DROPZONE_ID) { + destIndex = LAST_POSITION + } else if (destContainerId === FORMULA_BOX_ID) { + destIndex = destination.index + 1 + } + + addItem({ ...item.data, destIndex }) + } else { + if (destContainerId === LAST_DROPZONE_ID) { + destIndex = expressionArray.length + } else if (destContainerId === FORMULA_BOX_ID) { + destIndex = destination.index + } + + moveItem({ sourceIndex: item.sourceIndex, destIndex }) + } + } + + const validate = async () => { + setValidationOutput() + const expression = parseArrayToExpression(expressionArray) + let result = validateExpression(expression) + if (!result) { + result = await doBackendValidation({ + expression, + }) + } + setValidationOutput(result) + + return result?.status + } + + const onSaveClick = async () => { + setIsSavingCalculation(true) + let status = expressionStatus + + if (status !== VALID_EXPRESSION) { + status = await validate() + } + + if (status === VALID_EXPRESSION) { + let response + const expression = parseArrayToExpression(expressionArray) + + if (calculation.id) { + response = await updateCalculation({ + id: calculation.id, + name, + expression, + }) + } else { + response = await createCalculation({ + name, + expression, + }) + } + + onSave({ + id: calculation.id || response?.response.uid, + name, + isNew: !calculation.id, + expression, + }) + } + setIsSavingCalculation(false) + } + + const onDeleteClick = async () => { + setShowDeletePrompt() + await deleteCalculation({ id: calculation.id }) + onDelete({ + id: calculation.id, + }) + } + + return ( + <> + + + {calculation.id + ? i18n.t('Data / Edit calculation') + : i18n.t('Data / New calculation')} + + + setFocusItemId(null)} + onDragEnd={addOrMoveDraggedItem} + > +
    +
    + + +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + + {validationOutput?.message} + +
    + + setName(value.substr(0, 50)) + } + value={name} + dataTest="calculation-label" + dense + /> +
    +
    +
    +
    +
    +
    + + + {calculation.id && ( +
    + +
    + )} + + + + +
    +
    +
    + {showDeletePrompt && ( + + {i18n.t('Delete calculation')} + + {i18n.t( + 'Are you sure you want to delete this calculation? It may be used by other visualizations.' + )} + + + + + + + + + + )} + + + ) +} + +CalculationModal.propTypes = { + displayNameProp: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired, + calculation: PropTypes.shape({ + expression: PropTypes.string, + id: PropTypes.string, + name: PropTypes.string, + }), +} + +CalculationModal.defaultProps = { + calculation: {}, +} + +export default CalculationModal diff --git a/src/components/DataDimension/Calculation/DataElementOption.js b/src/components/DataDimension/Calculation/DataElementOption.js new file mode 100644 index 000000000..584386ee8 --- /dev/null +++ b/src/components/DataDimension/Calculation/DataElementOption.js @@ -0,0 +1,47 @@ +import { useSortable } from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import PropTypes from 'prop-types' +import React from 'react' +import { DIMENSION_TYPE_DATA_ELEMENT } from '../../../modules/dataTypes.js' +import { getIcon } from '../../../modules/dimensionListItem.js' +import { EXPRESSION_TYPE_DATA } from '../../../modules/expressions.js' +import styles from './styles/DataElementOption.style.js' + +const DataElementOption = ({ label, value, onDoubleClick }) => { + const data = { label, value, type: EXPRESSION_TYPE_DATA } + const { attributes, listeners, setNodeRef, transform } = useSortable({ + id: value, + data, + }) + const style = { + transform: CSS.Translate.toString(transform), + } + + return ( +
    +
    +
    onDoubleClick(data)}> + + {getIcon(DIMENSION_TYPE_DATA_ELEMENT)} + + {label} +
    +
    + +
    + ) +} + +DataElementOption.propTypes = { + label: PropTypes.string, + value: PropTypes.string, + onDoubleClick: PropTypes.func, +} + +export default DataElementOption diff --git a/src/components/DataDimension/Calculation/DataElementSelector.js b/src/components/DataDimension/Calculation/DataElementSelector.js new file mode 100644 index 000000000..2deeff868 --- /dev/null +++ b/src/components/DataDimension/Calculation/DataElementSelector.js @@ -0,0 +1,321 @@ +import { useDataEngine } from '@dhis2/app-runtime' +import { + CircularLoader, + InputField, + IntersectionDetector, + SingleSelectField, + SingleSelectOption, +} from '@dhis2/ui' +import { useSortable } from '@dnd-kit/sortable' +import { useDebounceCallback } from '@react-hook/debounce' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React, { useEffect, useRef, useState } from 'react' +import { apiFetchOptions, apiFetchGroups } from '../../../api/dimensions.js' +import i18n from '../../../locales/index.js' +import { + TOTALS, + DETAIL, + DIMENSION_TYPE_ALL, + DIMENSION_TYPE_DATA_ELEMENT, + dataTypeMap as dataTypes, +} from '../../../modules/dataTypes.js' +import DataElementOption from './DataElementOption.js' +import styles from './styles/DataElementSelector.style.js' + +const getOptions = () => ({ + [TOTALS]: i18n.t('Totals only'), + [DETAIL]: i18n.t('Details only'), +}) + +const GroupSelector = ({ currentValue, onChange, displayNameProp }) => { + const dataEngine = useDataEngine() + + const [loading, setLoading] = useState(true) + const [groups, setGroups] = useState([]) + + const defaultGroup = dataTypes[DIMENSION_TYPE_DATA_ELEMENT]?.defaultGroup + + useEffect(() => { + const fetchGroups = async () => { + setLoading(true) + + const result = await apiFetchGroups( + dataEngine, + DIMENSION_TYPE_DATA_ELEMENT, + displayNameProp + ) + + setGroups(result) + + setLoading(false) + } + + fetchGroups() + }, [dataEngine, displayNameProp]) + + return ( +
    + onChange(ref.selected)} + dense + loading={loading} + loadingText={i18n.t('Loading')} + dataTest={'data-element-group-select'} + > + {defaultGroup ? ( + + ) : null} + {!loading + ? groups.map((group) => ( + + )) + : null} + + +
    + ) +} + +GroupSelector.propTypes = { + currentValue: PropTypes.string.isRequired, + displayNameProp: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, +} + +const DisaggregationSelector = ({ currentValue, onChange }) => { + const options = getOptions() + return ( +
    + onChange(ref.selected)} + dense + dataTest={'data-element-disaggregation-select'} + > + {Object.entries(options).map((option) => ( + + ))} + + +
    + ) +} + +DisaggregationSelector.propTypes = { + currentValue: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, +} + +const DataElementSelector = ({ displayNameProp, onDoubleClick }) => { + const dataEngine = useDataEngine() + + const [searchTerm, setSearchTerm] = useState('') + const [group, setGroup] = useState(DIMENSION_TYPE_ALL) + const [subGroup, setSubGroup] = useState(TOTALS) + const [options, setOptions] = useState([]) + const [loading, setLoading] = useState(true) + + const { isSorting } = useSortable({}) + + const rootRef = useRef() + const hasNextPageRef = useRef(false) + const searchTermRef = useRef(searchTerm) + const pageRef = useRef(0) + const filterRef = useRef({ + dataType: DIMENSION_TYPE_DATA_ELEMENT, + group, + subGroup, + }) + + const fetchData = async (scrollToTop) => { + try { + setLoading(true) + + const result = await apiFetchOptions({ + dataEngine, + nameProp: displayNameProp, + filter: filterRef.current, + searchTerm: searchTermRef.current, + page: pageRef.current, + }) + + if (result?.dimensionItems) { + const newOptions = result.dimensionItems.map((item) => ({ + label: item.name, + value: item.id, + type: item.dimensionItemType, + expression: item.expression, + })) + + setOptions((prevOptions) => + pageRef.current > 1 + ? [...prevOptions, ...newOptions] + : newOptions + ) + + setLoading(false) + } + + hasNextPageRef.current = result?.nextPage ? true : false + } catch (error) { + // TODO handle errors + console.log('apiFetchOptions error: ', error) + } finally { + if (scrollToTop) { + rootRef.current.scrollTo({ + top: 0, + }) + } + } + } + + const debouncedFetchData = useDebounceCallback((newSearchTerm) => { + hasNextPageRef.current = false + pageRef.current = 1 + searchTermRef.current = newSearchTerm + + fetchData(true) + }, 500) + + const onSearchChange = ({ value }) => { + const newSearchTerm = value + + setSearchTerm(newSearchTerm) + + // debounce the fetch + debouncedFetchData(newSearchTerm) + } + + const onFilterChange = (newFilter) => { + if (newFilter.group) { + setGroup(newFilter.group) + + filterRef.current.group = newFilter.group + } + + if (newFilter.subGroup) { + setSubGroup(newFilter.subGroup) + filterRef.current.subGroup = newFilter.subGroup + } + + hasNextPageRef.current = false + pageRef.current = 1 + + fetchData(true) + } + + const onEndReached = ({ isIntersecting }) => { + if (isIntersecting) { + // if hasNextPage is set it means at least 1 request already happened and there is + // another page, fetch the next page + if (hasNextPageRef.current) { + pageRef.current += 1 + + fetchData() + } else if (pageRef.current === 0) { + // this is for fetching the initial page + pageRef.current = 1 + + fetchData() + } + } + } + + return ( + <> +
    +

    {i18n.t('Data elements')}

    + +
    + onFilterChange({ group })} + displayNameProp={displayNameProp} + /> + onFilterChange({ subGroup })} + /> +
    +
    +
    + {loading && ( +
    + +
    + )} +
    { + if (isSorting) { + rootRef.current.scrollTo({ + top: 0, + }) + } + }} + > +
    + {Boolean(options.length) && + options.map(({ label, value }) => ( + + ))} + {!loading && !options.length && ( +
    + {searchTermRef.current + ? i18n.t( + 'No data elements found for "{{- searchTerm}}"', + { searchTerm: searchTermRef.current } + ) + : i18n.t('No data elements found')} +
    + )} +
    + +
    +
    +
    +
    + + + ) +} + +DataElementSelector.propTypes = { + displayNameProp: PropTypes.string.isRequired, + onDoubleClick: PropTypes.func.isRequired, +} + +export default DataElementSelector diff --git a/src/components/DataDimension/Calculation/DndContext.js b/src/components/DataDimension/Calculation/DndContext.js new file mode 100644 index 000000000..846835894 --- /dev/null +++ b/src/components/DataDimension/Calculation/DndContext.js @@ -0,0 +1,192 @@ +import { + DndContext, + DragOverlay, + useSensor, + useSensors, + PointerSensor as DndKitPointerSensor, +} from '@dnd-kit/core' +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import DraggingItem from './DraggingItem.js' + +export const OPTIONS_PANEL = 'Sortable' + +const getIntersectionRatio = (entry, target) => { + const top = Math.max(target.top, entry.top) + const left = Math.max(target.left, entry.left) + const right = Math.min(target.left + target.width, entry.left + entry.width) + const bottom = Math.min( + target.top + target.height, + entry.top + entry.height + ) + const width = right - left + const height = bottom - top + + if (left < right && top < bottom) { + const targetArea = target.width * target.height + const entryArea = entry.width * entry.height + const intersectionArea = width * height + const intersectionRatio = + intersectionArea / (targetArea + entryArea - intersectionArea) + return Number(intersectionRatio.toFixed(4)) + } // Rectangles do not overlap, or overlap has an area of zero (edge/corner overlap) + + return 0 +} +const sortCollisionsDesc = ({ data: { value: a } }, { data: { value: b } }) => { + return b - a +} + +const rectIntersectionCustom = ({ + pointerCoordinates, + droppableContainers, +}) => { + // create a rect around the pointerCoords for calculating the intersection + + const pointerRectWidth = 40 + const pointerRectHeight = 40 + const pointerRect = { + width: pointerRectWidth, + height: pointerRectHeight, + top: pointerCoordinates.y - pointerRectHeight / 2, + bottom: pointerCoordinates.y + pointerRectHeight / 2, + left: pointerCoordinates.x - pointerRectWidth / 2, + right: pointerCoordinates.x + pointerRectWidth / 2, + } + const collisions = [] + + for (const droppableContainer of droppableContainers) { + const { + id, + rect: { current: rect }, + } = droppableContainer + + if (rect) { + const intersectionRatio = getIntersectionRatio(rect, pointerRect) + + if (intersectionRatio > 0) { + collisions.push({ + id, + data: { + droppableContainer, + value: intersectionRatio, + }, + }) + } + } + } + + return collisions.sort(sortCollisionsDesc) +} + +const isInteractiveElement = (el) => { + const interactiveElements = [ + 'button', + 'input', + 'textarea', + 'select', + 'option', + ] + + if (interactiveElements.includes(el.tagName.toLowerCase())) { + return true + } + + return false +} + +// disable dragging if user is in an input +class PointerSensor extends DndKitPointerSensor { + static activators = [ + { + eventName: 'onPointerDown', + handler: ({ nativeEvent: event }) => { + if ( + !event.isPrimary || + event.button !== 0 || + isInteractiveElement(event.target) + ) { + return false + } + + return true + }, + }, + ] +} + +const OuterDndContext = ({ children, onDragEnd, onDragStart }) => { + const [draggingItem, setDraggingItem] = useState(null) + const sensor = useSensor(PointerSensor, { + activationConstraint: { + distance: 15, + }, + }) + const sensors = useSensors(sensor) + + const handleDragStart = ({ active }) => { + setDraggingItem(active.data.current) + onDragStart && onDragStart() + } + + const handleDragCancel = () => { + setDraggingItem(null) + } + + const handleDragEnd = ({ active, over }) => { + if ( + !over?.id || + over?.data?.current?.sortable?.containerId === OPTIONS_PANEL || + !active.data.current + ) { + // dropped over non-droppable or over options panel + handleDragCancel() + return + } + + const item = { + id: active.id, + sourceContainerId: active.data.current.sortable.containerId, + sourceIndex: active.data.current.sortable.index, + data: { + label: active.data.current.label, + value: active.data.current.value, + type: active.data.current.type, + }, + } + const destination = { + containerId: over.data.current?.sortable.containerId || over.id, + index: over.data.current?.sortable.index, + } + + onDragEnd({ item, destination }) + setDraggingItem(null) + } + + return ( + + {children} + + {draggingItem ? ( + + + + ) : null} + + + ) +} + +OuterDndContext.propTypes = { + onDragEnd: PropTypes.func.isRequired, + children: PropTypes.node, + onDragStart: PropTypes.func, +} + +export default OuterDndContext diff --git a/src/components/DataDimension/Calculation/DragHandleIcon.js b/src/components/DataDimension/Calculation/DragHandleIcon.js new file mode 100644 index 000000000..e68b1f0f4 --- /dev/null +++ b/src/components/DataDimension/Calculation/DragHandleIcon.js @@ -0,0 +1,16 @@ +import React from 'react' + +export default ( + + + +) diff --git a/src/components/DataDimension/Calculation/DraggingItem.js b/src/components/DataDimension/Calculation/DraggingItem.js new file mode 100644 index 000000000..8463f10da --- /dev/null +++ b/src/components/DataDimension/Calculation/DraggingItem.js @@ -0,0 +1,46 @@ +import cx from 'classnames' +import PropTypes from 'prop-types' +import React from 'react' +import { DIMENSION_TYPE_DATA_ELEMENT } from '../../../modules/dataTypes.js' +import { getIcon } from '../../../modules/dimensionListItem.js' +import { + EXPRESSION_TYPE_DATA, + EXPRESSION_TYPE_NUMBER, + EXPRESSION_TYPE_OPERATOR, +} from '../../../modules/expressions.js' +import styles from './styles/DraggingItem.style.js' +import formulaItemStyles from './styles/FormulaItem.style.js' + +const DraggingItem = ({ label, type, value }) => { + const displayLabel = + type === EXPRESSION_TYPE_NUMBER ? value || label : label + + return ( + <> +
    + {type === EXPRESSION_TYPE_DATA && ( + + {getIcon(DIMENSION_TYPE_DATA_ELEMENT)} + + )} + {displayLabel} +
    + + + + ) +} + +DraggingItem.propTypes = { + label: PropTypes.string, + type: PropTypes.string, + value: PropTypes.string, +} + +export default DraggingItem diff --git a/src/components/DataDimension/Calculation/DropZone.js b/src/components/DataDimension/Calculation/DropZone.js new file mode 100644 index 000000000..2cfa69281 --- /dev/null +++ b/src/components/DataDimension/Calculation/DropZone.js @@ -0,0 +1,38 @@ +import { useDroppable } from '@dnd-kit/core' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React from 'react' +import styles from './styles/DropZone.style.js' + +const DropZone = ({ firstElementId, overLastDropZone }) => { + const { isOver, setNodeRef, active } = useDroppable({ + id: 'firstdropzone', + }) + + let draggingOver = false + if (overLastDropZone && !firstElementId) { + draggingOver = true + } else { + draggingOver = firstElementId === active?.id ? false : isOver + } + + return ( + <> +
    + + + ) +} + +DropZone.propTypes = { + firstElementId: PropTypes.string, + overLastDropZone: PropTypes.bool, +} + +export default DropZone diff --git a/src/components/DataDimension/Calculation/FormulaField.js b/src/components/DataDimension/Calculation/FormulaField.js new file mode 100644 index 000000000..075ff285c --- /dev/null +++ b/src/components/DataDimension/Calculation/FormulaField.js @@ -0,0 +1,106 @@ +import i18n from '@dhis2/d2-i18n' +import { Center, CircularLoader } from '@dhis2/ui' +import { useDroppable } from '@dnd-kit/core' +import { SortableContext } from '@dnd-kit/sortable' +import PropTypes from 'prop-types' +import React from 'react' +import FormulaIcon from '../../../assets/FormulaIcon.js' +import DropZone from './DropZone.js' +import FormulaItem from './FormulaItem.js' +import styles from './styles/FormulaField.style.js' + +export const LAST_DROPZONE_ID = 'lastdropzone' +export const FORMULA_BOX_ID = 'formulabox' + +const Placeholder = () => ( +
    + + + {i18n.t( + 'Drag items here, or double click in the list, to start building a calculation formula' + )} + + +
    +) + +const FormulaField = ({ + items = [], + selectedItemId, + focusItemId, + onChange, + onClick, + onDoubleClick, + loading, +}) => { + const { over, setNodeRef: setLastDropzoneRef } = useDroppable({ + id: LAST_DROPZONE_ID, + }) + + const itemIds = items.map((item) => item.id) + + const overLastDropZone = over?.id === LAST_DROPZONE_ID + + return ( +
    +
    +
    + {loading && ( +
    + +
    + )} + {!loading && itemIds && ( + + + {!items.length && } + {Boolean(items.length) && + items.map(({ id, label, type, value }, index) => ( + + ))} + + )} +
    + +
    + ) +} + +FormulaField.propTypes = { + onChange: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + onDoubleClick: PropTypes.func.isRequired, + focusItemId: PropTypes.string, + items: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string, + label: PropTypes.string, + type: PropTypes.string, + value: PropTypes.string, + }) + ), + loading: PropTypes.bool, + selectedItemId: PropTypes.string, +} + +export default FormulaField diff --git a/src/components/DataDimension/Calculation/FormulaItem.js b/src/components/DataDimension/Calculation/FormulaItem.js new file mode 100644 index 000000000..45e1a1f88 --- /dev/null +++ b/src/components/DataDimension/Calculation/FormulaItem.js @@ -0,0 +1,223 @@ +import { Tooltip } from '@dhis2/ui' +import { useSortable } from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React, { useState, useRef, useEffect } from 'react' +import { DIMENSION_TYPE_DATA_ELEMENT } from '../../../modules/dataTypes.js' +import { getIcon } from '../../../modules/dimensionListItem.js' +import { + EXPRESSION_TYPE_NUMBER, + EXPRESSION_TYPE_DATA, +} from '../../../modules/expressions.js' +import DragHandleIcon from './DragHandleIcon.js' +import styles from './styles/FormulaItem.style.js' + +const BEFORE = 'BEFORE' +const AFTER = 'AFTER' + +const maxMsBetweenClicks = 300 + +const TAG_INPUT = 'INPUT' + +const FormulaItem = ({ + id, + label, + value = '', + type, + isLast, + isHighlighted, + overLastDropZone, + onChange, + onClick, + onDoubleClick, + hasFocus, +}) => { + const { + attributes, + listeners, + index, + isDragging, + over, + active, + setNodeRef, + transform, + transition, + } = useSortable({ + id, + data: { label, type, value }, + }) + + const inputRef = useRef(null) + + const [clickTimeoutId, setClickTimeoutId] = useState(null) + + useEffect(() => { + if (hasFocus && inputRef.current) { + // setTimeout seems to be needed in order for the cursor + // to remain in the input. Without it, the cursor disappears + // even though the input still has the focus. + setTimeout(() => { + inputRef.current.focus() + }, 50) + } + }, [inputRef, hasFocus]) + + const activeIndex = active?.data.current.sortable.index || -1 + + const style = transform + ? { + transform: active + ? undefined + : CSS.Translate.toString({ + x: transform.x, + y: transform.y, + scaleX: 1, + scaleY: 1, + }), + transition, + } + : undefined + + let insertPosition + if (over?.id === id) { + // This item is being hovered over by the item being dragged + if (activeIndex === -1) { + //The item being dragged came from the expression options + // so we will insert after + insertPosition = AFTER + } else { + // The item being dragged is being moved in the formula + // so if the item is before the item being dragged, use the + // BEFORE position. Otherwise use the AFTER position + insertPosition = index > activeIndex ? AFTER : BEFORE + } + } else if (isLast && overLastDropZone) { + insertPosition = AFTER + } + + const handleClick = (e) => { + const tagname = e.target.tagName + clearTimeout(clickTimeoutId) + const to = setTimeout(function () { + if (tagname !== TAG_INPUT) { + onClick(id) + } else { + inputRef.current && inputRef.current.focus() + } + }, maxMsBetweenClicks) + setClickTimeoutId(to) + } + + const handleDoubleClick = (e) => { + clearTimeout(clickTimeoutId) + setClickTimeoutId(null) + if (e.target.tagName !== TAG_INPUT) { + onDoubleClick(id) + } else { + inputRef.current && inputRef.current.focus() + } + } + + const handleChange = (e) => onChange({ itemId: id, value: e.target.value }) + + const getContent = () => { + if (type === EXPRESSION_TYPE_NUMBER) { + return ( +
    + {DragHandleIcon} + + + + + +
    + ) + } + + if (type === EXPRESSION_TYPE_DATA) { + return ( + +
    + + {getIcon(DIMENSION_TYPE_DATA_ELEMENT)} + + {label} + +
    +
    + ) + } + + return ( +
    + {label} + +
    + ) + } + + return ( + <> +
    +
    + {getContent()} +
    +
    + + + ) +} + +FormulaItem.propTypes = { + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + onDoubleClick: PropTypes.func.isRequired, + hasFocus: PropTypes.bool, + isHighlighted: PropTypes.bool, + isLast: PropTypes.bool, + overLastDropZone: PropTypes.bool, + value: PropTypes.string, +} + +export default FormulaItem diff --git a/src/components/DataDimension/Calculation/MathOperatorSelector.js b/src/components/DataDimension/Calculation/MathOperatorSelector.js new file mode 100644 index 000000000..dd4d4321a --- /dev/null +++ b/src/components/DataDimension/Calculation/MathOperatorSelector.js @@ -0,0 +1,33 @@ +import PropTypes from 'prop-types' +import React from 'react' +import i18n from '../../../locales/index.js' +import { getOperators } from '../../../modules/expressions.js' +import DraggableOperator from './Operator.js' +import styles from './styles/MathOperatorSelector.style.js' + +const MathOperatorSelector = ({ onDoubleClick }) => ( + <> +
    +

    {i18n.t('Math operators')}

    +
    + {getOperators().map(({ label, value, type }, index) => ( + + ))} +
    +
    + + +) + +MathOperatorSelector.propTypes = { + onDoubleClick: PropTypes.func.isRequired, +} + +export default MathOperatorSelector diff --git a/src/components/DataDimension/Calculation/Operator.js b/src/components/DataDimension/Calculation/Operator.js new file mode 100644 index 000000000..2bd46479b --- /dev/null +++ b/src/components/DataDimension/Calculation/Operator.js @@ -0,0 +1,48 @@ +import { useSortable } from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React from 'react' +import { + EXPRESSION_TYPE_NUMBER, + EXPRESSION_TYPE_OPERATOR, +} from '../../../modules/expressions.js' +import formulaItemStyles from './styles/FormulaItem.style.js' +import styles from './styles/Operator.style.js' + +const Operator = ({ label, value, type, onDoubleClick }) => { + const data = { label, value, type } + const { attributes, listeners, setNodeRef, transform } = useSortable({ + id: `operator-${label}`, + data, + }) + const style = { + transform: CSS.Translate.toString(transform), + } + + return ( +
    +
    onDoubleClick(data)} + > + {label} +
    + + +
    + ) +} + +Operator.propTypes = { + label: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + onDoubleClick: PropTypes.func.isRequired, +} + +export default Operator diff --git a/src/components/DataDimension/Calculation/styles/CalculationModal.style.js b/src/components/DataDimension/Calculation/styles/CalculationModal.style.js new file mode 100644 index 000000000..a15c19c14 --- /dev/null +++ b/src/components/DataDimension/Calculation/styles/CalculationModal.style.js @@ -0,0 +1,68 @@ +import { colors, spacers } from '@dhis2/ui' +import css from 'styled-jsx/css' + +export default css` + .header { + background: ${colors.grey200}; + padding: ${spacers.dp16}; + font-weight: normal; + } + + .header-icon { + padding: 0 ${spacers.dp8}; + vertical-align: text-bottom; + line-height: 14px; + } + + .actions-wrapper { + margin-top: ${spacers.dp16}; + margin-bottom: ${spacers.dp16}; + margin-left: ${spacers.dp4}; + } + + .button-container { + display: inline-flex; + } + + .validate-button { + margin-bottom: ${spacers.dp4}; + } + + .remove-button { + margin-right: ${spacers.dp8}; + } + + .delete-button { + margin-right: ${spacers.dp8}; + } + + .content { + display: flex; + } + + .left-section { + width: 45%; + } + + .right-section { + width: 55%; + padding-left: ${spacers.dp8}; + font-size: 14px; + } + + .validation-message { + margin-left: ${spacers.dp8}; + } + + .validation-error { + color: ${colors.red500}; + } + + .validation-success { + color: ${colors.green500}; + } + + .name-input { + margin-top: ${spacers.dp12}; + } +` diff --git a/src/components/DataDimension/Calculation/styles/DataElementOption.style.js b/src/components/DataDimension/Calculation/styles/DataElementOption.style.js new file mode 100644 index 000000000..f3333d726 --- /dev/null +++ b/src/components/DataDimension/Calculation/styles/DataElementOption.style.js @@ -0,0 +1,41 @@ +import { spacers, colors } from '@dhis2/ui' +import css from 'styled-jsx/css' + +export default css` + .wrapper { + margin-top: ${spacers.dp4}; + } + + .wrapper:last-child { + margin-bottom: ${spacers.dp4}; + } + .draggable-item { + cursor: pointer; + display: inline-flex; + } + + .chip { + display: inline-flex; + background: ${colors.grey200}; + font-size: 14px; + padding: 2px ${spacers.dp8} 2px 2px; + border-radius: 3px; + user-select: none; + } + + .chip:hover { + background: ${colors.grey300}; + } + + .icon, + .label { + line-height: 18px; + } + + .icon { + margin-right: 2px; + display: inline-flex; + vertical-align: text-bottom; + padding-top: 1px; + } +` diff --git a/src/components/DataDimension/Calculation/styles/DataElementSelector.style.js b/src/components/DataDimension/Calculation/styles/DataElementSelector.style.js new file mode 100644 index 000000000..f3f5211bd --- /dev/null +++ b/src/components/DataDimension/Calculation/styles/DataElementSelector.style.js @@ -0,0 +1,79 @@ +import { colors, spacers } from '@dhis2/ui' +import css from 'styled-jsx/css' + +export default css` + .dimension-list-container { + position: relative; + } + + .dimension-list-scrollbox { + position: relative; + width: 100%; + height: 337px; + overflow: hidden; + overflow-y: auto; + border: 1px solid ${colors.grey400}; + } + + .dimension-list-scroller { + position: relative; + min-height: 1px; + padding: 0 ${spacers.dp4}; + } + + .dimension-list-scroller.loading { + filter: blur(2px); + } + + .scroll-detector { + boxsizing: border-box; + width: 100%; + height: 100px; + position: absolute; + bottom: 0; + left: 0; + z-index: -1; + } + + .dimension-list-overlay { + position: absolute; + width: 100%; + height: 100%; + z-index: 2; + top: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; + } + + .filter-wrapper { + padding: ${spacers.dp8}; + border: 1px solid ${colors.grey400}; + border-bottom: 0; + } + + .selector-wrapper { + display: flex; + gap: ${spacers.dp4}; + } + + .sub-header { + font-size: 14px; + font-weight: normal; + margin: 0 0 ${spacers.dp4}; + } + + .group-select { + width: 50%; + margin-top: ${spacers.dp4}; + } + + .empty-list { + text-align: center; + font-size: 14px; + line-height: 16px; + margin: ${spacers.dp24} 0 0; + color: ${colors.grey700}; + } +` diff --git a/src/components/DataDimension/Calculation/styles/DraggingItem.style.js b/src/components/DataDimension/Calculation/styles/DraggingItem.style.js new file mode 100644 index 000000000..60ed94da3 --- /dev/null +++ b/src/components/DataDimension/Calculation/styles/DraggingItem.style.js @@ -0,0 +1,13 @@ +import { colors, spacers } from '@dhis2/ui' +import css from 'styled-jsx/css' + +export default css` + .dragging { + border: 2px solid ${colors.blue500}; + cursor: grab; + } + + .number { + padding: 0 ${spacers.dp8}; + } +` diff --git a/src/components/DataDimension/Calculation/styles/DropZone.style.js b/src/components/DataDimension/Calculation/styles/DropZone.style.js new file mode 100644 index 000000000..3e21fbc12 --- /dev/null +++ b/src/components/DataDimension/Calculation/styles/DropZone.style.js @@ -0,0 +1,52 @@ +import { colors } from '@dhis2/ui' +import css from 'styled-jsx/css' + +export default css` + .first-dropzone { + position: absolute; + top: 0; + left: 0; + z-index: -1; + width: 24px; + height: 28px; + background-color: transparent; + } + + .dragging-over { + z-index: 100; + } + + .empty { + position: relative; + flex-grow: 1; + z-index: 100; + margin-left: -10px; + margin-top: -4px; + } + + .dragging-over::before, + .dragging-over::after { + content: ''; + position: absolute; + } + + /* the vertical line */ + .dragging-over::before { + top: 10px; + width: 4px; + left: 4px; + height: 18px; + background-color: ${colors.blue500}; + } + + /* the circle */ + .dragging-over::after { + top: 0; + left: 0; + width: 12px; + height: 12px; + border: 4px solid ${colors.blue500}; + background: transparent; + border-radius: 12px; + } +` diff --git a/src/components/DataDimension/Calculation/styles/FormulaField.style.js b/src/components/DataDimension/Calculation/styles/FormulaField.style.js new file mode 100644 index 000000000..24b120ec5 --- /dev/null +++ b/src/components/DataDimension/Calculation/styles/FormulaField.style.js @@ -0,0 +1,51 @@ +import { colors, spacers } from '@dhis2/ui' +import css from 'styled-jsx/css' + +export default css` + .formula-field { + border-right: 2px solid ${colors.grey200}; + height: 180px; + overflow: auto; + padding: 6px 12px; + position: relative; + display: flex; + align-items: flex-start; + align-content: flex-start; + flex-wrap: wrap; + gap: ${spacers.dp4} ${spacers.dp8}; + width: 100%; + } + + .container { + position: relative; + } + + .border { + position: absolute; + top: 0; + left: 6px; + height: 180px; + width: calc(100% - 6px); + border-left: 2px solid ${colors.grey200}; + border-top: 2px solid ${colors.grey200}; + border-bottom: 2px solid ${colors.grey200}; + } + + .placeholder { + height: 100%; + display: flex; + flex-direction: column; + gap: ${spacers.dp8}; + align-items: center; + justify-content: center; + margin-top: -28px; + padding: 0 ${spacers.dp32}; + } + + .help-text { + color: ${colors.grey600}; + font-size: 14px; + line-height: 19px; + user-select: none; + } +` diff --git a/src/components/DataDimension/Calculation/styles/FormulaItem.style.js b/src/components/DataDimension/Calculation/styles/FormulaItem.style.js new file mode 100644 index 000000000..6b0998960 --- /dev/null +++ b/src/components/DataDimension/Calculation/styles/FormulaItem.style.js @@ -0,0 +1,149 @@ +import { colors, spacers, theme } from '@dhis2/ui' +import css from 'styled-jsx/css' + +export default css` + .formula-item { + position: relative; + display: flex; + align-items: center; + width: fit-content; + } + + .formula-item:not(.inactive) { + opacity: 0.5; + } + + .content { + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + user-select: none; + height: 24px; + font-size: 14px; + border-radius: 3px; + background: ${colors.grey200}; + } + + .icon { + display: inline-flex; + margin-right: 2px; + } + + .operator { + padding: 0; + width: 24px; + } + + .data, + .number { + padding: 0 ${spacers.dp8} 0 2px; + } + + .operator .label { + margin-bottom: 1px; + } + + .data .label { + max-width: 280px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + /* number input */ + + .number-positioner { + position: relative; + line-height: 18px; + } + + .number-width { + /* Add extra space for number spinner */ + padding: 0 24px 0 0; + visibility: hidden; + } + + .input { + position: absolute; + width: 100%; + left: 0; + background-color: transparent; + border: 1px dashed #a0adba; + padding: 0 0 0 2px; + } + + .input:hover, + .input:focus { + background: white; + border: 1px solid rgba(0, 0, 0, 0.2); + } + + /* highlighted items */ + + .highlighted { + background: ${theme.secondary800}; + color: ${colors.white}; + } + + .highlighted .input { + color: ${colors.white}; + } + + .highlighted .input:hover, + .highlighted .input:active, + .highlighted .input:focus { + color: ${colors.grey900}; + } + + .highlighted :global(.icon path) { + fill: ${colors.white}; + } + + /* DND markers */ + + .inactive.insertBefore .content::before, + .inactive.insertAfter .content::before, + .inactive.insertBefore .content::after, + .inactive.insertAfter .content::after { + content: ''; + position: absolute; + z-index: 100; + } + + /* the vertical line */ + .content::before { + top: 6px; + bottom: 0; + width: 4px; + background-color: ${colors.blue500}; + } + + /* the circle */ + .content::after { + top: -4px; + width: 12px; + height: 12px; + border: 4px solid ${colors.blue500}; + background: transparent; + border-radius: 12px; + } + + .last-item { + flex: 1; + } + + .insertBefore .content::before { + left: -6px; + } + .insertBefore .content::after { + left: -10px; + } + + .insertAfter .content::before { + right: -6px; + } + .insertAfter .content::after { + right: -10px; + } +` diff --git a/src/components/DataDimension/Calculation/styles/MathOperatorSelector.style.js b/src/components/DataDimension/Calculation/styles/MathOperatorSelector.style.js new file mode 100644 index 000000000..98c2214ee --- /dev/null +++ b/src/components/DataDimension/Calculation/styles/MathOperatorSelector.style.js @@ -0,0 +1,23 @@ +import { colors, spacers } from '@dhis2/ui' +import css from 'styled-jsx/css' + +export default css` + .wrapper { + border: 1px solid ${colors.grey400}; + margin-top: ${spacers.dp8}; + } + + .operators { + display: flex; + flex-wrap: wrap; + gap: ${spacers.dp4}; + padding: ${spacers.dp4}; + border-top: 1px solid ${colors.grey400}; + } + + .sub-header { + font-size: 14px; + font-weight: normal; + margin: ${spacers.dp4} ${spacers.dp8}; + } +` diff --git a/src/components/DataDimension/Calculation/styles/Operator.style.js b/src/components/DataDimension/Calculation/styles/Operator.style.js new file mode 100644 index 000000000..71cb46519 --- /dev/null +++ b/src/components/DataDimension/Calculation/styles/Operator.style.js @@ -0,0 +1,13 @@ +import { colors, spacers } from '@dhis2/ui' +import css from 'styled-jsx/css' + +export default css` + .number { + padding: 0 ${spacers.dp8}; + } + + .operator:hover, + .number:hover { + background: ${colors.grey300}; + } +` diff --git a/src/components/DataDimension/DataDimension.js b/src/components/DataDimension/DataDimension.js index 400c7bced..52f2c33ea 100644 --- a/src/components/DataDimension/DataDimension.js +++ b/src/components/DataDimension/DataDimension.js @@ -1,3 +1,4 @@ +import { useConfig } from '@dhis2/app-runtime' import PropTypes from 'prop-types' import React from 'react' import { DIMENSION_ID_DATA } from '../../modules/predefinedDimensions.js' @@ -8,7 +9,14 @@ const DataDimension = ({ selectedDimensions, displayNameProp, infoBoxMessage, + onCalculationSave, }) => { + const { serverVersion } = useConfig() + const supportsEDI = + `${serverVersion.major}.${serverVersion.minor}.${ + serverVersion.patch || 0 + }` >= '2.40.0' + const onSelectItems = (selectedItem) => onSelect({ dimensionId: DIMENSION_ID_DATA, @@ -16,6 +24,7 @@ const DataDimension = ({ id: item.value, name: item.label, type: item.type, + expression: item.expression, })), }) @@ -26,11 +35,15 @@ const DataDimension = ({ label: item.name, isActive: item.isActive, type: item.type, + expression: item.expression, + access: item.access, }))} onSelect={onSelectItems} displayNameProp={displayNameProp} infoBoxMessage={infoBoxMessage} dataTest={'data-dimension'} + supportsEDI={supportsEDI} + onEDISave={onCalculationSave} /> ) } @@ -39,12 +52,16 @@ DataDimension.propTypes = { displayNameProp: PropTypes.string.isRequired, selectedDimensions: PropTypes.arrayOf( PropTypes.shape({ + expression: PropTypes.string, id: PropTypes.string, + isActive: PropTypes.bool, name: PropTypes.string, + type: PropTypes.string, }) ).isRequired, onSelect: PropTypes.func.isRequired, infoBoxMessage: PropTypes.string, + onCalculationSave: PropTypes.func, } DataDimension.defaultProps = { diff --git a/src/components/DataDimension/DataTypeSelector.js b/src/components/DataDimension/DataTypeSelector.js index e63db1e9c..b5485e9ed 100644 --- a/src/components/DataDimension/DataTypeSelector.js +++ b/src/components/DataDimension/DataTypeSelector.js @@ -5,10 +5,16 @@ import i18n from '../../locales/index.js' import { DIMENSION_TYPE_ALL, dataTypeMap as dataTypes, + DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, } from '../../modules/dataTypes.js' import styles from './styles/DataTypeSelector.style.js' -const DataTypeSelector = ({ currentDataType, onChange, dataTest }) => ( +const DataTypeSelector = ({ + currentDataType, + onChange, + dataTest, + includeCalculations, +}) => (
    ( label={i18n.t('All types')} dataTest={`${dataTest}-option-all`} /> - {Object.values(dataTypes).map((type) => ( - - ))} + {Object.values(dataTypes) + .filter( + (type) => + type.id !== DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM || + includeCalculations + ) + .map((type) => ( + + ))}
    @@ -40,6 +52,7 @@ DataTypeSelector.propTypes = { currentDataType: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, dataTest: PropTypes.string, + includeCalculations: PropTypes.bool, } export default DataTypeSelector diff --git a/src/components/DataDimension/ItemSelector.js b/src/components/DataDimension/ItemSelector.js index e02d14fb8..6256be9ba 100644 --- a/src/components/DataDimension/ItemSelector.js +++ b/src/components/DataDimension/ItemSelector.js @@ -1,33 +1,22 @@ import { useDataEngine } from '@dhis2/app-runtime' -import { - Transfer, - InputField, - IconInfo16, - IconDimensionDataSet16, - IconDimensionIndicator16, - IconDimensionEventDataItem16, - IconDimensionProgramIndicator16, -} from '@dhis2/ui' +import { Transfer, InputField, IconInfo16, Button, IconAdd24 } from '@dhis2/ui' import PropTypes from 'prop-types' import React, { useState } from 'react' import { apiFetchOptions } from '../../api/dimensions.js' -import DataElementIcon from '../../assets/DimensionItemIcons/DataElementIcon.js' -import GenericIcon from '../../assets/DimensionItemIcons/GenericIcon.js' import i18n from '../../locales/index.js' import { DATA_SETS_CONSTANTS, REPORTING_RATE } from '../../modules/dataSets.js' import { dataTypeMap as dataTypes, DIMENSION_TYPE_ALL, DIMENSION_TYPE_DATA_ELEMENT, - DIMENSION_TYPE_DATA_ELEMENT_OPERAND, DIMENSION_TYPE_DATA_SET, DIMENSION_TYPE_EVENT_DATA_ITEM, DIMENSION_TYPE_PROGRAM_INDICATOR, DIMENSION_TYPE_INDICATOR, TOTALS, - DIMENSION_TYPE_PROGRAM_DATA_ELEMENT, - DIMENSION_TYPE_PROGRAM_ATTRIBUTE, + DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, } from '../../modules/dataTypes.js' +import { getIcon, getTooltipText } from '../../modules/dimensionListItem.js' import { TRANSFER_HEIGHT, TRANSFER_OPTIONS_WIDTH, @@ -36,6 +25,7 @@ import { import { useDebounce, useDidUpdateEffect } from '../../modules/utils.js' import styles from '../styles/DimensionSelector.style.js' import { TransferOption } from '../TransferOption.js' +import CalculationModal from './Calculation/CalculationModal.js' import DataTypeSelector from './DataTypeSelector.js' import GroupSelector from './GroupSelector.js' @@ -50,6 +40,7 @@ const LeftHeader = ({ setSubGroup, displayNameProp, dataTest, + supportsEDI, }) => ( <>
    @@ -66,18 +57,20 @@ const LeftHeader = ({ currentDataType={dataType} onChange={setDataType} dataTest={`${dataTest}-data-types-select-field`} + includeCalculations={supportsEDI} /> - {dataTypes[dataType] && ( - - )} + {dataTypes[dataType] && + dataType !== DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM && ( + + )}
    @@ -94,6 +87,7 @@ LeftHeader.propTypes = { setSearchTerm: PropTypes.func, setSubGroup: PropTypes.func, subGroup: PropTypes.string, + supportsEDI: PropTypes.bool, } const EmptySelection = () => ( @@ -225,6 +219,8 @@ const ItemSelector = ({ displayNameProp, infoBoxMessage, dataTest, + supportsEDI, + onEDISave, }) => { const [state, setState] = useState({ searchTerm: '', @@ -235,11 +231,12 @@ const ItemSelector = ({ loading: true, nextPage: 1, }) + const [currentCalculation, setCurrentCalculation] = useState() const dataEngine = useDataEngine() const setSearchTerm = (searchTerm) => setState((state) => ({ ...state, searchTerm })) const setFilter = (filter) => setState((state) => ({ ...state, filter })) - const debouncedSearchTerm = useDebounce(state.searchTerm, 200) + const debouncedSearchTerm = useDebounce(state.searchTerm, 500) const fetchItems = async (page) => { setState((state) => ({ ...state, loading: true })) const result = await apiFetchOptions({ @@ -264,6 +261,7 @@ const ItemSelector = ({ value: `${item.id}.${metric.id}`, disabled: item.disabled, type: item.dimensionItemType, + expression: item.expression, }) } else { DATA_SETS_CONSTANTS.forEach((metric) => { @@ -272,6 +270,7 @@ const ItemSelector = ({ value: `${item.id}.${metric.id}`, disabled: item.disabled, type: item.dimensionItemType, + expression: item.expression, }) }) } @@ -281,6 +280,7 @@ const ItemSelector = ({ value: item.id, disabled: item.disabled, type: item.dimensionItemType, + expression: item.expression, }) } }) @@ -290,11 +290,11 @@ const ItemSelector = ({ options: page > 1 ? [...state.options, ...newOptions] : newOptions, nextPage: result.nextPage, })) - /* The following handles a very specific edge-case where the user can select all items from a - page and then reopen the modal. Usually Transfer triggers the onEndReached when the end of - the page is reached (scrolling down) or if too few items are on the left side (e.g. selecting - 49 items from page 1, leaving only 1 item on the left side). However, due to the way Transfer - works, if 0 items are available, more items are fetched, but all items are already selected + /* The following handles a very specific edge-case where the user can select all items from a + page and then reopen the modal. Usually Transfer triggers the onEndReached when the end of + the page is reached (scrolling down) or if too few items are on the left side (e.g. selecting + 49 items from page 1, leaving only 1 item on the left side). However, due to the way Transfer + works, if 0 items are available, more items are fetched, but all items are already selected (leaving 0 items on the left side still), the onReachedEnd won't trigger. Hence the code below: IF there is a next page AND some options were just fetched AND you have the same or more selected items than fetched items AND all fetched items are already selected -> fetch more! @@ -331,6 +331,9 @@ const ItemSelector = ({ value, label: matchingItem.label, type: matchingItem.type, + ...(matchingItem.expression + ? { expression: matchingItem.expression } + : {}), } }) ) @@ -348,116 +351,154 @@ const ItemSelector = ({ [...state.options, ...selectedItems].find( (item) => item.value === value )?.type - const getTooltipText = (itemType) => { - switch (itemType) { - case DIMENSION_TYPE_DATA_ELEMENT_OPERAND: - return dataTypes[DIMENSION_TYPE_DATA_ELEMENT].getItemName() - case REPORTING_RATE: - return dataTypes[DIMENSION_TYPE_DATA_SET].getItemName() - case DIMENSION_TYPE_PROGRAM_DATA_ELEMENT: - case DIMENSION_TYPE_PROGRAM_ATTRIBUTE: - return dataTypes[DIMENSION_TYPE_EVENT_DATA_ITEM].getItemName() - default: - return dataTypes[itemType]?.getItemName() + + const onSaveCalculation = async ({ id, name, expression, isNew }) => { + onEDISave({ + id, + name, + expression, + type: DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, + }) + + // close the modal + setCurrentCalculation() + + // reload the list of options + fetchItems(1) + + if (isNew) { + // select the new calculation + onSelect([ + ...selectedItems, + { + value: id, + label: name, + expression, + type: DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, + }, + ]) } } - const getIcon = (itemType) => { - switch (itemType) { - case DIMENSION_TYPE_INDICATOR: - return - case DIMENSION_TYPE_DATA_ELEMENT_OPERAND: - case DIMENSION_TYPE_DATA_ELEMENT: - return DataElementIcon - case REPORTING_RATE: - return - case DIMENSION_TYPE_EVENT_DATA_ITEM: - case DIMENSION_TYPE_PROGRAM_DATA_ELEMENT: - case DIMENSION_TYPE_PROGRAM_ATTRIBUTE: - return - case DIMENSION_TYPE_PROGRAM_INDICATOR: - return - default: - return GenericIcon - } + + const onDeleteCalculation = ({ id }) => { + // close the modal + setCurrentCalculation() + + // reload the list of options + fetchItems(1) + + // unselect the deleted calculation + onSelect([...selectedItems.filter((item) => item.value !== id)]) } + return ( - onChange(selected)} - selected={selectedItems.map((item) => item.value)} - options={[...state.options, ...selectedItems]} - loading={state.loading} - loadingPicked={state.loading} - sourceEmptyPlaceholder={ - - } - onEndReached={onEndReached} - leftHeader={ - { - setFilter({ - ...state.filter, - dataType, - group: null, - subGroup: - dataType === DIMENSION_TYPE_DATA_ELEMENT - ? TOTALS - : null, - }) - }} - group={state.filter.group} - setGroup={(group) => { - setFilter({ ...state.filter, group }) - }} - subGroup={state.filter.subGroup} - setSubGroup={(subGroup) => { - setFilter({ ...state.filter, subGroup }) - }} - searchTerm={state.searchTerm} - setSearchTerm={setSearchTerm} + <> + onChange(selected)} + selected={selectedItems.map((item) => item.value)} + options={[...state.options, ...selectedItems]} + loading={state.loading} + loadingPicked={state.loading} + sourceEmptyPlaceholder={ + + } + onEndReached={onEndReached} + leftHeader={ + { + setFilter({ + ...state.filter, + dataType, + group: null, + subGroup: + dataType === DIMENSION_TYPE_DATA_ELEMENT + ? TOTALS + : null, + }) + }} + group={state.filter.group} + setGroup={(group) => { + setFilter({ ...state.filter, group }) + }} + subGroup={state.filter.subGroup} + setSubGroup={(subGroup) => { + setFilter({ ...state.filter, subGroup }) + }} + searchTerm={state.searchTerm} + setSearchTerm={setSearchTerm} + displayNameProp={displayNameProp} + dataTest={`${dataTest}-left-header`} + supportsEDI={supportsEDI} + /> + } + leftFooter={ + supportsEDI ? ( +
    + +
    + ) : undefined + } + enableOrderChange + height={TRANSFER_HEIGHT} + optionsWidth={TRANSFER_OPTIONS_WIDTH} + selectedWidth={TRANSFER_SELECTED_WIDTH} + selectedEmptyComponent={} + rightHeader={} + rightFooter={rightFooter} + renderOption={(props) => ( + + setCurrentCalculation({ + id: props.value, + name: props.label, + expression: props.expression, + }) + : undefined + } + /* eslint-enable react/prop-types */ + /> + )} + dataTest={`${dataTest}-transfer`} + /> + {currentCalculation && supportsEDI && ( + setCurrentCalculation()} + onDelete={onDeleteCalculation} displayNameProp={displayNameProp} - dataTest={`${dataTest}-left-header`} - /> - } - enableOrderChange - height={TRANSFER_HEIGHT} - optionsWidth={TRANSFER_OPTIONS_WIDTH} - selectedWidth={TRANSFER_SELECTED_WIDTH} - selectedEmptyComponent={} - rightHeader={} - rightFooter={rightFooter} - renderOption={(props) => ( - )} - dataTest={`${dataTest}-transfer`} - /> + + ) } @@ -474,8 +515,11 @@ ItemSelector.propTypes = { value: PropTypes.string.isRequired, isActive: PropTypes.bool, type: PropTypes.string, + expression: PropTypes.string, }) ), + supportsEDI: PropTypes.bool, + onEDISave: PropTypes.func, } ItemSelector.defaultProps = { diff --git a/src/components/TransferOption.js b/src/components/TransferOption.js index 6e7c9ccda..81b7c1484 100644 --- a/src/components/TransferOption.js +++ b/src/components/TransferOption.js @@ -1,4 +1,4 @@ -import { Tooltip } from '@dhis2/ui' +import { Tooltip, IconEdit16 } from '@dhis2/ui' import cx from 'classnames' import PropTypes from 'prop-types' import React from 'react' @@ -16,6 +16,7 @@ export const TransferOption = ({ active, tooltipText, dataTest, + onEditClick, }) => { const renderContent = () => (
    { if (disabled) { return @@ -37,10 +39,22 @@ export const TransferOption = ({ } onDoubleClick({ label, value }, event) }} - data-test={`${dataTest}-content`} > {icon} {label} + {onEditClick && ( + { + e.stopPropagation() + onEditClick() + }} + data-test={`${dataTest}-edit-button`} + > + + + )} +
    ) @@ -76,4 +90,5 @@ TransferOption.propTypes = { tooltipText: PropTypes.string, onClick: PropTypes.func, onDoubleClick: PropTypes.func, + onEditClick: PropTypes.func, } diff --git a/src/components/styles/DimensionSelector.style.js b/src/components/styles/DimensionSelector.style.js index 0c3b88306..e142718b8 100644 --- a/src/components/styles/DimensionSelector.style.js +++ b/src/components/styles/DimensionSelector.style.js @@ -34,4 +34,7 @@ export default css` font-size: 12px; line-height: 16px; } + .calculation-button { + margin: ${spacers.dp8} 0; + } ` diff --git a/src/components/styles/TransferOption.style.js b/src/components/styles/TransferOption.style.js index 5b8eb8c93..efc5c5fd8 100644 --- a/src/components/styles/TransferOption.style.js +++ b/src/components/styles/TransferOption.style.js @@ -67,4 +67,22 @@ export default css` .label { font-size: 14px; } + + .edit { + height: 16px; + margin-top: 1px; + margin-left: ${spacers.dp8}; + cursor: pointer; + } + + .edit:hover { + background-color: rgba(0, 0, 0, 0.12); + outline: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 3px; + } + + .highlighted .edit:hover { + background-color: rgba(255, 255, 255, 0.12); + outline: 1px solid rgba(255, 255, 255, 0.12); + } ` diff --git a/src/index.js b/src/index.js index 1c3aab61a..e21a38df7 100644 --- a/src/index.js +++ b/src/index.js @@ -345,4 +345,5 @@ export { DIMENSION_TYPE_ORGANISATION_UNIT, DIMENSION_TYPE_PERIOD, DIMENSION_TYPE_ORGANISATION_UNIT_GROUP_SET, + DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, } from './modules/dataTypes.js' diff --git a/src/modules/__tests__/expressions.spec.js b/src/modules/__tests__/expressions.spec.js new file mode 100644 index 000000000..4208a1eba --- /dev/null +++ b/src/modules/__tests__/expressions.spec.js @@ -0,0 +1,166 @@ +import { + validateExpression, + parseArrayToExpression, + parseExpressionToArray, + EXPRESSION_TYPE_DATA, + EXPRESSION_TYPE_NUMBER, + EXPRESSION_TYPE_OPERATOR, + INVALID_EXPRESSION, + getItemIdsFromExpression, +} from '../expressions.js' + +const invalidTestExpressions = [ + { + message: + 'Formula is empty. Add items to the formula from the lists on the left.', + expressions: [''], + }, + // { + // message: 'Consecutive math operators', + // expressions: ['5+-', '5+++', '4+9-*', '5++9'], + // }, + { + message: 'Consecutive data elements', + expressions: ['#{cYeuwXTCPkU}#{Jtf34kNZhzP}'], + }, + { + message: 'Starts or ends with a math operator', + expressions: ['+', '+1', '1-2/', '*((#{cYeuwXTCPkU}*#{Jtf34kNZhzP})))'], + }, + { + message: 'Empty parentheses', + expressions: ['#{cYeuwXTCPkU}*()'], + }, + { + message: 'Missing left parenthesis (', + expressions: [ + ')', + '5)', + '((#{cYeuwXTCPkU}*#{P3jJH5Tu5VC.S34ULMcHMca})))', + ], + }, + { + message: 'Missing right parenthesis )', + expressions: ['(', '(5', '((#{cYeuwXTCPkU}*#{Jtf34kNZhzP})'], + }, +] + +const validTestExpressions = [ + '5+9', + '((#{cYeuwXTCPkU}*#{Jtf34kNZhzP}))#{iKGjnOOaPlE}', + '#{P3jJH5Tu5VC.S34ULMcHMca}*#{Jtf34kNZhzP}', + '(5)+9', + '(5+9)', + '10/-5', +] + +describe('validateExpression', () => { + invalidTestExpressions.forEach(({ expressions, message }) => { + expressions.forEach((exp) => { + test(`Fails: ${message}`, () => { + expect(validateExpression(exp)).toEqual({ + status: INVALID_EXPRESSION, + message, + }) + }) + }) + }) + + validTestExpressions.forEach((exp) => { + test(`Passes validation: ${exp}`, () => { + expect(validateExpression(exp)).toEqual(undefined) + }) + }) +}) + +describe('parseArrayToExpression', () => { + test('exp 1', () => { + const expressionArray = [ + { + label: 'abc123', + value: '#{abc123}', + type: EXPRESSION_TYPE_DATA, + }, + { label: '+', value: '+', type: EXPRESSION_TYPE_OPERATOR }, + { + label: 'def456.xyz999', + value: '#{def456.xyz999}', + type: EXPRESSION_TYPE_DATA, + }, + { label: '/', value: '/', type: EXPRESSION_TYPE_OPERATOR }, + { label: '10', value: '10', type: EXPRESSION_TYPE_NUMBER }, + ] + const expected = '#{abc123}+#{def456.xyz999}/10' + + expect(parseArrayToExpression(expressionArray)).toEqual(expected) + }) +}) + +describe('parseExpressionToArray', () => { + test('exp 1', () => { + const expression = '#{abc123}/10*99' + const expected = [ + { + label: 'abc123', + value: '#{abc123}', + type: EXPRESSION_TYPE_DATA, + }, + { label: '/', value: '/', type: EXPRESSION_TYPE_OPERATOR }, + { label: '10', value: '10', type: EXPRESSION_TYPE_NUMBER }, + { label: '×', value: '*', type: EXPRESSION_TYPE_OPERATOR }, + { label: '99', value: '99', type: EXPRESSION_TYPE_NUMBER }, + ] + + expect(parseExpressionToArray(expression)).toEqual(expected) + }) +}) + +describe('getItemIdsFromExpression', () => { + test('exp 1', () => { + const expression = '#{abc123}/10*99' + const expected = ['abc123'] + + expect(getItemIdsFromExpression(expression)).toEqual(expected) + }) + + test('exp 2', () => { + const expression = '#{abc123}/10*#{def456}' + const expected = ['abc123', 'def456'] + + expect(getItemIdsFromExpression(expression)).toEqual(expected) + }) + + test('exp 3', () => { + const expression = '#{abc123}/10*#{def456.xyz999}' + const expected = ['abc123', 'def456.xyz999'] + + expect(getItemIdsFromExpression(expression)).toEqual(expected) + }) + + test('exp 4', () => { + const expression = '#{abc123}/10*#{def456.xyz999}+#{ghi789}' + const expected = ['abc123', 'def456.xyz999', 'ghi789'] + + expect(getItemIdsFromExpression(expression)).toEqual(expected) + }) + + test('exp 5', () => { + const expression = '#{abc123}/10*#{def456.xyz999}+#{ghi789}+#{jkl000}' + const expected = ['abc123', 'def456.xyz999', 'ghi789', 'jkl000'] + + expect(getItemIdsFromExpression(expression)).toEqual(expected) + }) + + test('exp 6', () => { + const expression = '5/10' + const expected = [] + + expect(getItemIdsFromExpression(expression)).toEqual(expected) + }) + + test('exp 6', () => { + const expected = [] + + expect(getItemIdsFromExpression()).toEqual(expected) + }) +}) diff --git a/src/modules/__tests__/hash.spec.js b/src/modules/__tests__/hash.spec.js new file mode 100644 index 000000000..233ad6edb --- /dev/null +++ b/src/modules/__tests__/hash.spec.js @@ -0,0 +1,125 @@ +import { DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM } from '../dataTypes.js' +import { getExpressionHashFromVisualization, getHash } from '../hash.js' + +describe('getHash', () => { + const textInput = 'Raymond Luxury Yacht' + + it('accepts a string and returns a hash', () => { + const hash = getHash(textInput) + + expect(typeof hash).toBe('string') + expect(hash).not.toBe(textInput) + }) + + it('is deterministic', () => { + expect(getHash(textInput)).toBe(getHash(textInput)) + }) + + it('returns undefined for invalid input', () => { + const unsupportedTypes = ['', 1, true, null, undefined, {}, []] + + unsupportedTypes.forEach((type) => + expect(getHash(type)).toBe(undefined) + ) + }) +}) + +describe('getExpressionHashFromVisualization', () => { + const edi1 = { + id: 'OdiHJayrsKo', + dimensionItemType: DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, + expression: '#{abc} * 10', + } + + const edi2 = { + id: 'Uvn6LCg7dVU', + dimensionItemType: DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, + expression: '#{abc} * 20', + } + + const dxWithEdi = { + dimension: 'dx', + items: [edi1, edi2], + } + + it('generates a hash (columns)', () => { + expect( + typeof getExpressionHashFromVisualization({ + columns: [dxWithEdi], + rows: [], + filters: [], + }) + ).toBe('string') + }) + + it('generates a hash (rows)', () => { + expect( + typeof getExpressionHashFromVisualization({ + columns: [], + rows: [dxWithEdi], + filters: [], + }) + ).toBe('string') + }) + + it('generates a hash (filters)', () => { + expect( + typeof getExpressionHashFromVisualization({ + columns: [], + rows: [], + filters: [dxWithEdi], + }) + ).toBe('string') + }) + + it('does not generate a hash when there are no dimensions', () => { + expect( + getExpressionHashFromVisualization({ + columns: [], + rows: [], + filters: [], + }) + ).toBe(undefined) + }) + + it('does not generate a hash when there are no EDI dimensions', () => { + expect( + getExpressionHashFromVisualization({ + columns: [ + { + id: 'OdiHJayrsKo', + dimensionItemType: 'INDICATOR', + }, + ], + rows: [], + filters: [], + }) + ).toBe(undefined) + }) + + it('sorts the edi objects by id before generating the hash to optimize caching', () => { + expect( + getExpressionHashFromVisualization({ + columns: [ + { + dimension: 'dx', + items: [edi1, edi2], + }, + ], + rows: [], + filters: [], + }) + ).toBe( + getExpressionHashFromVisualization({ + columns: [ + { + dimension: 'dx', + items: [edi2, edi1], + }, + ], + rows: [], + filters: [], + }) + ) + }) +}) diff --git a/src/modules/__tests__/parseExpression.spec.js b/src/modules/__tests__/parseExpression.spec.js new file mode 100644 index 000000000..15b5f9564 --- /dev/null +++ b/src/modules/__tests__/parseExpression.spec.js @@ -0,0 +1,139 @@ +import { parseExpression } from '../expressions.js' + +test('matches numbers and operators', () => { + expect(parseExpression('1+2-3*4/5')).toEqual([ + '1', + '+', + '2', + '-', + '3', + '*', + '4', + '/', + '5', + ]) +}) + +test('matches #{} with letters, numbers and operators', () => { + expect(parseExpression('#{abc123}+100-200*300/400')).toEqual([ + '#{abc123}', + '+', + '100', + '-', + '200', + '*', + '300', + '/', + '400', + ]) +}) + +test('matches numbers and operators with brackets', () => { + expect(parseExpression('1+(2-3)*4/5')).toEqual([ + '1', + '+', + '(', + '2', + '-', + '3', + ')', + '*', + '4', + '/', + '5', + ]) +}) + +test('matches #{} with letters, numbers and operators with brackets', () => { + expect(parseExpression('(100-200)+#{abc123}*300/400')).toEqual([ + '(', + '100', + '-', + '200', + ')', + '+', + '#{abc123}', + '*', + '300', + '/', + '400', + ]) +}) + +test('matches #{} with numbers and operators with brackets', () => { + expect(parseExpression('#{123}+(10-200)*3000/40000')).toEqual([ + '#{123}', + '+', + '(', + '10', + '-', + '200', + ')', + '*', + '3000', + '/', + '40000', + ]) +}) + +test('matches #{} with letters and numbers only', () => { + expect(parseExpression('#{abc123}')).toEqual(['#{abc123}']) +}) + +test('matches #{} with numbers only', () => { + expect(parseExpression('#{123}')).toEqual(['#{123}']) +}) + +test('matches #{} with letters only', () => { + expect(parseExpression('#{abc}')).toEqual(['#{abc}']) +}) + +test('matches #{} with no input', () => { + expect(parseExpression('')).toEqual([]) +}) + +test('matches multiple #{} with operators', () => { + expect(parseExpression('#{abc123}+#{def456}')).toEqual([ + '#{abc123}', + '+', + '#{def456}', + ]) +}) + +test('matches multiple #{} containing dots with operators', () => { + expect(parseExpression('#{abc123.xyz999}+#{def456.xyz999}')).toEqual([ + '#{abc123.xyz999}', + '+', + '#{def456.xyz999}', + ]) +}) + +test('matches multiple #{} with operators with brackets', () => { + expect(parseExpression('(#{abc123}/#{def456})*#{ghi789}')).toEqual([ + '(', + '#{abc123}', + '/', + '#{def456}', + ')', + '*', + '#{ghi789}', + ]) +}) + +test('matches on decimal numbers', () => { + expect(parseExpression('#{abc123}*1.2/#{def456}')).toEqual([ + '#{abc123}', + '*', + '1.2', + '/', + '#{def456}', + ]) +}) + +test('matches on decimal numbers with #{} containing dots', () => { + expect(parseExpression('1.2+#{abc123.xyz999}')).toEqual([ + '1.2', + '+', + '#{abc123.xyz999}', + ]) +}) diff --git a/src/modules/dataTypes.js b/src/modules/dataTypes.js index 39b5be806..7a7664726 100644 --- a/src/modules/dataTypes.js +++ b/src/modules/dataTypes.js @@ -18,6 +18,8 @@ export const DIMENSION_TYPE_PERIOD = 'PERIOD' export const DIMENSION_TYPE_ORGANISATION_UNIT = 'ORGANISATION_UNIT' export const DIMENSION_TYPE_ORGANISATION_UNIT_GROUP_SET = 'ORGANISATION_UNIT_GROUP_SET' +export const DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM = + 'EXPRESSION_DIMENSION_ITEM' export const TOTALS = 'totals' export const DETAIL = 'detail' @@ -93,6 +95,11 @@ export const dataTypeMap = { getGroupEmptyLabel: () => i18n.t('No programs found'), getGroupLoadingLabel: () => i18n.t('Loading programs'), }, + [DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM]: { + id: DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, + getName: () => i18n.t('Calculations'), + getItemName: () => i18n.t('Calculation'), + }, } export function defaultGroupId(dataType) { diff --git a/src/modules/dimensionListItem.js b/src/modules/dimensionListItem.js new file mode 100644 index 000000000..38d02103b --- /dev/null +++ b/src/modules/dimensionListItem.js @@ -0,0 +1,61 @@ +import { + IconDimensionDataSet16, + IconDimensionIndicator16, + IconDimensionEventDataItem16, + IconDimensionProgramIndicator16, +} from '@dhis2/ui' +import React from 'react' +import DataElementIcon from '../assets/DimensionItemIcons/DataElementIcon.js' +import GenericIcon from '../assets/DimensionItemIcons/GenericIcon.js' +import CalculationIcon from './../assets/DimensionItemIcons/CalculationIcon.js' +import { REPORTING_RATE } from './dataSets.js' +import { + DIMENSION_TYPE_DATA_ELEMENT, + DIMENSION_TYPE_DATA_ELEMENT_OPERAND, + DIMENSION_TYPE_DATA_SET, + DIMENSION_TYPE_EVENT_DATA_ITEM, + DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, + DIMENSION_TYPE_PROGRAM_ATTRIBUTE, + DIMENSION_TYPE_PROGRAM_DATA_ELEMENT, + dataTypeMap as dataTypes, + DIMENSION_TYPE_INDICATOR, + DIMENSION_TYPE_PROGRAM_INDICATOR, +} from './dataTypes.js' + +export const getTooltipText = ({ type, expression }) => { + if (type === DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM && expression) { + return dataTypes[DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM].getItemName() + } + switch (type) { + case DIMENSION_TYPE_DATA_ELEMENT_OPERAND: + return dataTypes[DIMENSION_TYPE_DATA_ELEMENT].getItemName() + case REPORTING_RATE: + return dataTypes[DIMENSION_TYPE_DATA_SET].getItemName() + case DIMENSION_TYPE_PROGRAM_DATA_ELEMENT: + case DIMENSION_TYPE_PROGRAM_ATTRIBUTE: + return dataTypes[DIMENSION_TYPE_EVENT_DATA_ITEM].getItemName() + default: + return dataTypes[type]?.getItemName() + } +} +export const getIcon = (type) => { + switch (type) { + case DIMENSION_TYPE_INDICATOR: + return + case DIMENSION_TYPE_DATA_ELEMENT_OPERAND: + case DIMENSION_TYPE_DATA_ELEMENT: + return DataElementIcon + case REPORTING_RATE: + return + case DIMENSION_TYPE_EVENT_DATA_ITEM: + case DIMENSION_TYPE_PROGRAM_DATA_ELEMENT: + case DIMENSION_TYPE_PROGRAM_ATTRIBUTE: + return + case DIMENSION_TYPE_PROGRAM_INDICATOR: + return + case DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM: + return CalculationIcon + default: + return GenericIcon + } +} diff --git a/src/modules/expressions.js b/src/modules/expressions.js new file mode 100644 index 000000000..99eba9dcf --- /dev/null +++ b/src/modules/expressions.js @@ -0,0 +1,119 @@ +import i18n from '../locales/index.js' + +export const EXPRESSION_TYPE_NUMBER = 'EXPRESSION_TYPE_NUMBER' +export const EXPRESSION_TYPE_OPERATOR = 'EXPRESSION_TYPE_OPERATOR' +export const EXPRESSION_TYPE_DATA = 'EXPRESSION_TYPE_DATA' + +export const VALID_EXPRESSION = 'OK' +export const INVALID_EXPRESSION = 'ERROR' + +export const getOperators = () => [ + { value: '+', label: '+', type: EXPRESSION_TYPE_OPERATOR }, + { value: '-', label: '-', type: EXPRESSION_TYPE_OPERATOR }, + { value: '*', label: '×', type: EXPRESSION_TYPE_OPERATOR }, + { value: '/', label: '/', type: EXPRESSION_TYPE_OPERATOR }, + { value: '(', label: '(', type: EXPRESSION_TYPE_OPERATOR }, + { value: ')', label: ')', type: EXPRESSION_TYPE_OPERATOR }, + { + value: '', + label: i18n.t('Number'), + type: EXPRESSION_TYPE_NUMBER, + }, +] + +export const parseExpression = (input) => { + const regex = /(#{[a-zA-Z0-9#.]+}|[+\-*/()])|(\d+(\.\d+)?)/g + return input.match(regex) || [] +} + +export const parseExpressionToArray = (expression = '', metadata = []) => { + return ( + parseExpression(expression).map((value) => { + if (value.startsWith('#{') && value.endsWith('}')) { + const id = value.slice(2, -1) + const label = + metadata.find((item) => item.id === id)?.name || id + return { value, label, type: EXPRESSION_TYPE_DATA } + } + + if (isNaN(value)) { + return { + value, + label: getOperators().find((op) => op.value === value) + .label, + type: EXPRESSION_TYPE_OPERATOR, + } + } + + return { + value, + label: value, + type: EXPRESSION_TYPE_NUMBER, + } + }) || [] + ) +} + +export const parseArrayToExpression = (input = []) => + input.map((item) => item.value).join('') + +export const validateExpression = (expression) => { + let result + const leftParenthesisCount = expression.split('(').length - 1 + const rightParenthesisCount = expression.split(')').length - 1 + + if (!expression) { + // empty formula + result = { + status: INVALID_EXPRESSION, + message: i18n.t( + 'Formula is empty. Add items to the formula from the lists on the left.' + ), + } + // TODO: reimplement this but allow negative values, e.g. 10 / -5 + // } else if (/[-+/*]{2,}/.test(expression)) { + // // two math operators next to each other + // result = { + // status: INVALID_EXPRESSION, + // message: i18n.t('Consecutive math operators'), + // } + } else if (/}#/.test(expression)) { + // two data elements next to each other + result = { + status: INVALID_EXPRESSION, + message: i18n.t('Consecutive data elements'), + } + } else if (/^[+\-*/]|[+\-*/]$/.test(expression)) { + // starting or ending with a math operator + result = { + status: INVALID_EXPRESSION, + message: i18n.t('Starts or ends with a math operator'), + } + } else if (/\(\)/.test(expression)) { + // contains an empty set of parentheses + result = { + status: INVALID_EXPRESSION, + message: i18n.t('Empty parentheses'), + } + } else if (leftParenthesisCount > rightParenthesisCount) { + // ( but no ) + result = { + status: INVALID_EXPRESSION, + message: i18n.t('Missing right parenthesis )'), + } + } else if (rightParenthesisCount > leftParenthesisCount) { + // ) but no ( + result = { + status: INVALID_EXPRESSION, + message: i18n.t('Missing left parenthesis ('), + } + } + + return result +} + +export const getItemIdsFromExpression = (expression = '') => { + const regex = /#{([a-zA-Z0-9#]+.*?)}/g + const matches = expression.match(regex) + return matches ? matches.map((match) => match.slice(2, -1)) : [] +} diff --git a/src/modules/hash.js b/src/modules/hash.js new file mode 100644 index 000000000..2b33b15c4 --- /dev/null +++ b/src/modules/hash.js @@ -0,0 +1,22 @@ +import SHA1 from 'crypto-js/sha1' +import { DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM } from './dataTypes.js' +import { layoutGetAllItems } from './layout/layoutGetAllItems.js' + +const isValidValue = (value) => typeof value === 'string' && value.length + +export const getHash = (value) => + isValidValue(value) ? SHA1(value).toString() : undefined + +export const getExpressionHashFromVisualization = (visualization) => + getHash( + layoutGetAllItems(visualization) + ?.filter( + (item) => + item.dimensionItemType === + DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM && + isValidValue(item.expression) + ) + .sort((i1, i2) => (i1.id < i2.id ? -1 : i1.id > i2.id ? 1 : 0)) + .map((edi) => edi.expression) + .join('') + ) diff --git a/yarn.lock b/yarn.lock index 6d7f4cbab..77e2352c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2312,6 +2312,37 @@ "@dhis2/ui-icons" "8.4.11" prop-types "^15.7.2" +"@dnd-kit/accessibility@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz#3ccbefdfca595b0a23a5dc57d3de96bc6935641c" + integrity sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg== + dependencies: + tslib "^2.0.0" + +"@dnd-kit/core@^6.0.7": + version "6.0.7" + resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.0.7.tgz#eb9bcb2ab5cfba3c5e8d5dc64b61e5e9cc5567dc" + integrity sha512-qcLBTVTjmLuLqC0RHQ+dFKN5neWmAI56H9xZ+he9WEJEkAvR76YAcz7DSWDJfjErepfG2H3Fkb9lYiX7cPR62g== + dependencies: + "@dnd-kit/accessibility" "^3.0.0" + "@dnd-kit/utilities" "^3.2.1" + tslib "^2.0.0" + +"@dnd-kit/sortable@^7.0.2": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-7.0.2.tgz#791d550872457f3f3c843e00d159b640f982011c" + integrity sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA== + dependencies: + "@dnd-kit/utilities" "^3.2.0" + tslib "^2.0.0" + +"@dnd-kit/utilities@^3.2.0", "@dnd-kit/utilities@^3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.1.tgz#53f9e2016fd2506ec49e404c289392cfff30332a" + integrity sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA== + dependencies: + tslib "^2.0.0" + "@emotion/cache@^10.0.27": version "10.0.29" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" @@ -3031,6 +3062,13 @@ prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" +"@react-hook/debounce@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@react-hook/debounce/-/debounce-4.0.0.tgz#5da87e7bfa158cfe2830ffc997dc1b755e261379" + integrity sha512-706Xcg+KKWHk9BuZQUQ0ZQKp9zhv3/MbqFenWVfHcynYpSGRVwQTzJRGvPxvsdtXxJv+HfgKTY/O/hEejakwmA== + dependencies: + "@react-hook/latest" "^1.0.2" + "@react-hook/latest@^1.0.2": version "1.0.3" resolved "https://registry.yarnpkg.com/@react-hook/latest/-/latest-1.0.3.tgz#c2d1d0b0af8b69ec6e2b3a2412ba0768ac82db80" @@ -7680,6 +7718,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" + integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -19319,8 +19362,10 @@ watchpack@^1.7.4: resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: + chokidar "^3.4.1" graceful-fs "^4.1.2" neo-async "^2.5.0" + watchpack-chokidar2 "^2.0.1" optionalDependencies: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" From fd48d489316a1cb8c10d21f0344f13d383819fd0 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 24 Apr 2023 12:05:50 +0000 Subject: [PATCH 048/285] chore(release): cut 25.0.0 [skip ci] # [25.0.0](https://github.com/dhis2/analytics/compare/v24.10.1...v25.0.0) (2023-04-24) ### Features * custom calculations (DHIS2-13871) ([#1370](https://github.com/dhis2/analytics/issues/1370)) ([d174e3e](https://github.com/dhis2/analytics/commit/d174e3e8a1dd1ea756cf42eaf7748b2135151e57)) ### BREAKING CHANGES * requires metadata to be provided for the new EDI dimension type --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cecea0508..fba0d11b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# [25.0.0](https://github.com/dhis2/analytics/compare/v24.10.1...v25.0.0) (2023-04-24) + + +### Features + +* custom calculations (DHIS2-13871) ([#1370](https://github.com/dhis2/analytics/issues/1370)) ([d174e3e](https://github.com/dhis2/analytics/commit/d174e3e8a1dd1ea756cf42eaf7748b2135151e57)) + + +### BREAKING CHANGES + +* requires metadata to be provided for the new EDI dimension type + ## [24.10.1](https://github.com/dhis2/analytics/compare/v24.10.0...v24.10.1) (2023-04-24) diff --git a/package.json b/package.json index a644a5b5c..871207f71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "24.10.1", + "version": "25.0.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 784655d6583935d74809e88dd55fbd3385950799 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 27 Apr 2023 14:51:08 +0200 Subject: [PATCH 049/285] test: add data-test to modal and data elements (DHIS2-13871) (#1455) --- .../DataDimension/Calculation/CalculationModal.js | 2 +- .../DataDimension/Calculation/DataElementOption.js | 6 +++++- .../DataDimension/Calculation/DataElementSelector.js | 3 +++ src/components/DataDimension/Calculation/FormulaField.js | 4 ++-- .../DataDimension/Calculation/MathOperatorSelector.js | 2 +- src/components/LegendKey/LegendKey.js | 2 +- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/DataDimension/Calculation/CalculationModal.js b/src/components/DataDimension/Calculation/CalculationModal.js index 4d3c23146..6fdcf8119 100644 --- a/src/components/DataDimension/Calculation/CalculationModal.js +++ b/src/components/DataDimension/Calculation/CalculationModal.js @@ -445,7 +445,7 @@ const CalculationModal = ({
    {showDeletePrompt && ( - + {i18n.t('Delete calculation')} {i18n.t( diff --git a/src/components/DataDimension/Calculation/DataElementOption.js b/src/components/DataDimension/Calculation/DataElementOption.js index 584386ee8..7743ff3b3 100644 --- a/src/components/DataDimension/Calculation/DataElementOption.js +++ b/src/components/DataDimension/Calculation/DataElementOption.js @@ -26,7 +26,11 @@ const DataElementOption = ({ label, value, onDoubleClick }) => { ref={setNodeRef} style={style} > -
    onDoubleClick(data)}> +
    onDoubleClick(data)} + data-test="data-element-option" + > {getIcon(DIMENSION_TYPE_DATA_ELEMENT)} diff --git a/src/components/DataDimension/Calculation/DataElementSelector.js b/src/components/DataDimension/Calculation/DataElementSelector.js index 2deeff868..123fe20fd 100644 --- a/src/components/DataDimension/Calculation/DataElementSelector.js +++ b/src/components/DataDimension/Calculation/DataElementSelector.js @@ -69,6 +69,7 @@ const GroupSelector = ({ currentValue, onChange, displayNameProp }) => { value={defaultGroup.id} key={defaultGroup.id} label={defaultGroup.getName()} + dataTest={`data-element-group-select-option-${defaultGroup.id}`} /> ) : null} {!loading @@ -77,6 +78,7 @@ const GroupSelector = ({ currentValue, onChange, displayNameProp }) => { value={group.id} key={group.id} label={group.name} + dataTest={`data-element-group-select-option-${group.id}`} /> )) : null} @@ -107,6 +109,7 @@ const DisaggregationSelector = ({ currentValue, onChange }) => { value={option[0]} key={option[0]} label={option[1]} + dataTest={`data-element-disaggregation-select-option-${option[0]}`} /> ))} diff --git a/src/components/DataDimension/Calculation/FormulaField.js b/src/components/DataDimension/Calculation/FormulaField.js index 075ff285c..388394deb 100644 --- a/src/components/DataDimension/Calculation/FormulaField.js +++ b/src/components/DataDimension/Calculation/FormulaField.js @@ -13,7 +13,7 @@ export const LAST_DROPZONE_ID = 'lastdropzone' export const FORMULA_BOX_ID = 'formulabox' const Placeholder = () => ( -
    +
    {i18n.t( @@ -47,7 +47,7 @@ const FormulaField = ({
    {loading && (
    diff --git a/src/components/DataDimension/Calculation/MathOperatorSelector.js b/src/components/DataDimension/Calculation/MathOperatorSelector.js index dd4d4321a..d87689f6a 100644 --- a/src/components/DataDimension/Calculation/MathOperatorSelector.js +++ b/src/components/DataDimension/Calculation/MathOperatorSelector.js @@ -9,7 +9,7 @@ const MathOperatorSelector = ({ onDoubleClick }) => ( <>

    {i18n.t('Math operators')}

    -
    +
    {getOperators().map(({ label, value, type }, index) => ( { return legendSets.length ? ( -
    +
    {legendSets.map((legendSet, index) => (
    Date: Thu, 27 Apr 2023 15:26:08 +0200 Subject: [PATCH 050/285] feat: icon in SV visualization DHIS2-10496 (#1440) * feat: toggle icon in SV depending on DV option * feat: show icon in SV when assigned and DV option is enabled DHIS2-10496 * fix: embed svg directly to allow for changing colors This requires the SVG icons from the API to use "currentColor" in their fill/stroke attributes instead of the fixed color currently used. * fix: add more space between icon and text * fix: replace hardcoded color with currentColor This allow for controlling the color of the icon from the app. * fix: fix merge conflicts * fix: avoid crash in dashboard when no legend is applied * fix: add xmlns attribute required for download DHIS2-15161 The attribute is not required when the SVG is embedded in another document, but in the case of download, the SVG is "standalone" and requires the namespace. * fix: center subtitle DHIS2-13702 * fix: center title * fix: remove marging which clashes with rounded borders * fix: fix missing icon and value vertical align in download DHIS2-15162 * fix: attempt to fix some alignment issues when icon is used Related to DHIS2-15157 --------- Co-authored-by: Martin --- .../config/generators/dhis/singleValue.js | 217 ++++++++++++------ 1 file changed, 145 insertions(+), 72 deletions(-) diff --git a/src/visualizations/config/generators/dhis/singleValue.js b/src/visualizations/config/generators/dhis/singleValue.js index 669a046d0..fcb9c1365 100644 --- a/src/visualizations/config/generators/dhis/singleValue.js +++ b/src/visualizations/config/generators/dhis/singleValue.js @@ -20,28 +20,49 @@ import { const svgNS = 'http://www.w3.org/2000/svg' +// Compute text width before rendering +// Not exactly precise but close enough +const getTextWidth = (text, font) => { + const canvas = document.createElement('canvas') + const context = canvas.getContext('2d') + context.font = font + return context.measureText(text).width +} + const generateValueSVG = ({ formattedValue, subText, valueColor, + icon, noData, - y, + containerWidth, + containerHeight, }) => { - const textSize = 300 + const ratio = containerHeight / containerWidth + const iconSize = 300 + const iconPadding = 50 + const textSize = iconSize * 0.85 + const textWidth = getTextWidth(formattedValue, `${textSize}px Roboto`) + const subTextSize = 40 - const svgValue = document.createElementNS(svgNS, 'svg') - svgValue.setAttribute('xmlns', svgNS) - svgValue.setAttribute( - 'viewBox', - `0 -${textSize + 50} ${textSize * 0.75 * formattedValue.length} ${ - textSize + 200 - }` - ) + const showIcon = icon && formattedValue !== noData.text - if (y) { - svgValue.setAttribute('y', y) + let viewBoxWidth = textWidth + + if (showIcon) { + viewBoxWidth += iconSize + iconPadding } + const viewBoxHeight = viewBoxWidth * ratio + + const svgValue = document.createElementNS(svgNS, 'svg') + svgValue.setAttribute('viewBox', `0 0 ${viewBoxWidth} ${viewBoxHeight}`) + svgValue.setAttribute('width', '95%') + svgValue.setAttribute('height', '95%') + svgValue.setAttribute('x', '50%') + svgValue.setAttribute('y', '50%') + svgValue.setAttribute('style', 'overflow: visible') + let fillColor = colors.grey900 if (valueColor) { @@ -50,41 +71,56 @@ const generateValueSVG = ({ fillColor = colors.grey600 } + // show icon if configured in maintenance app + if (showIcon) { + // embed icon to allow changing color + // (elements with fill need to use "currentColor" for this to work) + const iconSvgNode = document.createElementNS(svgNS, 'svg') + iconSvgNode.setAttribute('width', iconSize) + iconSvgNode.setAttribute('height', iconSize) + iconSvgNode.setAttribute('viewBox', '0 0 48 48') + iconSvgNode.setAttribute('y', `-${iconSize / 2}`) + iconSvgNode.setAttribute( + 'x', + `-${(iconSize + iconPadding + textWidth) / 2}` + ) + iconSvgNode.setAttribute('style', `color: ${fillColor}`) + + const parser = new DOMParser() + const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml') + + Array.from(svgIconDocument.documentElement.children).forEach((node) => + iconSvgNode.appendChild(node) + ) + + svgValue.appendChild(iconSvgNode) + } + const textNode = document.createElementNS(svgNS, 'text') - textNode.setAttribute('text-anchor', 'middle') textNode.setAttribute('font-size', textSize) textNode.setAttribute('font-weight', '300') textNode.setAttribute('letter-spacing', '-5') - textNode.setAttribute('x', '50%') + textNode.setAttribute('text-anchor', 'middle') + textNode.setAttribute('x', showIcon ? `${(iconSize + iconPadding) / 2}` : 0) + // vertical align, "alignment-baseline: central" is not supported by Batik + textNode.setAttribute('y', '.35em') textNode.setAttribute('fill', fillColor) textNode.setAttribute('data-test', 'visualization-primary-value') + textNode.appendChild(document.createTextNode(formattedValue)) svgValue.appendChild(textNode) if (subText) { - const svgSubText = document.createElementNS(svgNS, 'svg') - const subTextSize = 40 - svgSubText.setAttribute( - 'viewBox', - `0 -50 ${textSize * 0.75 * formattedValue.length} ${textSize + 200}` - ) - - if (y) { - svgSubText.setAttribute('y', y) - } - const subTextNode = document.createElementNS(svgNS, 'text') subTextNode.setAttribute('text-anchor', 'middle') subTextNode.setAttribute('font-size', subTextSize) - subTextNode.setAttribute('x', '50%') - subTextNode.setAttribute('x', '50%') + subTextNode.setAttribute('y', iconSize / 2) + subTextNode.setAttribute('dy', subTextSize) subTextNode.setAttribute('fill', colors.grey600) subTextNode.appendChild(document.createTextNode(subText)) - svgSubText.appendChild(subTextNode) - - svgValue.appendChild(svgSubText) + svgValue.appendChild(subTextNode) } return svgValue @@ -92,15 +128,38 @@ const generateValueSVG = ({ const generateDashboardItem = ( config, - { valueColor, titleColor, backgroundColor, noData } + { + svgContainer, + width, + height, + valueColor, + titleColor, + backgroundColor, + noData, + icon, + } ) => { + svgContainer.appendChild( + generateValueSVG({ + formattedValue: config.formattedValue, + subText: config.subText, + valueColor, + noData, + icon, + containerWidth: width, + containerHeight: height, + }) + ) + const container = document.createElement('div') container.setAttribute( 'style', `display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; background-color:${backgroundColor};` ) - const titleStyle = `font-size: 12px; color: ${titleColor || '#666'};` + const titleStyle = `padding: 0 8px; text-align: center; font-size: 12px; color: ${ + titleColor || '#666' + };` const title = document.createElement('span') title.setAttribute('style', titleStyle) @@ -112,25 +171,14 @@ const generateDashboardItem = ( if (config.subtitle) { const subtitle = document.createElement('span') - subtitle.setAttribute( - 'style', - titleStyle + ' margin-top: 4px; padding: 0 8px' - ) + subtitle.setAttribute('style', titleStyle + ' margin-top: 4px;') subtitle.appendChild(document.createTextNode(config.subtitle)) container.appendChild(subtitle) } - container.appendChild( - generateValueSVG({ - formattedValue: config.formattedValue, - subText: config.subText, - valueColor, - noData, - y: 40, - }) - ) + container.appendChild(svgContainer) return container } @@ -161,32 +209,33 @@ const getXFromTextAlign = (textAlign) => { const generateDVItem = ( config, - { valueColor, backgroundColor, titleColor, parentEl, fontStyle, noData } + { + svgContainer, + width, + height, + valueColor, + noData, + backgroundColor, + titleColor, + fontStyle, + icon, + } ) => { - const parentElBBox = parentEl.getBoundingClientRect() - - const width = parentElBBox.width - const height = parentElBBox.height - - const svgNS = 'http://www.w3.org/2000/svg' - - const svg = document.createElementNS(svgNS, 'svg') - svg.setAttribute('xmlns', svgNS) - svg.setAttribute('viewBox', `0 0 ${width} ${height}`) - svg.setAttribute('width', width) - svg.setAttribute('height', height) - svg.setAttribute('data-test', 'visualization-container') - if (backgroundColor) { - svg.setAttribute('style', `background-color: ${backgroundColor};`) + svgContainer.setAttribute( + 'style', + `background-color: ${backgroundColor};` + ) const background = document.createElementNS(svgNS, 'rect') background.setAttribute('width', '100%') background.setAttribute('height', '100%') background.setAttribute('fill', backgroundColor) - svg.appendChild(background) + svgContainer.appendChild(background) } + const svgWrapper = document.createElementNS(svgNS, 'svg') + const title = document.createElementNS(svgNS, 'text') const titleFontStyle = mergeFontStyleWithDefault( fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_TITLE], @@ -234,7 +283,7 @@ const generateDVItem = ( if (config.title) { title.appendChild(document.createTextNode(config.title)) - svg.appendChild(title) + svgWrapper.appendChild(title) } const subtitleFontStyle = mergeFontStyleWithDefault( @@ -291,23 +340,27 @@ const generateDVItem = ( if (config.subtitle) { subtitle.appendChild(document.createTextNode(config.subtitle)) - svg.appendChild(subtitle) + svgWrapper.appendChild(subtitle) } - svg.appendChild( + svgContainer.appendChild(svgWrapper) + + svgContainer.appendChild( generateValueSVG({ formattedValue: config.formattedValue, subText: config.subText, valueColor, noData, - y: 20, + icon, + containerWidth: width, + containerHeight: height, }) ) - return svg + return svgContainer } -const shouldUseContrastColor = (inputColor) => { +const shouldUseContrastColor = (inputColor = '') => { // based on https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color var color = inputColor.charAt(0) === '#' ? inputColor.substring(1, 7) : inputColor @@ -328,7 +381,7 @@ const shouldUseContrastColor = (inputColor) => { export default function ( config, parentEl, - { dashboard, legendSets, fontStyle, noData, legendOptions } + { dashboard, legendSets, fontStyle, noData, legendOptions, icon } ) { const legendSet = legendOptions && legendSets[0] const legendColor = @@ -348,25 +401,45 @@ export default function ( parentEl.style.display = 'flex' parentEl.style.justifyContent = 'center' + const parentElBBox = parentEl.getBoundingClientRect() + const width = parentElBBox.width + const height = parentElBBox.height + + const svgContainer = document.createElementNS(svgNS, 'svg') + svgContainer.setAttribute('xmlns', svgNS) + svgContainer.setAttribute('viewBox', `0 0 ${width} ${height}`) + svgContainer.setAttribute('width', dashboard ? '100%' : width) + svgContainer.setAttribute('height', dashboard ? '100%' : height) + svgContainer.setAttribute('data-test', 'visualization-container') + if (dashboard) { parentEl.style.borderRadius = spacers.dp8 + return generateDashboardItem(config, { + svgContainer, + width, + height, valueColor, backgroundColor, noData, - ...(shouldUseContrastColor(legendColor) + icon, + ...(legendColor && shouldUseContrastColor(legendColor) ? { titleColor: colors.white } : {}), }) } else { parentEl.style.height = `100%` + return generateDVItem(config, { + svgContainer, + width, + height, valueColor, backgroundColor, titleColor, - parentEl, - fontStyle, noData, + icon, + fontStyle, }) } } From f1c8997df32e57b6bd210643569c1317662a33b3 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 27 Apr 2023 13:30:32 +0000 Subject: [PATCH 051/285] chore(release): cut 25.1.0 [skip ci] # [25.1.0](https://github.com/dhis2/analytics/compare/v25.0.0...v25.1.0) (2023-04-27) ### Features * icon in SV visualization DHIS2-10496 ([#1440](https://github.com/dhis2/analytics/issues/1440)) ([e6563ca](https://github.com/dhis2/analytics/commit/e6563cacc5e901a04d5432330b09b685936ddd70)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fba0d11b2..59b9f11d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [25.1.0](https://github.com/dhis2/analytics/compare/v25.0.0...v25.1.0) (2023-04-27) + + +### Features + +* icon in SV visualization DHIS2-10496 ([#1440](https://github.com/dhis2/analytics/issues/1440)) ([e6563ca](https://github.com/dhis2/analytics/commit/e6563cacc5e901a04d5432330b09b685936ddd70)) + # [25.0.0](https://github.com/dhis2/analytics/compare/v24.10.1...v25.0.0) (2023-04-24) diff --git a/package.json b/package.json index 871207f71..a999dd577 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.0.0", + "version": "25.1.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From f0ee1f16450e1b6de7c879c8ecdeec7c1d7c89fb Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Tue, 2 May 2023 10:33:48 +0200 Subject: [PATCH 052/285] fix: address various SV SVG issues after KFMT (#1456) * fix: use contrast color in subtext DHIS2-15233 * fix: only apply contrast color when bg color is applied DHIS2-15237 * fix: fix y position for title/subtitle based on font size DHIS2-15236 * fix: do not set background-color when not available DHIS2-15235 * fix: add padding above title/subtitle DHIS2-15234 --- .../config/generators/dhis/singleValue.js | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/visualizations/config/generators/dhis/singleValue.js b/src/visualizations/config/generators/dhis/singleValue.js index fcb9c1365..2fb07039f 100644 --- a/src/visualizations/config/generators/dhis/singleValue.js +++ b/src/visualizations/config/generators/dhis/singleValue.js @@ -33,6 +33,7 @@ const generateValueSVG = ({ formattedValue, subText, valueColor, + textColor, icon, noData, containerWidth, @@ -117,7 +118,7 @@ const generateValueSVG = ({ subTextNode.setAttribute('font-size', subTextSize) subTextNode.setAttribute('y', iconSize / 2) subTextNode.setAttribute('dy', subTextSize) - subTextNode.setAttribute('fill', colors.grey600) + subTextNode.setAttribute('fill', textColor) subTextNode.appendChild(document.createTextNode(subText)) svgValue.appendChild(subTextNode) @@ -144,6 +145,7 @@ const generateDashboardItem = ( formattedValue: config.formattedValue, subText: config.subText, valueColor, + textColor: titleColor, noData, icon, containerWidth: width, @@ -154,7 +156,9 @@ const generateDashboardItem = ( const container = document.createElement('div') container.setAttribute( 'style', - `display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; background-color:${backgroundColor};` + `display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; padding-top: 8px; ${ + backgroundColor ? `background-color:${backgroundColor};` : '' + }` ) const titleStyle = `padding: 0 8px; text-align: center; font-size: 12px; color: ${ @@ -241,11 +245,13 @@ const generateDVItem = ( fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_TITLE], FONT_STYLE_VISUALIZATION_TITLE ) + const titleYPosition = titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE] + title.setAttribute( 'x', getXFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]) ) - title.setAttribute('y', 28) + title.setAttribute('y', titleYPosition) title.setAttribute( 'text-anchor', getTextAnchorFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]) @@ -295,8 +301,11 @@ const generateDVItem = ( 'x', getXFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]) ) - subtitle.setAttribute('y', 28) - subtitle.setAttribute('dy', 22) + subtitle.setAttribute('y', titleYPosition) + subtitle.setAttribute( + 'dy', + `${subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE] + 4}` + ) subtitle.setAttribute( 'text-anchor', getTextAnchorFromTextAlign( @@ -350,6 +359,7 @@ const generateDVItem = ( formattedValue: config.formattedValue, subText: config.subText, valueColor, + textColor: titleColor, noData, icon, containerWidth: width, @@ -423,7 +433,9 @@ export default function ( backgroundColor, noData, icon, - ...(legendColor && shouldUseContrastColor(legendColor) + ...(legendOptions.style === LEGEND_DISPLAY_STYLE_FILL && + legendColor && + shouldUseContrastColor(legendColor) ? { titleColor: colors.white } : {}), }) From f89291a8a77c78e037e739edc969ebafef046d87 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 2 May 2023 08:38:20 +0000 Subject: [PATCH 053/285] chore(release): cut 25.1.1 [skip ci] ## [25.1.1](https://github.com/dhis2/analytics/compare/v25.1.0...v25.1.1) (2023-05-02) ### Bug Fixes * address various SV SVG issues after KFMT ([#1456](https://github.com/dhis2/analytics/issues/1456)) ([f0ee1f1](https://github.com/dhis2/analytics/commit/f0ee1f16450e1b6de7c879c8ecdeec7c1d7c89fb)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59b9f11d3..d42e5aff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.1](https://github.com/dhis2/analytics/compare/v25.1.0...v25.1.1) (2023-05-02) + + +### Bug Fixes + +* address various SV SVG issues after KFMT ([#1456](https://github.com/dhis2/analytics/issues/1456)) ([f0ee1f1](https://github.com/dhis2/analytics/commit/f0ee1f16450e1b6de7c879c8ecdeec7c1d7c89fb)) + # [25.1.0](https://github.com/dhis2/analytics/compare/v25.0.0...v25.1.0) (2023-04-27) diff --git a/package.json b/package.json index a999dd577..816e1ac79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.0", + "version": "25.1.1", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 2785a9e3e7c656d830df78c84c4ef29de74b0614 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Wed, 3 May 2023 11:05:31 +0200 Subject: [PATCH 054/285] fix: small cosmetic fixes to SV SVG (#1457) * fix: use same border radius as dashboard item box DHIS2-15244 * fix: use more space between title and subtitle DHIS2-15236 --- src/visualizations/config/generators/dhis/singleValue.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/visualizations/config/generators/dhis/singleValue.js b/src/visualizations/config/generators/dhis/singleValue.js index 2fb07039f..bbe3e9e60 100644 --- a/src/visualizations/config/generators/dhis/singleValue.js +++ b/src/visualizations/config/generators/dhis/singleValue.js @@ -1,4 +1,4 @@ -import { colors, spacers } from '@dhis2/ui' +import { colors } from '@dhis2/ui' import { FONT_STYLE_VISUALIZATION_TITLE, FONT_STYLE_VISUALIZATION_SUBTITLE, @@ -304,7 +304,7 @@ const generateDVItem = ( subtitle.setAttribute('y', titleYPosition) subtitle.setAttribute( 'dy', - `${subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE] + 4}` + `${subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE] + 10}` ) subtitle.setAttribute( 'text-anchor', @@ -423,7 +423,7 @@ export default function ( svgContainer.setAttribute('data-test', 'visualization-container') if (dashboard) { - parentEl.style.borderRadius = spacers.dp8 + parentEl.style.borderRadius = '3px' return generateDashboardItem(config, { svgContainer, From 30770a5e1b09c19f1a4be5bd0c651d1ddfdb75e8 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 3 May 2023 09:15:50 +0000 Subject: [PATCH 055/285] chore(release): cut 25.1.2 [skip ci] ## [25.1.2](https://github.com/dhis2/analytics/compare/v25.1.1...v25.1.2) (2023-05-03) ### Bug Fixes * small cosmetic fixes to SV SVG ([#1457](https://github.com/dhis2/analytics/issues/1457)) ([2785a9e](https://github.com/dhis2/analytics/commit/2785a9e3e7c656d830df78c84c4ef29de74b0614)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d42e5aff2..4b4bfe721 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.2](https://github.com/dhis2/analytics/compare/v25.1.1...v25.1.2) (2023-05-03) + + +### Bug Fixes + +* small cosmetic fixes to SV SVG ([#1457](https://github.com/dhis2/analytics/issues/1457)) ([2785a9e](https://github.com/dhis2/analytics/commit/2785a9e3e7c656d830df78c84c4ef29de74b0614)) + ## [25.1.1](https://github.com/dhis2/analytics/compare/v25.1.0...v25.1.1) (2023-05-02) diff --git a/package.json b/package.json index 816e1ac79..860289976 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.1", + "version": "25.1.2", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 869543f14a03d6bcfad8e009ab9ac4bc87d16de1 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 23 May 2023 10:18:42 +0200 Subject: [PATCH 056/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/ar.po | 169 ++++++++++++++++++++++++++------------------ i18n/cs.po | 171 +++++++++++++++++++++++++++------------------ i18n/da.po | 172 ++++++++++++++++++++++++++++----------------- i18n/es.po | 181 ++++++++++++++++++++++++++++++------------------ i18n/es_419.po | 180 ++++++++++++++++++++++++++++------------------- i18n/fr.po | 171 +++++++++++++++++++++++++++------------------ i18n/id.po | 176 +++++++++++++++++++++++++++++----------------- i18n/km.po | 172 ++++++++++++++++++++++++++++----------------- i18n/lo.po | 172 +++++++++++++++++++++++++++------------------ i18n/my.po | 172 ++++++++++++++++++++++++++++----------------- i18n/nb.po | 176 +++++++++++++++++++++++++++++----------------- i18n/prs.po | 172 ++++++++++++++++++++++++++++----------------- i18n/ps.po | 172 ++++++++++++++++++++++++++++----------------- i18n/pt.po | 169 ++++++++++++++++++++++++++------------------ i18n/pt_BR.po | 172 ++++++++++++++++++++++++++++----------------- i18n/ru.po | 176 +++++++++++++++++++++++++++++----------------- i18n/sv.po | 172 ++++++++++++++++++++++++++++----------------- i18n/tet.po | 172 ++++++++++++++++++++++++++++----------------- i18n/tg.po | 172 ++++++++++++++++++++++++++++----------------- i18n/uk.po | 169 ++++++++++++++++++++++++++------------------ i18n/ur.po | 172 ++++++++++++++++++++++++++++----------------- i18n/uz.po | 169 ++++++++++++++++++++++++++------------------ i18n/uz_Latn.po | 176 +++++++++++++++++++++++++++++----------------- i18n/vi.po | 176 +++++++++++++++++++++++++++++----------------- i18n/zh.po | 171 +++++++++++++++++++++++++++------------------ i18n/zh_CN.po | 172 ++++++++++++++++++++++++++++----------------- 26 files changed, 2806 insertions(+), 1688 deletions(-) diff --git a/i18n/ar.po b/i18n/ar.po index a57ec26f3..37ad6f121 100644 --- a/i18n/ar.po +++ b/i18n/ar.po @@ -1,16 +1,16 @@ # # Translators: # Viktor Varland , 2022 -# phil_dhis2, 2022 # Hamza Assada <7amza.it@gmail.com>, 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Hamza Assada <7amza.it@gmail.com>, 2022\n" -"Language-Team: Arabic (https://www.transifex.com/hisp-uio/teams/100509/ar/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Arabic (https://app.transifex.com/hisp-uio/teams/100509/ar/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -86,11 +86,46 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" -msgstr "نوع البيانات" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "جميع الأنواع" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "إلغاء" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" msgid "Totals only" msgstr "" @@ -98,15 +133,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "جار التحميل" + +msgid "Data elements" +msgstr "عناصر البيانات" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "نوع البيانات" + +msgid "All types" +msgstr "جميع الأنواع" + msgid "Disaggregation" msgstr "تصنيف" msgid "No data" msgstr "لا توجد بيانات" -msgid "Loading" -msgstr "جار التحميل" - msgid "Search by data item name" msgstr "" @@ -119,9 +180,6 @@ msgstr "العناصر المحددة" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -134,9 +192,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -149,6 +204,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -215,9 +273,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "إلغاء" - msgid "Delete" msgstr "حذف" @@ -269,7 +324,7 @@ msgstr "الوصف" msgid "Rename" msgstr "إعادة التسمية" -msgid "{{objectName}} (copy)" +msgid "{{- objectName}} (copy)" msgstr "" msgid "Save {{fileType}} as" @@ -599,48 +654,6 @@ msgstr "الفترات المحددة" msgid "No periods selected" msgstr "لم يتم تحديد أية فترات زمنية" -msgid "January" -msgstr "يناير" - -msgid "February" -msgstr "فبراير" - -msgid "March" -msgstr "مارس" - -msgid "April" -msgstr "ابريل" - -msgid "May" -msgstr "مايو" - -msgid "June" -msgstr "يونيو" - -msgid "July" -msgstr "يوليو" - -msgid "August" -msgstr "اغسطس" - -msgid "September" -msgstr "سبتمبر" - -msgid "October" -msgstr "اكتوبر" - -msgid "November" -msgstr "نوفمبر" - -msgid "December" -msgstr "ديسمبر" - -msgid "Week {{weekNumber}}" -msgstr "الإسبوع {{weekNumber}}" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "ثنائي الإسبوع {{biWeekNumber}}" - msgid "Daily" msgstr "يومي" @@ -929,9 +942,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "عناصر البيانات" - msgid "Data element group" msgstr "مجموعة عناصر البيانات" @@ -977,6 +987,33 @@ msgstr "مؤشرات البرنامج" msgid "Program indicator" msgstr "مؤشرات البرنامج" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "العدد" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "صغير جدا" diff --git a/i18n/cs.po b/i18n/cs.po index 7a5439c60..043bda071 100644 --- a/i18n/cs.po +++ b/i18n/cs.po @@ -1,14 +1,14 @@ # # Translators: -# trendspotter , 2022 +# Jiří Podhorecký, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: trendspotter , 2022\n" -"Language-Team: Czech (https://www.transifex.com/hisp-uio/teams/100509/cs/)\n" +"Last-Translator: Jiří Podhorecký, 2023\n" +"Language-Team: Czech (https://app.transifex.com/hisp-uio/teams/100509/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -83,11 +83,46 @@ msgstr "Tato aplikace nemohla načíst požadovaná data." msgid "Network error" msgstr "Chyba sítě" -msgid "Data Type" -msgstr "Typ dat" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "Všechny typy" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Zrušit" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "Ano, smazat" msgid "Totals only" msgstr "Pouze součty" @@ -95,15 +130,41 @@ msgstr "Pouze součty" msgid "Details only" msgstr "Pouze podrobnosti" +msgid "Loading" +msgstr "Načítání" + +msgid "Data elements" +msgstr "Datové prvky" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "Nebyly nalezeny žádné datové prvky pro „{{- searchTerm}}“" + +msgid "No data elements found" +msgstr "Nebyly nalezeny žádné datové prvky" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "Typ dat" + +msgid "All types" +msgstr "Všechny typy" + msgid "Disaggregation" msgstr "Rozčlenění" msgid "No data" msgstr "Nejsou data" -msgid "Loading" -msgstr "Načítání" - msgid "Search by data item name" msgstr "Hledání podle názvu datové položky" @@ -116,9 +177,6 @@ msgstr "Vybrané položky" msgid "No indicators found" msgstr "Nebyly nalezeny žádné indikátory" -msgid "No data elements found" -msgstr "Nebyly nalezeny žádné datové prvky" - msgid "No data sets found" msgstr "Nebyly nalezeny žádné datové soubory" @@ -131,9 +189,6 @@ msgstr "Nebyly nalezeny žádné programové indikátory" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "Nebyly nalezeny žádné indikátory pro „{{- searchTerm}}“" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "Nebyly nalezeny žádné datové prvky pro „{{- searchTerm}}“" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "Nebyly nalezeny žádné soubory dat pro „{{- searchTerm}}“" @@ -146,6 +201,9 @@ msgstr "Nebyly nalezeny žádné programové indikátory pro „{{- searchTerm}} msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "Pro dotaz „{{- searchTerm}}“ nebylo nic nalezeno" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "Metrický typ" @@ -215,9 +273,6 @@ msgid "" msgstr "" "Tento {{fileType}} a související interpretace budou smazány. Pokračovat?" -msgid "Cancel" -msgstr "Zrušit" - msgid "Delete" msgstr "Smazat" @@ -269,8 +324,8 @@ msgstr "Popis" msgid "Rename" msgstr "Přejmenovat" -msgid "{{objectName}} (copy)" -msgstr "{{objectName}} (kopie)" +msgid "{{- objectName}} (copy)" +msgstr "" msgid "Save {{fileType}} as" msgstr "Uložit {{fileType}} jako" @@ -614,48 +669,6 @@ msgstr "Vybraná období" msgid "No periods selected" msgstr "Nejsou vybrána žádná období" -msgid "January" -msgstr "Leden" - -msgid "February" -msgstr "únor" - -msgid "March" -msgstr "březen" - -msgid "April" -msgstr "Duben" - -msgid "May" -msgstr "Květen" - -msgid "June" -msgstr "červen" - -msgid "July" -msgstr "červenec" - -msgid "August" -msgstr "Srpen" - -msgid "September" -msgstr "Září" - -msgid "October" -msgstr "říjen" - -msgid "November" -msgstr "Listopad" - -msgid "December" -msgstr "prosinec" - -msgid "Week {{weekNumber}}" -msgstr "Týden {{weekNumber}}" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "dva týdny {{biWeekNumber}}" - msgid "Daily" msgstr "Denně" @@ -944,9 +957,6 @@ msgstr "Nebyly nalezeny žádné skupiny indikátorů" msgid "Loading indicator groups" msgstr "Načítání skupin indikátorů" -msgid "Data elements" -msgstr "Datové prvky" - msgid "Data element group" msgstr "Skupina datových prvků" @@ -992,6 +1002,33 @@ msgstr "Indikátory programu" msgid "Program indicator" msgstr "Indikátor programu" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Číslo" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "Velmi malý" diff --git a/i18n/da.po b/i18n/da.po index c3671c1f2..7ad165ade 100644 --- a/i18n/da.po +++ b/i18n/da.po @@ -1,14 +1,14 @@ # # Translators: -# phil_dhis2, 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2022\n" -"Language-Team: Danish (https://www.transifex.com/hisp-uio/teams/100509/da/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Danish (https://app.transifex.com/hisp-uio/teams/100509/da/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -80,10 +80,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Cancel" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -92,13 +127,39 @@ msgstr "" msgid "Details only" msgstr "" -msgid "Disaggregation" +msgid "Loading" msgstr "" -msgid "No data" +msgid "Data elements" +msgstr "Data elements" + +msgid "Search by data element name" msgstr "" -msgid "Loading" +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + +msgid "Disaggregation" +msgstr "" + +msgid "No data" msgstr "" msgid "Search by data item name" @@ -113,9 +174,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -128,9 +186,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -143,6 +198,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -209,9 +267,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Cancel" - msgid "Delete" msgstr "Delete" @@ -263,6 +318,9 @@ msgstr "Description" msgid "Rename" msgstr "" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -376,6 +434,9 @@ msgstr "" msgid "No results found" msgstr "No results found" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "Created by" @@ -575,48 +636,6 @@ msgstr "" msgid "No periods selected" msgstr "" -msgid "January" -msgstr "January" - -msgid "February" -msgstr "February" - -msgid "March" -msgstr "March" - -msgid "April" -msgstr "April" - -msgid "May" -msgstr "May" - -msgid "June" -msgstr "June" - -msgid "July" -msgstr "July" - -msgid "August" -msgstr "August" - -msgid "September" -msgstr "September" - -msgid "October" -msgstr "October" - -msgid "November" -msgstr "November" - -msgid "December" -msgstr "December" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "Daily" @@ -839,6 +858,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -902,9 +924,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Data elements" - msgid "Data element group" msgstr "Data element group" @@ -950,6 +969,33 @@ msgstr "" msgid "Program indicator" msgstr "" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Number" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/es.po b/i18n/es.po index 01f9fad0d..c71fcd0dd 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -1,22 +1,23 @@ # # Translators: # ericbp , 2020 -# Marta Vila , 2021 # Alison Andrade , 2021 # Juan M Alcantara Acosta , 2021 # Prabhjot Singh, 2021 # Janeth Cruz, 2022 # Gabriela Rodriguez , 2022 -# phil_dhis2, 2022 # Viktor Varland , 2022 +# Marta Vila , 2023 +# Enzo Nicolas Rossi , 2023 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Viktor Varland , 2022\n" -"Language-Team: Spanish (https://www.transifex.com/hisp-uio/teams/100509/es/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Spanish (https://app.transifex.com/hisp-uio/teams/100509/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -89,11 +90,46 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" -msgstr "Tipo de datos" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "Todos los tipos" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Cancelar" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" msgid "Totals only" msgstr "" @@ -101,15 +137,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Cargando" + +msgid "Data elements" +msgstr "Elementos de datos" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "Tipo de datos" + +msgid "All types" +msgstr "Todos los tipos" + msgid "Disaggregation" msgstr "Desagregación" msgid "No data" msgstr "No hay datos" -msgid "Loading" -msgstr "Cargando" - msgid "Search by data item name" msgstr "" @@ -122,9 +184,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -137,9 +196,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -152,6 +208,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -189,7 +248,7 @@ msgid "Dimension recommended with selected data" msgstr "" msgid "All items" -msgstr "" +msgstr "Todos los elementos" msgid "Automatically include all items" msgstr "" @@ -218,9 +277,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Cancelar" - msgid "Delete" msgstr "Eliminar" @@ -272,6 +328,9 @@ msgstr "Descripción" msgid "Rename" msgstr "Renombrar" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -385,6 +444,9 @@ msgstr "" msgid "No results found" msgstr "No results found" +msgid "Not available offline" +msgstr "No disponible sin conexión a internet" + msgid "Created by" msgstr "Creado por" @@ -587,48 +649,6 @@ msgstr "" msgid "No periods selected" msgstr "No se ha seleccionado ningún periodo" -msgid "January" -msgstr "Enero" - -msgid "February" -msgstr "Febrero" - -msgid "March" -msgstr "Marzo" - -msgid "April" -msgstr "Abril" - -msgid "May" -msgstr "Mayo" - -msgid "June" -msgstr "Junio" - -msgid "July" -msgstr "Julio" - -msgid "August" -msgstr "Agosto" - -msgid "September" -msgstr "Septiembre" - -msgid "October" -msgstr "Octubre" - -msgid "November" -msgstr "Noviembre" - -msgid "December" -msgstr "Diciembre" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "Diario" @@ -851,6 +871,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -914,9 +937,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Elementos de datos" - msgid "Data element group" msgstr "Grupo de elemento de datos" @@ -962,6 +982,33 @@ msgstr "Indicadores de programa" msgid "Program indicator" msgstr "" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Número" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/es_419.po b/i18n/es_419.po index 70265363d..397bfb887 100644 --- a/i18n/es_419.po +++ b/i18n/es_419.po @@ -1,14 +1,15 @@ # # Translators: # Jaime Bosque , 2022 +# Enzo Nicolas Rossi , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Jaime Bosque , 2022\n" -"Language-Team: Spanish (Latin America) (https://www.transifex.com/hisp-uio/teams/100509/es_419/)\n" +"Last-Translator: Enzo Nicolas Rossi , 2023\n" +"Language-Team: Spanish (Latin America) (https://app.transifex.com/hisp-uio/teams/100509/es_419/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -81,10 +82,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Cancelar" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -93,15 +129,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "" + +msgid "Data elements" +msgstr "" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "" msgid "No data" msgstr "No hay datos" -msgid "Loading" -msgstr "" - msgid "Search by data item name" msgstr "" @@ -114,9 +176,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -129,9 +188,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -144,6 +200,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -210,9 +269,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Cancelar" - msgid "Delete" msgstr "Borrar" @@ -220,7 +276,7 @@ msgid "File" msgstr "" msgid "New" -msgstr "" +msgstr "Nuevo" msgid "Open…" msgstr "" @@ -259,12 +315,12 @@ msgid "Name" msgstr "Nombre" msgid "Description" -msgstr "" +msgstr "Descripción" msgid "Rename" msgstr "" -msgid "{{objectName}} (copy)" +msgid "{{- objectName}} (copy)" msgstr "" msgid "Save {{fileType}} as" @@ -387,7 +443,7 @@ msgid "Created by" msgstr "" msgid "Anyone" -msgstr "" +msgstr "Quien sea" msgid "Only you" msgstr "" @@ -405,7 +461,7 @@ msgid "Created" msgstr "" msgid "Last updated" -msgstr "" +msgstr "Última actualización" msgid "Type" msgstr "" @@ -585,48 +641,6 @@ msgstr "" msgid "No periods selected" msgstr "" -msgid "January" -msgstr "" - -msgid "February" -msgstr "" - -msgid "March" -msgstr "" - -msgid "April" -msgstr "" - -msgid "May" -msgstr "" - -msgid "June" -msgstr "" - -msgid "July" -msgstr "" - -msgid "August" -msgstr "" - -msgid "September" -msgstr "" - -msgid "October" -msgstr "" - -msgid "November" -msgstr "" - -msgid "December" -msgstr "" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "Diario" @@ -739,7 +753,7 @@ msgid "Last month" msgstr "Mes anterior" msgid "Last 3 months" -msgstr "" +msgstr "Últimos 3 meses" msgid "Last 6 months" msgstr "" @@ -915,9 +929,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "" - msgid "Data element group" msgstr "" @@ -963,6 +974,33 @@ msgstr "" msgid "Program indicator" msgstr "" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" @@ -1045,7 +1083,7 @@ msgid "Data" msgstr "Datos" msgid "Organisation unit" -msgstr "" +msgstr "Unidad organizativa" msgid "Assigned Categories" msgstr "" @@ -1054,7 +1092,7 @@ msgid "Pivot table" msgstr "" msgid "Area" -msgstr "" +msgstr "Área" msgid "Stacked area" msgstr "" @@ -1066,7 +1104,7 @@ msgid "Stacked bar" msgstr "" msgid "Column" -msgstr "" +msgstr "Columna" msgid "Year over year (column)" msgstr "" diff --git a/i18n/fr.po b/i18n/fr.po index b1ec48ba8..2085f22e0 100644 --- a/i18n/fr.po +++ b/i18n/fr.po @@ -7,16 +7,16 @@ # tx_e2f_fr r25 , 2021 # Karoline Tufte Lien , 2022 # Edem Kossi , 2022 -# phil_dhis2, 2022 -# Yao Selom SAKA (HISP WCA) , 2022 +# Yao Selom SAKA (HISP WCA) , 2023 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Yao Selom SAKA (HISP WCA) , 2022\n" -"Language-Team: French (https://www.transifex.com/hisp-uio/teams/100509/fr/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: French (https://app.transifex.com/hisp-uio/teams/100509/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -89,11 +89,46 @@ msgstr "" msgid "Network error" msgstr "Erreur réseau" -msgid "Data Type" -msgstr "Type de données" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "Tous les types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Annuler" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "Oui, supprimer" msgid "Totals only" msgstr "Totaux uniquement" @@ -101,15 +136,41 @@ msgstr "Totaux uniquement" msgid "Details only" msgstr "Détails seulement" +msgid "Loading" +msgstr "Chargement" + +msgid "Data elements" +msgstr "Eléments de données" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "Aucun élément de données trouvé pour \"{{- searchTerm}}\"" + +msgid "No data elements found" +msgstr "Aucun élément de donnée trouvé" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "Type de données" + +msgid "All types" +msgstr "Tous les types" + msgid "Disaggregation" msgstr "Disaggregation" msgid "No data" msgstr "Aucune donnée" -msgid "Loading" -msgstr "Chargement" - msgid "Search by data item name" msgstr "Rechercher par le nom de la donnée" @@ -122,9 +183,6 @@ msgstr "Éléments sélectionnés" msgid "No indicators found" msgstr "Aucun indicateur trouvé" -msgid "No data elements found" -msgstr "Aucun élément de donnée trouvé" - msgid "No data sets found" msgstr "Aucun ensemble de données trouvé" @@ -137,9 +195,6 @@ msgstr "Aucun Indicateurs de programme trouvé" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "Aucun indicateur trouvé pour \"{{- searchTerm}}\"" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "Aucun élément de données trouvé pour \"{{- searchTerm}}\"" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "Aucune donnée trouvée pour \"{{- searchTerm}}\"" @@ -152,6 +207,9 @@ msgstr "Aucun Indicateur de programme trouvé pour \"{{- searchTerm}}\"" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "Rien retrouvé pour \"{{- searchTerm}}\"" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "Type de métrique" @@ -218,9 +276,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Annuler" - msgid "Delete" msgstr "Supprimer" @@ -272,7 +327,7 @@ msgstr "Description" msgid "Rename" msgstr "Renommer" -msgid "{{objectName}} (copy)" +msgid "{{- objectName}} (copy)" msgstr "" msgid "Save {{fileType}} as" @@ -593,48 +648,6 @@ msgstr "Périodes sélectionnées" msgid "No periods selected" msgstr "Aucune périodes n'est sélectionnée" -msgid "January" -msgstr "Janvier" - -msgid "February" -msgstr "Février" - -msgid "March" -msgstr "Mars" - -msgid "April" -msgstr "Avril" - -msgid "May" -msgstr "Mai" - -msgid "June" -msgstr "Juin" - -msgid "July" -msgstr "Juillet" - -msgid "August" -msgstr "Août" - -msgid "September" -msgstr "Septembre" - -msgid "October" -msgstr "Octobre" - -msgid "November" -msgstr "Novembre" - -msgid "December" -msgstr "Décembre" - -msgid "Week {{weekNumber}}" -msgstr "Semaine{{NuméroSemaine}}" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "Bimensuel{{biWeekNumber}}" - msgid "Daily" msgstr "Quotidien" @@ -923,9 +936,6 @@ msgstr "Aucun groupe d'indicateurs trouvé" msgid "Loading indicator groups" msgstr "Chargement des groupes d'indicateurs" -msgid "Data elements" -msgstr "Eléments de données" - msgid "Data element group" msgstr "Groupe d'éléments de données" @@ -971,6 +981,33 @@ msgstr "Program indicators" msgid "Program indicator" msgstr "Gestion des indicateurs de programme" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Nombre" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "Super petit" diff --git a/i18n/id.po b/i18n/id.po index 3534dcecc..b78277d16 100644 --- a/i18n/id.po +++ b/i18n/id.po @@ -5,15 +5,15 @@ # Carwoto Sa'an , 2022 # Guardian Sanjaya , 2022 # Aprisa Chrysantina , 2022 -# Viktor Varland , 2022 +# Viktor Varland , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Viktor Varland , 2022\n" -"Language-Team: Indonesian (https://www.transifex.com/hisp-uio/teams/100509/id/)\n" +"Last-Translator: Viktor Varland , 2023\n" +"Language-Team: Indonesian (https://app.transifex.com/hisp-uio/teams/100509/id/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -84,11 +84,46 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" -msgstr "Tipe Data" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "Semua jenis" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Batal" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" msgid "Totals only" msgstr "Total saja" @@ -96,15 +131,41 @@ msgstr "Total saja" msgid "Details only" msgstr "Detail saja" +msgid "Loading" +msgstr "Loading" + +msgid "Data elements" +msgstr "Data elemen" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "Tidak ada elemen data yang ditemukan untuk \"{{- searchTerm}}\"" + +msgid "No data elements found" +msgstr "Tidak ada elemen data yang ditemukan" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "Tipe Data" + +msgid "All types" +msgstr "Semua jenis" + msgid "Disaggregation" msgstr "Disagregasi" msgid "No data" msgstr "Tidak ada data" -msgid "Loading" -msgstr "Loading" - msgid "Search by data item name" msgstr "Cari berdasarkan nama item data" @@ -117,9 +178,6 @@ msgstr "Item yang Dipilih" msgid "No indicators found" msgstr "Tidak ada indikator yang ditemukan" -msgid "No data elements found" -msgstr "Tidak ada elemen data yang ditemukan" - msgid "No data sets found" msgstr "Tidak ada kumpulan data yang ditemukan" @@ -132,9 +190,6 @@ msgstr "Tidak ada indikator program yang ditemukan" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "Tidak ada indikator yang ditemukan untuk \"{{- searchTerm}}\"" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "Tidak ada elemen data yang ditemukan untuk \"{{- searchTerm}}\"" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "Tidak ada kumpulan data yang ditemukan untuk \"{{- searchTerm}}\"" @@ -147,6 +202,9 @@ msgstr "Tidak ada indikator program yang ditemukan untuk \"{{- searchTerm}}\"" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "Tidak ada yang ditemukan untuk \"{{- searchTerm}}\"" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "Jenis metrik" @@ -213,9 +271,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "{{fileType}} ini dan interpretasi terkait akan dihapus. Terus?" -msgid "Cancel" -msgstr "Batal" - msgid "Delete" msgstr "Hapus" @@ -267,6 +322,9 @@ msgstr "Deskripsi" msgid "Rename" msgstr "Ganti nama" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "Simpan {{fileType}} sebagai" @@ -380,6 +438,9 @@ msgstr "" msgid "No results found" msgstr "Tidak ada hasil ditemukan" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "Dibuat oleh" @@ -589,48 +650,6 @@ msgstr "Periode yang Dipilih" msgid "No periods selected" msgstr "Tidak ada periode yang dipilih" -msgid "January" -msgstr "Januari" - -msgid "February" -msgstr "Februari" - -msgid "March" -msgstr "Maret" - -msgid "April" -msgstr "April" - -msgid "May" -msgstr "Mei" - -msgid "June" -msgstr "Juni" - -msgid "July" -msgstr "Juli" - -msgid "August" -msgstr "Agustus" - -msgid "September" -msgstr "September" - -msgid "October" -msgstr "Oktober" - -msgid "November" -msgstr "November" - -msgid "December" -msgstr "Desember" - -msgid "Week {{weekNumber}}" -msgstr "Minggu {{weekNumber}}" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "Dua Minggu {{biWeekNumber}}" - msgid "Daily" msgstr "Harian" @@ -853,6 +872,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -916,9 +938,6 @@ msgstr "Tidak ada grup indikator yang ditemukan" msgid "Loading indicator groups" msgstr "Memuat grup indikator" -msgid "Data elements" -msgstr "Data elemen" - msgid "Data element group" msgstr "Grup elemen data" @@ -964,6 +983,33 @@ msgstr "Indikator program" msgid "Program indicator" msgstr "Indikator program" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Angka" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "Ekstra kecil" diff --git a/i18n/km.po b/i18n/km.po index bd4121b2e..426315c9b 100644 --- a/i18n/km.po +++ b/i18n/km.po @@ -1,15 +1,15 @@ # # Translators: -# phil_dhis2, 2022 # Viktor Varland , 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Viktor Varland , 2022\n" -"Language-Team: Khmer (https://www.transifex.com/hisp-uio/teams/100509/km/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Khmer (https://app.transifex.com/hisp-uio/teams/100509/km/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -80,10 +80,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "បោះបង់" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -92,15 +127,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Loading" + +msgid "Data elements" +msgstr "Data elements" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "" msgid "No data" msgstr "" -msgid "Loading" -msgstr "Loading" - msgid "Search by data item name" msgstr "" @@ -113,9 +174,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -128,9 +186,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -143,6 +198,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -209,9 +267,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "បោះបង់" - msgid "Delete" msgstr "​លុប​" @@ -263,6 +318,9 @@ msgstr "ការ​ពិពណ៌នា" msgid "Rename" msgstr "ប្ដូរ​ឈ្មោះ" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -376,6 +434,9 @@ msgstr "" msgid "No results found" msgstr "" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "" @@ -572,48 +633,6 @@ msgstr "" msgid "No periods selected" msgstr "" -msgid "January" -msgstr "មករា​" - -msgid "February" -msgstr "កុម្ភៈ​" - -msgid "March" -msgstr "មីនា​" - -msgid "April" -msgstr "មេសា​" - -msgid "May" -msgstr "ឧសភា​" - -msgid "June" -msgstr "មិថុនា​" - -msgid "July" -msgstr "កក្កដា​" - -msgid "August" -msgstr "សីហា​" - -msgid "September" -msgstr "កញ្ញា​" - -msgid "October" -msgstr "តុលា​" - -msgid "November" -msgstr "វិច្ឆិកា​" - -msgid "December" -msgstr "ធ្នូ​" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "រាល់​ថ្ងៃ" @@ -836,6 +855,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -899,9 +921,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Data elements" - msgid "Data element group" msgstr "" @@ -947,6 +966,33 @@ msgstr "" msgid "Program indicator" msgstr "" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "តួរលេខ" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/lo.po b/i18n/lo.po index ccb6d28a7..30b0de7ef 100644 --- a/i18n/lo.po +++ b/i18n/lo.po @@ -1,17 +1,18 @@ # # Translators: -# phil_dhis2, 2022 # Viktor Varland , 2022 # Somkhit Bouavong , 2022 # Saysamone Sibounma, 2022 +# Phouthasinh PHEUAYSITHIPHONE, 2023 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Saysamone Sibounma, 2022\n" -"Language-Team: Lao (https://www.transifex.com/hisp-uio/teams/100509/lo/)\n" +"Last-Translator: phil_dhis2, 2023\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" "Content-Transfer-Encoding: 8bit\n" @@ -82,10 +83,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Cancel" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -94,15 +130,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "ກໍາລັງໂຫຼດ" + +msgid "Data elements" +msgstr "ອົງປະກອບຂໍ້ມູນ" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "Disaggregation" msgid "No data" msgstr "ບໍ່ມີຂໍ້ມູນ" -msgid "Loading" -msgstr "ກໍາລັງໂຫຼດ" - msgid "Search by data item name" msgstr "" @@ -115,9 +177,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -130,9 +189,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -145,6 +201,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -211,9 +270,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Cancel" - msgid "Delete" msgstr "ລົບ" @@ -265,7 +321,7 @@ msgstr "ລາຍລະອຽດ" msgid "Rename" msgstr "ປ່ຽນຊື່" -msgid "{{objectName}} (copy)" +msgid "{{- objectName}} (copy)" msgstr "" msgid "Save {{fileType}} as" @@ -580,48 +636,6 @@ msgstr "" msgid "No periods selected" msgstr "ບໍ່ທັນໄດ້ເລືອກໄລຍະເວລາ" -msgid "January" -msgstr "ມັງກອນ" - -msgid "February" -msgstr "ກຸມພາ" - -msgid "March" -msgstr "ມີນາ" - -msgid "April" -msgstr "ເມສາ" - -msgid "May" -msgstr "ພຶດສະພາ" - -msgid "June" -msgstr "ມີຖຸນາ" - -msgid "July" -msgstr "ກໍລະກົດ" - -msgid "August" -msgstr "ສິງຫາ" - -msgid "September" -msgstr "ກັນຍາ" - -msgid "October" -msgstr "ຕຸລາ" - -msgid "November" -msgstr "ພະຈິກ" - -msgid "December" -msgstr "ທັນວາ" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "ທຸກໆມື້" @@ -692,13 +706,13 @@ msgid "Last 30 days" msgstr "Last 30 days" msgid "Last 60 days" -msgstr "" +msgstr "60 ມື້ກ່ອນ" msgid "Last 90 days" -msgstr "" +msgstr "90 ມື້ກ່ອນ" msgid "Last 180 days" -msgstr "" +msgstr "180 ມື້ກ່ອນ" msgid "This week" msgstr "ທິດນີ້" @@ -910,9 +924,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "ອົງປະກອບຂໍ້ມູນ" - msgid "Data element group" msgstr "ກຸ່ມຂອລອົງປະກອບຂໍ້ມູນ" @@ -958,6 +969,33 @@ msgstr "ຕົວຊີ້ວັດສະເພາະວຽກຂອງຂະແ msgid "Program indicator" msgstr "Program Indicator Management" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "ຈຳນວນ" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/my.po b/i18n/my.po index 0d57fe7f2..f4933563d 100644 --- a/i18n/my.po +++ b/i18n/my.po @@ -2,15 +2,15 @@ # Translators: # Viktor Varland , 2021 # Wanda , 2022 -# phil_dhis2, 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2022\n" -"Language-Team: Burmese (https://www.transifex.com/hisp-uio/teams/100509/my/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Burmese (https://app.transifex.com/hisp-uio/teams/100509/my/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -81,10 +81,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "ပယ်ဖျက်သည်" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -93,15 +128,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Loading, please wait" + +msgid "Data elements" +msgstr "အချက်အလတ်အစိတ်အပိုင်းများ" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "" msgid "No data" msgstr "အချက်အလက်မရှိပါ။" -msgid "Loading" -msgstr "Loading, please wait" - msgid "Search by data item name" msgstr "" @@ -114,9 +175,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -129,9 +187,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -144,6 +199,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -210,9 +268,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "ပယ်ဖျက်သည်" - msgid "Delete" msgstr "ဖျက်ဆီးသည်" @@ -264,6 +319,9 @@ msgstr "ဖော်ပြချက်" msgid "Rename" msgstr "အမည်သစ်ပေးခြင်း" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -377,6 +435,9 @@ msgstr "" msgid "No results found" msgstr "No results found" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "Created by" @@ -573,48 +634,6 @@ msgstr "" msgid "No periods selected" msgstr "သက်ဆိုင်ရာ အချိန်ကာလကို မရွေးချယ်ထားပါ။" -msgid "January" -msgstr "ဇန်နဝါရီ" - -msgid "February" -msgstr "ဖေဖော်ဝါရီ" - -msgid "March" -msgstr "မတ်" - -msgid "April" -msgstr "ဧပြီ" - -msgid "May" -msgstr "မေ" - -msgid "June" -msgstr "ဇွန်" - -msgid "July" -msgstr "ဇူလိုင်" - -msgid "August" -msgstr "သြဂုတ်" - -msgid "September" -msgstr "စက်တင်ဘာ" - -msgid "October" -msgstr "အောက်တိုဘာ" - -msgid "November" -msgstr "နိုဝင်ဘာ" - -msgid "December" -msgstr "ဒီဇင်ဘာ" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "နေ့စဉ်" @@ -837,6 +856,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -900,9 +922,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "အချက်အလတ်အစိတ်အပိုင်းများ" - msgid "Data element group" msgstr "data element အုပ်စု" @@ -948,6 +967,33 @@ msgstr "" msgid "Program indicator" msgstr "" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "ဂဏန်း" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/nb.po b/i18n/nb.po index a02bfa73b..05f6b1145 100644 --- a/i18n/nb.po +++ b/i18n/nb.po @@ -1,15 +1,15 @@ # # Translators: -# Caroline Hesthagen Holen , 2022 # Karoline Tufte Lien , 2022 +# Caroline Hesthagen Holen , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Karoline Tufte Lien , 2022\n" -"Language-Team: Norwegian Bokmål (https://www.transifex.com/hisp-uio/teams/100509/nb/)\n" +"Last-Translator: Caroline Hesthagen Holen , 2023\n" +"Language-Team: Norwegian Bokmål (https://app.transifex.com/hisp-uio/teams/100509/nb/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -81,11 +81,46 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" -msgstr "Datatype" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "Alle typer" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Avbryt" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" msgid "Totals only" msgstr "Kun totaler" @@ -93,15 +128,41 @@ msgstr "Kun totaler" msgid "Details only" msgstr "Kun detaljer" +msgid "Loading" +msgstr "Laster" + +msgid "Data elements" +msgstr "Dataelementer" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "Ingen dataelementer funnet for \"{{- searchTerm}}\"" + +msgid "No data elements found" +msgstr "Ingen dataelementer funnet" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "Datatype" + +msgid "All types" +msgstr "Alle typer" + msgid "Disaggregation" msgstr "Disaggregering" msgid "No data" msgstr "Ingen data" -msgid "Loading" -msgstr "Laster" - msgid "Search by data item name" msgstr "Søk etter datapunktnavn" @@ -114,9 +175,6 @@ msgstr "Valgte elementer" msgid "No indicators found" msgstr "Ingen indikatorer funnet" -msgid "No data elements found" -msgstr "Ingen dataelementer funnet" - msgid "No data sets found" msgstr "Ingen datasett funnet" @@ -129,9 +187,6 @@ msgstr "Ingen programindikatorer funnet" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "Ingen indikatorer funnet for \"{{- searchTerm}}\"" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "Ingen dataelementer funnet for \"{{- searchTerm}}\"" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "Ingen datasett funnet for \"{{- searchTerm}}\"" @@ -144,6 +199,9 @@ msgstr "Ingen programindikatorer funnet for \"{{- searchTerm}}\"" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "Ingenting funnet for \"{{- searchTerm}}\"" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "Metrisk type" @@ -213,9 +271,6 @@ msgid "" msgstr "" "Denne {{fileType}} og relaterte tolkninger vil bli slettet. Fortsette?" -msgid "Cancel" -msgstr "Avbryt" - msgid "Delete" msgstr "Slett" @@ -267,6 +322,9 @@ msgstr "Beskrivelse" msgid "Rename" msgstr "Gi nytt navn" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "Lagre {{fileType}} som" @@ -380,6 +438,9 @@ msgstr "" msgid "No results found" msgstr "Ingen resultater funnet" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "Opprettet av" @@ -581,48 +642,6 @@ msgstr "Valgte perioder" msgid "No periods selected" msgstr "Ingen perioder er valgt" -msgid "January" -msgstr "Januar" - -msgid "February" -msgstr "Februar" - -msgid "March" -msgstr "Mars" - -msgid "April" -msgstr "April" - -msgid "May" -msgstr "Mai" - -msgid "June" -msgstr "Juni" - -msgid "July" -msgstr "Juli" - -msgid "August" -msgstr "August" - -msgid "September" -msgstr "September" - -msgid "October" -msgstr "Oktober" - -msgid "November" -msgstr "November" - -msgid "December" -msgstr "Desember" - -msgid "Week {{weekNumber}}" -msgstr "Uke {{weekNumber}}" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "Touke {{biWeekNumber}}" - msgid "Daily" msgstr "Daglig" @@ -845,6 +864,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -908,9 +930,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Dataelementer" - msgid "Data element group" msgstr "Dataelementgruppe" @@ -956,6 +975,33 @@ msgstr "Programindikatorer" msgid "Program indicator" msgstr "Programindikator" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Nummer" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "Ekstra liten" diff --git a/i18n/prs.po b/i18n/prs.po index 292673b66..128c2fc79 100644 --- a/i18n/prs.po +++ b/i18n/prs.po @@ -1,14 +1,14 @@ # # Translators: -# phil_dhis2, 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2022\n" -"Language-Team: Persian (Afghanistan) (https://www.transifex.com/hisp-uio/teams/100509/fa_AF/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Persian (Afghanistan) (https://app.transifex.com/hisp-uio/teams/100509/fa_AF/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -80,10 +80,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "انصراف" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -92,15 +127,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "در حال بار کردن" + +msgid "Data elements" +msgstr "دیتا ایلمنت ها" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "تکثیر" msgid "No data" msgstr "معلوماتی وجود ندارد" -msgid "Loading" -msgstr "در حال بار کردن" - msgid "Search by data item name" msgstr "" @@ -113,9 +174,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -128,9 +186,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -143,6 +198,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -209,9 +267,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "انصراف" - msgid "Delete" msgstr "حذف" @@ -263,6 +318,9 @@ msgstr "شرح" msgid "Rename" msgstr "تغییر نام" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -376,6 +434,9 @@ msgstr "" msgid "No results found" msgstr "هیج نتیجه ی یافت نشد" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "ایجاد شده توسط" @@ -575,48 +636,6 @@ msgstr "" msgid "No periods selected" msgstr "هېڅ مودې نه دي غوره شوې" -msgid "January" -msgstr "جنوری" - -msgid "February" -msgstr "فبروری" - -msgid "March" -msgstr "مارچ" - -msgid "April" -msgstr "آپریل" - -msgid "May" -msgstr "می" - -msgid "June" -msgstr "جون" - -msgid "July" -msgstr "جولای" - -msgid "August" -msgstr "آگست" - -msgid "September" -msgstr "سپتامبر" - -msgid "October" -msgstr "اکتبر" - -msgid "November" -msgstr "نوامبر" - -msgid "December" -msgstr "دسامبر" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "روزنه" @@ -839,6 +858,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -902,9 +924,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "دیتا ایلمنت ها" - msgid "Data element group" msgstr "د مالوماتو (ډاټا) د برخې ګروپ" @@ -950,6 +969,33 @@ msgstr "د پروګرام شاخصونه" msgid "Program indicator" msgstr "ایجاد پروگرام شاخص" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "نمبر" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/ps.po b/i18n/ps.po index 988a67ff7..0852be527 100644 --- a/i18n/ps.po +++ b/i18n/ps.po @@ -1,15 +1,15 @@ # # Translators: # Viktor Varland , 2022 -# phil_dhis2, 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2022\n" -"Language-Team: Pashto (https://www.transifex.com/hisp-uio/teams/100509/ps/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Pashto (https://app.transifex.com/hisp-uio/teams/100509/ps/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -81,10 +81,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "رد یې کړئ [ ژباړه ـ ردول ]" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -93,15 +128,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "د پورته کېدو یا لوډینګ په حال کې" + +msgid "Data elements" +msgstr "د مالوماتو (ډاټا) برخې" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "" msgid "No data" msgstr "د مالوماتو (ډاټا) نشتوالۍ" -msgid "Loading" -msgstr "د پورته کېدو یا لوډینګ په حال کې" - msgid "Search by data item name" msgstr "" @@ -114,9 +175,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -129,9 +187,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -144,6 +199,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -210,9 +268,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "رد یې کړئ [ ژباړه ـ ردول ]" - msgid "Delete" msgstr "خذف یې کړئ" @@ -264,6 +319,9 @@ msgstr "تشریح [ ژباړه ـ نښه ـ تشریح ]" msgid "Rename" msgstr "بیا نومول" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -377,6 +435,9 @@ msgstr "" msgid "No results found" msgstr "هېڅ پایله ترلاسه نه شوه" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "لخوا ایجاد یا رامنځ ته شوی" @@ -576,48 +637,6 @@ msgstr "" msgid "No periods selected" msgstr "هېڅ مودې نه دي غوره شوې" -msgid "January" -msgstr "جنوري" - -msgid "February" -msgstr "فبروري" - -msgid "March" -msgstr "مارچ" - -msgid "April" -msgstr "اپرېل" - -msgid "May" -msgstr "می [ میاشت. می]" - -msgid "June" -msgstr "جون" - -msgid "July" -msgstr "جولای" - -msgid "August" -msgstr "اګست" - -msgid "September" -msgstr "سپټمبر" - -msgid "October" -msgstr "اکتوبر" - -msgid "November" -msgstr "نومبر" - -msgid "December" -msgstr "ډیسمبر" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "ورځني [ د مودې ډول ]" @@ -840,6 +859,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -903,9 +925,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "د مالوماتو (ډاټا) برخې" - msgid "Data element group" msgstr "د مالوماتو (ډاټا) د برخې ګروپ" @@ -951,6 +970,33 @@ msgstr "د پروګرام شاخص" msgid "Program indicator" msgstr "" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "عدد [ بشپړ عدد ]" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/pt.po b/i18n/pt.po index a5a8c0f1d..d1b69e4ef 100644 --- a/i18n/pt.po +++ b/i18n/pt.po @@ -1,19 +1,19 @@ # # Translators: # Sheila André , 2020 -# phil_dhis2, 2022 # Gabriela Rodriguez , 2022 # Ge Joao , 2022 # Viktor Varland , 2022 # Fernando Jorge Bade, 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Fernando Jorge Bade, 2022\n" -"Language-Team: Portuguese (https://www.transifex.com/hisp-uio/teams/100509/pt/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Portuguese (https://app.transifex.com/hisp-uio/teams/100509/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -86,11 +86,46 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" -msgstr "Tipo de dados" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "Todos os tipos" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Cancelar" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" msgid "Totals only" msgstr "" @@ -98,15 +133,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Carregando.." + +msgid "Data elements" +msgstr "Elementos de Dados" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "Tipo de dados" + +msgid "All types" +msgstr "Todos os tipos" + msgid "Disaggregation" msgstr "Desagregação" msgid "No data" msgstr "Não ha dados" -msgid "Loading" -msgstr "Carregando.." - msgid "Search by data item name" msgstr "" @@ -119,9 +180,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -134,9 +192,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -149,6 +204,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -215,9 +273,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Cancelar" - msgid "Delete" msgstr "Suprimir" @@ -269,7 +324,7 @@ msgstr "Descrição" msgid "Rename" msgstr "Renomear" -msgid "{{objectName}} (copy)" +msgid "{{- objectName}} (copy)" msgstr "" msgid "Save {{fileType}} as" @@ -590,48 +645,6 @@ msgstr "" msgid "No periods selected" msgstr "Não há períodos relativos seleccionados" -msgid "January" -msgstr "Janeiro" - -msgid "February" -msgstr "Fevereiro" - -msgid "March" -msgstr "Março" - -msgid "April" -msgstr "Abril" - -msgid "May" -msgstr "Maio" - -msgid "June" -msgstr "Junho" - -msgid "July" -msgstr "Julho" - -msgid "August" -msgstr "Agosto" - -msgid "September" -msgstr "Setembro" - -msgid "October" -msgstr "Outubro" - -msgid "November" -msgstr "Novembro" - -msgid "December" -msgstr "Dezembro" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "Diário" @@ -920,9 +933,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Elementos de Dados" - msgid "Data element group" msgstr "Grupo de Elemento de Dados" @@ -968,6 +978,33 @@ msgstr "Indicador do programa" msgid "Program indicator" msgstr "Gestão do indicadores de programa" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Número" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/pt_BR.po b/i18n/pt_BR.po index 63c02c3d3..5d67e5a44 100644 --- a/i18n/pt_BR.po +++ b/i18n/pt_BR.po @@ -1,15 +1,15 @@ # # Translators: # Viktor Varland , 2021 -# phil_dhis2, 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2022\n" -"Language-Team: Portuguese (Brazil) (https://www.transifex.com/hisp-uio/teams/100509/pt_BR/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Portuguese (Brazil) (https://app.transifex.com/hisp-uio/teams/100509/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -82,10 +82,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Cancelar" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -94,15 +129,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Carregando.." + +msgid "Data elements" +msgstr "Os elementos de dados" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "" msgid "No data" msgstr "Não ha dados" -msgid "Loading" -msgstr "Carregando.." - msgid "Search by data item name" msgstr "" @@ -115,9 +176,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -130,9 +188,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -145,6 +200,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -211,9 +269,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Cancelar" - msgid "Delete" msgstr "Apagar" @@ -265,6 +320,9 @@ msgstr "Descrição" msgid "Rename" msgstr "Renomear" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -378,6 +436,9 @@ msgstr "" msgid "No results found" msgstr "" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "" @@ -580,48 +641,6 @@ msgstr "" msgid "No periods selected" msgstr "Não há períodos relativos seleccionados" -msgid "January" -msgstr "Janeiro" - -msgid "February" -msgstr "Fevereiro" - -msgid "March" -msgstr "Março" - -msgid "April" -msgstr "Abril" - -msgid "May" -msgstr "Maio" - -msgid "June" -msgstr "Junho" - -msgid "July" -msgstr "Julho" - -msgid "August" -msgstr "Agosto" - -msgid "September" -msgstr "Setembro" - -msgid "October" -msgstr "Outubro" - -msgid "November" -msgstr "Novembro" - -msgid "December" -msgstr "Dezembro" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "Diário" @@ -844,6 +863,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -907,9 +929,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Os elementos de dados" - msgid "Data element group" msgstr "Grupo de elemento de dado" @@ -955,6 +974,33 @@ msgstr "" msgid "Program indicator" msgstr "" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Número" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/ru.po b/i18n/ru.po index e44255a90..6c11677b6 100644 --- a/i18n/ru.po +++ b/i18n/ru.po @@ -5,15 +5,15 @@ # Wanda , 2022 # Viktor Varland , 2022 # Ulanbek Abakirov , 2022 -# phil_dhis2, 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2022\n" -"Language-Team: Russian (https://www.transifex.com/hisp-uio/teams/100509/ru/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Russian (https://app.transifex.com/hisp-uio/teams/100509/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -87,11 +87,46 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" -msgstr "Тип данных" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "Все типы" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Отмена" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" msgid "Totals only" msgstr "" @@ -99,15 +134,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Загрузка" + +msgid "Data elements" +msgstr "Элементы данных" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "Тип данных" + +msgid "All types" +msgstr "Все типы" + msgid "Disaggregation" msgstr "Разукрупнение" msgid "No data" msgstr "Нет данных" -msgid "Loading" -msgstr "Загрузка" - msgid "Search by data item name" msgstr "" @@ -120,9 +181,6 @@ msgstr "Выбранные элементы" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -135,9 +193,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -150,6 +205,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -216,9 +274,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Отмена" - msgid "Delete" msgstr "Удалить" @@ -270,6 +325,9 @@ msgstr "Описание" msgid "Rename" msgstr "Переименовать" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -383,6 +441,9 @@ msgstr "" msgid "No results found" msgstr "Результаты не найдены" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "Создано" @@ -588,48 +649,6 @@ msgstr "Выбранные периоды" msgid "No periods selected" msgstr "Периоды не выбраны" -msgid "January" -msgstr "Январь" - -msgid "February" -msgstr "Февраль" - -msgid "March" -msgstr "Март" - -msgid "April" -msgstr "Апрель" - -msgid "May" -msgstr "Май" - -msgid "June" -msgstr "Июнь" - -msgid "July" -msgstr "Июль" - -msgid "August" -msgstr "Август" - -msgid "September" -msgstr "Сентябрь" - -msgid "October" -msgstr "Октябрь" - -msgid "November" -msgstr "Ноябрь" - -msgid "December" -msgstr "Декабрь" - -msgid "Week {{weekNumber}}" -msgstr "Неделя{{номерНедели}}" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "Двухнедельный {{biWeekNumber}}" - msgid "Daily" msgstr "Ежедневно" @@ -852,6 +871,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -915,9 +937,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Элементы данных" - msgid "Data element group" msgstr "Группы элемента данных" @@ -963,6 +982,33 @@ msgstr "Индикаторы программы" msgid "Program indicator" msgstr "Program indicator" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Число" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "Очень маленький" diff --git a/i18n/sv.po b/i18n/sv.po index 82271983c..afe88915f 100644 --- a/i18n/sv.po +++ b/i18n/sv.po @@ -1,15 +1,15 @@ # # Translators: -# phil_dhis2, 2022 # Viktor Varland , 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Viktor Varland , 2022\n" -"Language-Team: Swedish (https://www.transifex.com/hisp-uio/teams/100509/sv/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Swedish (https://app.transifex.com/hisp-uio/teams/100509/sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -81,10 +81,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Avbryt" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -93,13 +128,39 @@ msgstr "" msgid "Details only" msgstr "" -msgid "Disaggregation" +msgid "Loading" msgstr "" -msgid "No data" +msgid "Data elements" +msgstr "dataelement" + +msgid "Search by data element name" msgstr "" -msgid "Loading" +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + +msgid "Disaggregation" +msgstr "" + +msgid "No data" msgstr "" msgid "Search by data item name" @@ -114,9 +175,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -129,9 +187,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -144,6 +199,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -210,9 +268,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Avbryt" - msgid "Delete" msgstr "Radera" @@ -264,6 +319,9 @@ msgstr "Beskrivning" msgid "Rename" msgstr "Döpa om" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -377,6 +435,9 @@ msgstr "" msgid "No results found" msgstr "Inga resultat hittades" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "Skapad av" @@ -576,48 +637,6 @@ msgstr "" msgid "No periods selected" msgstr "" -msgid "January" -msgstr "januari" - -msgid "February" -msgstr "februari" - -msgid "March" -msgstr "Mars" - -msgid "April" -msgstr "april" - -msgid "May" -msgstr "Maj" - -msgid "June" -msgstr "juni" - -msgid "July" -msgstr "juli" - -msgid "August" -msgstr "augusti" - -msgid "September" -msgstr "september" - -msgid "October" -msgstr "oktober" - -msgid "November" -msgstr "november" - -msgid "December" -msgstr "december" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "Dagligen" @@ -840,6 +859,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -903,9 +925,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "dataelement" - msgid "Data element group" msgstr "Dataelementgrupp" @@ -951,6 +970,33 @@ msgstr "programindikatorer" msgid "Program indicator" msgstr "" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Antal" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/tet.po b/i18n/tet.po index 3b373dc73..e6dd8285a 100644 --- a/i18n/tet.po +++ b/i18n/tet.po @@ -1,15 +1,15 @@ # # Translators: -# phil_dhis2, 2022 # Viktor Varland , 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Viktor Varland , 2022\n" -"Language-Team: Tetum (Tetun) (https://www.transifex.com/hisp-uio/teams/100509/tet/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Tetum (Tetun) (https://app.transifex.com/hisp-uio/teams/100509/tet/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -80,10 +80,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Kansela" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -92,15 +127,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "" + +msgid "Data elements" +msgstr "Elementus dadus" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "" msgid "No data" msgstr "Laiha dadus" -msgid "Loading" -msgstr "" - msgid "Search by data item name" msgstr "" @@ -113,9 +174,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -128,9 +186,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -143,6 +198,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -209,9 +267,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Kansela" - msgid "Delete" msgstr "Soe" @@ -263,6 +318,9 @@ msgstr "Deskrisaun" msgid "Rename" msgstr "Tau naran foun" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -376,6 +434,9 @@ msgstr "" msgid "No results found" msgstr "" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "Kria husi" @@ -572,48 +633,6 @@ msgstr "" msgid "No periods selected" msgstr "Laiha períodu selesionadu" -msgid "January" -msgstr "Janeiru" - -msgid "February" -msgstr "Fevereiru" - -msgid "March" -msgstr "Marsu" - -msgid "April" -msgstr "Abril" - -msgid "May" -msgstr "Mai" - -msgid "June" -msgstr "Juñu" - -msgid "July" -msgstr "Jullu" - -msgid "August" -msgstr "Agostu" - -msgid "September" -msgstr "Setembru" - -msgid "October" -msgstr "Outubru" - -msgid "November" -msgstr "Novembru" - -msgid "December" -msgstr "Dezembru" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "Diáriu" @@ -836,6 +855,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -899,9 +921,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Elementus dadus" - msgid "Data element group" msgstr "" @@ -947,6 +966,33 @@ msgstr "" msgid "Program indicator" msgstr "" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Númeru" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/tg.po b/i18n/tg.po index 3d0d42a16..cd5d4de83 100644 --- a/i18n/tg.po +++ b/i18n/tg.po @@ -1,15 +1,15 @@ # # Translators: -# phil_dhis2, 2022 # Viktor Varland , 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Viktor Varland , 2022\n" -"Language-Team: Tajik (https://www.transifex.com/hisp-uio/teams/100509/tg/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Tajik (https://app.transifex.com/hisp-uio/teams/100509/tg/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -81,10 +81,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Бекор кардан" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -93,15 +128,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Бор мешавад" + +msgid "Data elements" +msgstr "Унсурҳои иттилоот" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "" msgid "No data" msgstr "Ягон иттилоот нест" -msgid "Loading" -msgstr "Бор мешавад" - msgid "Search by data item name" msgstr "" @@ -114,9 +175,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -129,9 +187,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -144,6 +199,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -210,9 +268,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Бекор кардан" - msgid "Delete" msgstr "Нест кардан" @@ -264,6 +319,9 @@ msgstr "Тавсиф" msgid "Rename" msgstr "Ивази ном" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -377,6 +435,9 @@ msgstr "" msgid "No results found" msgstr "" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "" @@ -576,48 +637,6 @@ msgstr "" msgid "No periods selected" msgstr "Ягон давра инитихоб нашудааст" -msgid "January" -msgstr "Январ" - -msgid "February" -msgstr "Феврал" - -msgid "March" -msgstr "Март" - -msgid "April" -msgstr "Апрел" - -msgid "May" -msgstr "Май" - -msgid "June" -msgstr "Июн" - -msgid "July" -msgstr "Июл" - -msgid "August" -msgstr "Август" - -msgid "September" -msgstr "Сентябр" - -msgid "October" -msgstr "Октябр" - -msgid "November" -msgstr "Ноябр" - -msgid "December" -msgstr "Декабр" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "Ҳаррӯза" @@ -840,6 +859,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -903,9 +925,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Унсурҳои иттилоот" - msgid "Data element group" msgstr "Гурӯҳи унсурҳои иттилоот" @@ -951,6 +970,33 @@ msgstr "" msgid "Program indicator" msgstr "Идоракунии индикатори барнома" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Рақам" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/uk.po b/i18n/uk.po index f026b4a3c..14d00b5d9 100644 --- a/i18n/uk.po +++ b/i18n/uk.po @@ -1,18 +1,18 @@ # # Translators: # Wanda , 2022 -# phil_dhis2, 2022 # Viktor Varland , 2022 -# Éva Tamási, 2022 # Nadiia , 2022 +# Éva Tamási, 2023 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Nadiia , 2022\n" -"Language-Team: Ukrainian (https://www.transifex.com/hisp-uio/teams/100509/uk/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Ukrainian (https://app.transifex.com/hisp-uio/teams/100509/uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -86,11 +86,46 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" -msgstr "Всі типи" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Скасувати" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "Так, видалити" msgid "Totals only" msgstr "" @@ -98,15 +133,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Завантаження" + +msgid "Data elements" +msgstr "Елементи даних" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "Всі типи" + msgid "Disaggregation" msgstr "" msgid "No data" msgstr "Немає даних" -msgid "Loading" -msgstr "Завантаження" - msgid "Search by data item name" msgstr "" @@ -119,9 +180,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -134,9 +192,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -149,6 +204,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -215,9 +273,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Скасувати" - msgid "Delete" msgstr "Видалити" @@ -269,7 +324,7 @@ msgstr "Змалювання" msgid "Rename" msgstr "Перейменувати" -msgid "{{objectName}} (copy)" +msgid "{{- objectName}} (copy)" msgstr "" msgid "Save {{fileType}} as" @@ -593,48 +648,6 @@ msgstr "" msgid "No periods selected" msgstr "" -msgid "January" -msgstr "Січень" - -msgid "February" -msgstr "Лютий" - -msgid "March" -msgstr "Березень" - -msgid "April" -msgstr "Квітень" - -msgid "May" -msgstr "Травень" - -msgid "June" -msgstr "Червень" - -msgid "July" -msgstr "Липень" - -msgid "August" -msgstr "Серпень" - -msgid "September" -msgstr "Вересень" - -msgid "October" -msgstr "Жовтень" - -msgid "November" -msgstr "Листопад" - -msgid "December" -msgstr "Грудень" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "Щодня" @@ -923,9 +936,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Елементи даних" - msgid "Data element group" msgstr "Група елементів даних" @@ -971,6 +981,33 @@ msgstr "Програмні показники" msgid "Program indicator" msgstr "Program indicator" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Кількість" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/ur.po b/i18n/ur.po index 3440cd76e..d43274683 100644 --- a/i18n/ur.po +++ b/i18n/ur.po @@ -1,15 +1,15 @@ # # Translators: -# phil_dhis2, 2022 # Viktor Varland , 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Viktor Varland , 2022\n" -"Language-Team: Urdu (https://www.transifex.com/hisp-uio/teams/100509/ur/)\n" +"Last-Translator: phil_dhis2, 2023\n" +"Language-Team: Urdu (https://app.transifex.com/hisp-uio/teams/100509/ur/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -81,10 +81,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "منسوخ" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -93,15 +128,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "لوڈ کر رہا ہے" + +msgid "Data elements" +msgstr "ڈیٹا عنصر" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "ناجائز" msgid "No data" msgstr "کوئی مواد نہیں" -msgid "Loading" -msgstr "لوڈ کر رہا ہے" - msgid "Search by data item name" msgstr "" @@ -114,9 +175,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -129,9 +187,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -144,6 +199,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -210,9 +268,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "منسوخ" - msgid "Delete" msgstr "حذف" @@ -264,6 +319,9 @@ msgstr "تفصیلات" msgid "Rename" msgstr "نام بدلو" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -377,6 +435,9 @@ msgstr "" msgid "No results found" msgstr "کوئی نتیجہ نہیں ملا" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "تخلیق کیا" @@ -576,48 +637,6 @@ msgstr "" msgid "No periods selected" msgstr "کوئی مدت منتخب نہیں" -msgid "January" -msgstr "جنوری" - -msgid "February" -msgstr "فروری" - -msgid "March" -msgstr "مارچ" - -msgid "April" -msgstr "اپریل" - -msgid "May" -msgstr "مئی" - -msgid "June" -msgstr "جون" - -msgid "July" -msgstr "جولائی" - -msgid "August" -msgstr "اگست" - -msgid "September" -msgstr "ستمبر" - -msgid "October" -msgstr "اکتوبر" - -msgid "November" -msgstr "نومبر" - -msgid "December" -msgstr "دسمبر" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "روزانہ" @@ -840,6 +859,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -903,9 +925,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "ڈیٹا عنصر" - msgid "Data element group" msgstr "ڈیٹا عناصر گروپ" @@ -951,6 +970,33 @@ msgstr "پروگرام اشارے" msgid "Program indicator" msgstr "پروگرام کا اشارہ" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "نمبر" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" diff --git a/i18n/uz.po b/i18n/uz.po index 8e5c02dce..70f05028e 100644 --- a/i18n/uz.po +++ b/i18n/uz.po @@ -1,14 +1,14 @@ # # Translators: -# Ibatov , 2022 +# Ibatov , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Ibatov , 2022\n" -"Language-Team: Uzbek (Cyrillic) (https://www.transifex.com/hisp-uio/teams/100509/uz@Cyrl/)\n" +"Last-Translator: Ibatov , 2023\n" +"Language-Team: Uzbek (Cyrillic) (https://app.transifex.com/hisp-uio/teams/100509/uz@Cyrl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -79,11 +79,46 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" -msgstr "Маълумот тури" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "Барча турлари" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Бекор қилиш" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" msgid "Totals only" msgstr "" @@ -91,15 +126,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Юкланмоқда" + +msgid "Data elements" +msgstr "Маълумот элементлари" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "Маълумот тури" + +msgid "All types" +msgstr "Барча турлари" + msgid "Disaggregation" msgstr "Ажратиш" msgid "No data" msgstr "Маълумот йўқ" -msgid "Loading" -msgstr "Юкланмоқда" - msgid "Search by data item name" msgstr "" @@ -112,9 +173,6 @@ msgstr "Элемент танланган" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -127,9 +185,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -142,6 +197,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -208,9 +266,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Бекор қилиш" - msgid "Delete" msgstr "Ўчириб ташлаш" @@ -262,7 +317,7 @@ msgstr "Тавсиф" msgid "Rename" msgstr "Қайта номлашч" -msgid "{{objectName}} (copy)" +msgid "{{- objectName}} (copy)" msgstr "" msgid "Save {{fileType}} as" @@ -577,48 +632,6 @@ msgstr "Танланган Даврлар" msgid "No periods selected" msgstr "Давр танланмаган" -msgid "January" -msgstr "Январь" - -msgid "February" -msgstr "Февраль" - -msgid "March" -msgstr "Март" - -msgid "April" -msgstr "Апрель" - -msgid "May" -msgstr "Май" - -msgid "June" -msgstr "Июнь" - -msgid "July" -msgstr "Июль" - -msgid "August" -msgstr "Август" - -msgid "September" -msgstr "Сентябрь" - -msgid "October" -msgstr "Октябрь" - -msgid "November" -msgstr "Ноябрь" - -msgid "December" -msgstr "Декабрь" - -msgid "Week {{weekNumber}}" -msgstr "Ҳафта {{ҳафтаРақам}}" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "Жуфт ҳафта {{жуфтҲафтаРақам}}" - msgid "Daily" msgstr "Кунлик" @@ -907,9 +920,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Маълумот элементлари" - msgid "Data element group" msgstr "Маълумотлар элементи гуруҳи" @@ -955,6 +965,33 @@ msgstr "Дастур индикаторлари" msgid "Program indicator" msgstr "Дастур индикатори" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Тартиб рақами" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "Жуда Кичик" diff --git a/i18n/uz_Latn.po b/i18n/uz_Latn.po index 8a63fddb3..44b9ee9a8 100644 --- a/i18n/uz_Latn.po +++ b/i18n/uz_Latn.po @@ -1,14 +1,14 @@ # # Translators: -# Yury Rogachev , 2022 +# Yury Rogachev , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Yury Rogachev , 2022\n" -"Language-Team: Uzbek (Latin) (https://www.transifex.com/hisp-uio/teams/100509/uz@Latn/)\n" +"Last-Translator: Yury Rogachev , 2023\n" +"Language-Team: Uzbek (Latin) (https://app.transifex.com/hisp-uio/teams/100509/uz@Latn/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -79,11 +79,46 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" -msgstr "Maʼlumot turi" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "Barcha turlari" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Бекор қилиш" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" msgid "Totals only" msgstr "" @@ -91,15 +126,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Yuklanmoqda" + +msgid "Data elements" +msgstr "Maʼlumot elementlari" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "Maʼlumot turi" + +msgid "All types" +msgstr "Barcha turlari" + msgid "Disaggregation" msgstr "" msgid "No data" msgstr "Maʼlumot yoʼq" -msgid "Loading" -msgstr "Yuklanmoqda" - msgid "Search by data item name" msgstr "" @@ -112,9 +173,6 @@ msgstr "Element tanlangan" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -127,9 +185,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -142,6 +197,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -208,9 +266,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Бекор қилиш" - msgid "Delete" msgstr "Oʼchirib tashlash" @@ -262,6 +317,9 @@ msgstr "Tavsif" msgid "Rename" msgstr "Қайта номлаш" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -375,6 +433,9 @@ msgstr "" msgid "No results found" msgstr "Hech qanday natija topilmadi" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "Томонидан яратилган" @@ -571,48 +632,6 @@ msgstr "Tanlangan Davrlar" msgid "No periods selected" msgstr "Davr tanlanmagan" -msgid "January" -msgstr "Yanvar" - -msgid "February" -msgstr "Fevral" - -msgid "March" -msgstr "Mart" - -msgid "April" -msgstr "Аprel" - -msgid "May" -msgstr "May" - -msgid "June" -msgstr "Iyun" - -msgid "July" -msgstr "Iyul" - -msgid "August" -msgstr "Аvgust" - -msgid "September" -msgstr "Sentyabr" - -msgid "October" -msgstr "Oktyabr" - -msgid "November" -msgstr "Noyabr" - -msgid "December" -msgstr "Dekabr" - -msgid "Week {{weekNumber}}" -msgstr "Hafta {{weekNumber}}" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "Juft hafta {{biWeekNumber}}" - msgid "Daily" msgstr "Kunlik" @@ -835,6 +854,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -898,9 +920,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Maʼlumot elementlari" - msgid "Data element group" msgstr "Maʼlumotlar elementi guruhi" @@ -946,6 +965,33 @@ msgstr "Dastur indikatorlari" msgid "Program indicator" msgstr "Dastur indikatori" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Tartib raqami" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "Juda Kichik" diff --git a/i18n/vi.po b/i18n/vi.po index 95144d8fa..792fa3ce4 100644 --- a/i18n/vi.po +++ b/i18n/vi.po @@ -1,16 +1,16 @@ # # Translators: # Mai Nguyen , 2022 -# phil_dhis2, 2022 # Viktor Varland , 2022 +# phil_dhis2, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Viktor Varland , 2022\n" -"Language-Team: Vietnamese (https://www.transifex.com/hisp-uio/teams/100509/vi/)\n" +"Last-Translator: phil_dhis2, 2023\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" "Content-Transfer-Encoding: 8bit\n" @@ -81,11 +81,46 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" -msgstr "Loại dữ liệu" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "Các loại" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Hủy" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" msgid "Totals only" msgstr "" @@ -93,15 +128,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "Đang tải" + +msgid "Data elements" +msgstr "Các phần tử dữ liệu" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "Loại dữ liệu" + +msgid "All types" +msgstr "Các loại" + msgid "Disaggregation" msgstr "Phân Tách Phần Tử Dữ Liệu" msgid "No data" msgstr "Không có dữ liệu" -msgid "Loading" -msgstr "Đang tải" - msgid "Search by data item name" msgstr "" @@ -114,9 +175,6 @@ msgstr "Các mục đã chọn" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -129,9 +187,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -144,6 +199,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -210,9 +268,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Hủy" - msgid "Delete" msgstr "Xóa" @@ -264,6 +319,9 @@ msgstr "Miêu tả" msgid "Rename" msgstr "Đổi tên" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -377,6 +435,9 @@ msgstr "" msgid "No results found" msgstr "Không tìm thấy kết quả nào" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "Được tạo bởi" @@ -573,48 +634,6 @@ msgstr "Thời điểm đã chọn" msgid "No periods selected" msgstr "Chưa chọn thời điểm" -msgid "January" -msgstr "Tháng một" - -msgid "February" -msgstr "Tháng hai" - -msgid "March" -msgstr "Tháng ba" - -msgid "April" -msgstr "Tháng tư" - -msgid "May" -msgstr "Tháng năm" - -msgid "June" -msgstr "Tháng sáu" - -msgid "July" -msgstr "Tháng bảy" - -msgid "August" -msgstr "Tháng tám" - -msgid "September" -msgstr "Tháng chín" - -msgid "October" -msgstr "Tháng mười" - -msgid "November" -msgstr "Tháng mười một" - -msgid "December" -msgstr "Tháng mười hai" - -msgid "Week {{weekNumber}}" -msgstr "Tuần {{weekNumber}}" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "Hai tuần {{biWeekNumber}}" - msgid "Daily" msgstr "Hàng ngày" @@ -837,6 +856,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -900,9 +922,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Các phần tử dữ liệu" - msgid "Data element group" msgstr "Nhóm dữ liệu" @@ -948,6 +967,33 @@ msgstr "Chỉ Số Chương Trình" msgid "Program indicator" msgstr "Quản lý Chỉ số chương trình" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Số" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "Rất nhỏ" diff --git a/i18n/zh.po b/i18n/zh.po index d2a473adc..a21e5fe34 100644 --- a/i18n/zh.po +++ b/i18n/zh.po @@ -1,17 +1,17 @@ # # Translators: # Viktor Varland , 2021 -# 晓东 林 <13981924470@126.com>, 2022 # phil_dhis2, 2022 # easylin , 2022 +# 晓东 林 <13981924470@126.com>, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: easylin , 2022\n" -"Language-Team: Chinese (https://www.transifex.com/hisp-uio/teams/100509/zh/)\n" +"Last-Translator: 晓东 林 <13981924470@126.com>, 2023\n" +"Language-Team: Chinese (https://app.transifex.com/hisp-uio/teams/100509/zh/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -82,11 +82,46 @@ msgstr "此应用无法检索所需数据。" msgid "Network error" msgstr "网络错误" -msgid "Data Type" -msgstr "数据类型" +msgid "Data / Edit calculation" +msgstr "" -msgid "All types" -msgstr "所有类型" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "取消" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" msgid "Totals only" msgstr "总计" @@ -94,15 +129,41 @@ msgstr "总计" msgid "Details only" msgstr "仅详细信息" +msgid "Loading" +msgstr "载入" + +msgid "Data elements" +msgstr "数据元" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "找不到“ {{-searchTerm}}”的数据元素" + +msgid "No data elements found" +msgstr "找不到数据元素" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "数据类型" + +msgid "All types" +msgstr "所有类型" + msgid "Disaggregation" msgstr "分解" msgid "No data" msgstr "无数据" -msgid "Loading" -msgstr "载入" - msgid "Search by data item name" msgstr "按数据项名称搜索" @@ -115,9 +176,6 @@ msgstr "选择的条目" msgid "No indicators found" msgstr "找不到指标" -msgid "No data elements found" -msgstr "找不到数据元素" - msgid "No data sets found" msgstr "找不到数据集" @@ -130,9 +188,6 @@ msgstr "找不到项目指标" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "找不到“ {{-searchTerm}}”的指标" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "找不到“ {{-searchTerm}}”的数据元素" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "找不到“ {{-searchTerm}}”的数据集" @@ -145,6 +200,9 @@ msgstr "找不到“ {{-searchTerm}}”的计划指标" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "找不到“ {{-searchTerm}}”" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "尺度类型" @@ -211,9 +269,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "此{{fileType}}和相关解释将被删除。继续?" -msgid "Cancel" -msgstr "取消" - msgid "Delete" msgstr "删除" @@ -265,8 +320,8 @@ msgstr "描述" msgid "Rename" msgstr "改名" -msgid "{{objectName}} (copy)" -msgstr "{{objectName}}(副本)" +msgid "{{- objectName}} (copy)" +msgstr "" msgid "Save {{fileType}} as" msgstr "将{{fileType}}另存为" @@ -580,48 +635,6 @@ msgstr "选择的期间" msgid "No periods selected" msgstr "未选择期间" -msgid "January" -msgstr "1月" - -msgid "February" -msgstr "2月" - -msgid "March" -msgstr "3月" - -msgid "April" -msgstr "4月" - -msgid "May" -msgstr "5月" - -msgid "June" -msgstr "6月" - -msgid "July" -msgstr "7月" - -msgid "August" -msgstr "8月" - -msgid "September" -msgstr "9月" - -msgid "October" -msgstr "10月" - -msgid "November" -msgstr "11月" - -msgid "December" -msgstr "12月" - -msgid "Week {{weekNumber}}" -msgstr "{{weekNumber}}周" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr " {{biWeekNumber}} 两周" - msgid "Daily" msgstr "每日" @@ -910,9 +923,6 @@ msgstr "找不到指标组" msgid "Loading indicator groups" msgstr "加载指标组" -msgid "Data elements" -msgstr "数据元" - msgid "Data element group" msgstr "数据元组" @@ -958,6 +968,33 @@ msgstr "项目指标" msgid "Program indicator" msgstr "项目指标" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "数据" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "特小" diff --git a/i18n/zh_CN.po b/i18n/zh_CN.po index e6138e1f0..21a6c2c1d 100644 --- a/i18n/zh_CN.po +++ b/i18n/zh_CN.po @@ -1,15 +1,15 @@ # # Translators: # 晓东 林 <13981924470@126.com>, 2022 -# easylin , 2022 +# easylin , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-22T09:12:05.760Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: easylin , 2022\n" -"Language-Team: Chinese (China) (https://www.transifex.com/hisp-uio/teams/100509/zh_CN/)\n" +"Last-Translator: easylin , 2023\n" +"Language-Team: Chinese (China) (https://app.transifex.com/hisp-uio/teams/100509/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -80,10 +80,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "取消" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -92,15 +127,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "载入" + +msgid "Data elements" +msgstr "数据元" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "分解" msgid "No data" msgstr "无数据" -msgid "Loading" -msgstr "载入" - msgid "Search by data item name" msgstr "" @@ -113,9 +174,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -128,9 +186,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -143,6 +198,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -209,9 +267,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "取消" - msgid "Delete" msgstr "删除" @@ -263,6 +318,9 @@ msgstr "描述" msgid "Rename" msgstr "重命名" +msgid "{{- objectName}} (copy)" +msgstr "" + msgid "Save {{fileType}} as" msgstr "" @@ -376,6 +434,9 @@ msgstr "" msgid "No results found" msgstr "没有结果" +msgid "Not available offline" +msgstr "" + msgid "Created by" msgstr "创建自" @@ -572,48 +633,6 @@ msgstr "" msgid "No periods selected" msgstr "" -msgid "January" -msgstr "01" - -msgid "February" -msgstr "02" - -msgid "March" -msgstr "03" - -msgid "April" -msgstr "04" - -msgid "May" -msgstr "05" - -msgid "June" -msgstr "06" - -msgid "July" -msgstr "07" - -msgid "August" -msgstr "08" - -msgid "September" -msgstr "09" - -msgid "October" -msgstr "10" - -msgid "November" -msgstr "11" - -msgid "December" -msgstr "12" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "每日" @@ -836,6 +855,9 @@ msgstr "" msgid "Save translations" msgstr "" +msgid "Cannot save while offline" +msgstr "" + msgid "Could not load translations" msgstr "" @@ -899,9 +921,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "数据元" - msgid "Data element group" msgstr "数据元组" @@ -947,6 +966,33 @@ msgstr "项目指标" msgid "Program indicator" msgstr "项目指标" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "数字" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" From 0f71ad3258f0fac99e53e78eba8968cd235870a8 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 23 May 2023 08:36:35 +0000 Subject: [PATCH 057/285] chore(release): cut 25.1.3 [skip ci] ## [25.1.3](https://github.com/dhis2/analytics/compare/v25.1.2...v25.1.3) (2023-05-23) ### Bug Fixes * **translations:** sync translations from transifex (master) ([869543f](https://github.com/dhis2/analytics/commit/869543f14a03d6bcfad8e009ab9ac4bc87d16de1)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b4bfe721..eb209ffb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.3](https://github.com/dhis2/analytics/compare/v25.1.2...v25.1.3) (2023-05-23) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([869543f](https://github.com/dhis2/analytics/commit/869543f14a03d6bcfad8e009ab9ac4bc87d16de1)) + ## [25.1.2](https://github.com/dhis2/analytics/compare/v25.1.1...v25.1.2) (2023-05-03) diff --git a/package.json b/package.json index 860289976..5057b1cd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.2", + "version": "25.1.3", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 0faecefb62fc285110d76bf60a9382af156b1c85 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 24 May 2023 03:45:44 +0200 Subject: [PATCH 058/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/lo.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/i18n/lo.po b/i18n/lo.po index 30b0de7ef..ec754e79f 100644 --- a/i18n/lo.po +++ b/i18n/lo.po @@ -3,15 +3,15 @@ # Viktor Varland , 2022 # Somkhit Bouavong , 2022 # Saysamone Sibounma, 2022 -# Phouthasinh PHEUAYSITHIPHONE, 2023 # phil_dhis2, 2023 +# Phouthasinh PHEUAYSITHIPHONE, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2023\n" +"Last-Translator: Phouthasinh PHEUAYSITHIPHONE, 2023\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" @@ -733,7 +733,7 @@ msgid "Weeks this year" msgstr "ອາທິດ ຂອງປີນີ້" msgid "This bi-week" -msgstr "" +msgstr "ສອງເທື່ອຕໍ່ອາທິດນີ້" msgid "Last bi-week" msgstr "" @@ -820,7 +820,7 @@ msgid "Weeks" msgstr "ອາທິດ" msgid "Bi-weeks" -msgstr "" +msgstr "ສອງເທື່ອຕໍ່ທິດ" msgid "Months" msgstr "ເດືອນ" @@ -1084,7 +1084,7 @@ msgid "Assigned Categories" msgstr "" msgid "Pivot table" -msgstr "" +msgstr "ແບບຕາຕະລາງໄພວ໋ອດ" msgid "Area" msgstr "" @@ -1114,7 +1114,7 @@ msgid "Line" msgstr "" msgid "Line list" -msgstr "" +msgstr "ລາຍການລາຍບຸກຄົນ" msgid "Year over year (line)" msgstr "" From 4c5c3a6f0fe7a424062fd74504e743700cf173e7 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 24 May 2023 01:50:30 +0000 Subject: [PATCH 059/285] chore(release): cut 25.1.4 [skip ci] ## [25.1.4](https://github.com/dhis2/analytics/compare/v25.1.3...v25.1.4) (2023-05-24) ### Bug Fixes * **translations:** sync translations from transifex (master) ([0faecef](https://github.com/dhis2/analytics/commit/0faecefb62fc285110d76bf60a9382af156b1c85)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb209ffb8..f1c0288eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.4](https://github.com/dhis2/analytics/compare/v25.1.3...v25.1.4) (2023-05-24) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([0faecef](https://github.com/dhis2/analytics/commit/0faecefb62fc285110d76bf60a9382af156b1c85)) + ## [25.1.3](https://github.com/dhis2/analytics/compare/v25.1.2...v25.1.3) (2023-05-23) diff --git a/package.json b/package.json index 5057b1cd1..fee3ac556 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.3", + "version": "25.1.4", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 9c3165ee58559c6c3601b0e12ee7d4796fa9b12f Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 25 May 2023 03:43:48 +0200 Subject: [PATCH 060/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/es.po | 8 +- i18n/si.po | 1167 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1171 insertions(+), 4 deletions(-) create mode 100644 i18n/si.po diff --git a/i18n/es.po b/i18n/es.po index c71fcd0dd..010aaac50 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -8,15 +8,15 @@ # Gabriela Rodriguez , 2022 # Viktor Varland , 2022 # Marta Vila , 2023 -# Enzo Nicolas Rossi , 2023 # phil_dhis2, 2023 +# Enzo Nicolas Rossi , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2023\n" +"Last-Translator: Enzo Nicolas Rossi , 2023\n" "Language-Team: Spanish (https://app.transifex.com/hisp-uio/teams/100509/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -236,13 +236,13 @@ msgid "Remove" msgstr "Eliminar" msgid "Filter dimensions" -msgstr "" +msgstr "Dimensiones del filtro" msgid "Main dimensions" msgstr "" msgid "Your dimensions" -msgstr "" +msgstr "Dimensiones propias" msgid "Dimension recommended with selected data" msgstr "" diff --git a/i18n/si.po b/i18n/si.po new file mode 100644 index 000000000..8f7b8ab05 --- /dev/null +++ b/i18n/si.po @@ -0,0 +1,1167 @@ +# +# Translators: +# Malinda Wijeratne, 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: i18next-conv\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" +"PO-Revision-Date: 2020-04-28 22:05+0000\n" +"Last-Translator: Malinda Wijeratne, 2023\n" +"Language-Team: Sinhala (https://app.transifex.com/hisp-uio/teams/100509/si/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: si\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "view only" +msgstr "" + +msgid "view and edit" +msgstr "" + +msgid "all users ({{accessLevel}})" +msgstr "" + +msgid "{{userOrGroup}} ({{accessLevel}})" +msgstr "" + +msgid "Shared with {{commaSeparatedListOfUsersAndGroups}}" +msgstr "" + +msgid "Not shared with any users or groups" +msgstr "" + +msgid "No description" +msgstr "විස්තරයක් නැත" + +msgid "Last updated {{time}}" +msgstr "" + +msgid "Created {{time}} by {{author}}" +msgstr "" + +msgid "Created {{time}}" +msgstr "" + +msgid "Viewed {{count}} times" +msgid_plural "Viewed {{count}} times" +msgstr[0] "" +msgstr[1] "" + +msgid "Notifications" +msgstr "" + +msgid "You're subscribed and getting updates about new interpretations." +msgstr "" + +msgid "Unsubscribe" +msgstr "" + +msgid "Subscribe to get updates about new interpretations." +msgstr "" + +msgid "Subscribe" +msgstr "" + +msgid "About this map" +msgstr "" + +msgid "About this line list" +msgstr "" + +msgid "About this visualization" +msgstr "" + +msgid "This app could not retrieve required data." +msgstr "" + +msgid "Network error" +msgstr "" + +msgid "Data / Edit calculation" +msgstr "" + +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" +msgstr "" + +msgid "Totals only" +msgstr "" + +msgid "Details only" +msgstr "" + +msgid "Loading" +msgstr "" + +msgid "Data elements" +msgstr "" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + +msgid "Disaggregation" +msgstr "" + +msgid "No data" +msgstr "" + +msgid "Search by data item name" +msgstr "" + +msgid "No items selected" +msgstr "" + +msgid "Selected Items" +msgstr "" + +msgid "No indicators found" +msgstr "" + +msgid "No data sets found" +msgstr "" + +msgid "No event data items found" +msgstr "" + +msgid "No program indicators found" +msgstr "" + +msgid "No indicators found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data sets found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No event data items found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No program indicators found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "Nothing found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "Calculation" +msgstr "" + +msgid "Metric type" +msgstr "" + +msgid "All metrics" +msgstr "" + +msgid "Move to {{axisName}}" +msgstr "" + +msgid "Add to {{axisName}}" +msgstr "" + +msgid "Not available for {{visualizationType}}" +msgstr "" + +msgid "Remove Assigned Categories" +msgstr "" + +msgid "Add Assigned Categories" +msgstr "" + +msgid "Remove" +msgstr "ඉවත් කරන්" + +msgid "Filter dimensions" +msgstr "" + +msgid "Main dimensions" +msgstr "" + +msgid "Your dimensions" +msgstr "" + +msgid "Dimension recommended with selected data" +msgstr "" + +msgid "All items" +msgstr "" + +msgid "Automatically include all items" +msgstr "" + +msgid "" +"Select all {{- dimensionTitle}} items. With this option, new items added in " +"the future will be automatically included." +msgstr "" + +msgid "Manually select items..." +msgstr "" + +msgid "Nothing found in {{- dimensionTitle}}" +msgstr "" + +msgid "Search" +msgstr "සොයන්න" + +msgid "Nothing found for {{- searchTerm}}" +msgstr "" + +msgid "Delete {{fileType}}" +msgstr "" + +msgid "" +"This {{fileType}} and related interpretations will be deleted. Continue?" +msgstr "" + +msgid "Delete" +msgstr "ඉවත් කරන්න" + +msgid "File" +msgstr "" + +msgid "New" +msgstr "නව" + +msgid "Open…" +msgstr "" + +msgid "Save" +msgstr "" + +msgid "Save…" +msgstr "" + +msgid "Save as…" +msgstr "" + +msgid "Rename…" +msgstr "" + +msgid "Translate…" +msgstr "" + +msgid "Share…" +msgstr "" + +msgid "Get link…" +msgstr "" + +msgid "Open in this app" +msgstr "" + +msgid "Close" +msgstr "වසන්න" + +msgid "Rename {{fileType}}" +msgstr "" + +msgid "Name" +msgstr "" + +msgid "Description" +msgstr "විස්තරය" + +msgid "Rename" +msgstr "" + +msgid "{{- objectName}} (copy)" +msgstr "" + +msgid "Save {{fileType}} as" +msgstr "" + +msgid "event report" +msgstr "" + +msgid "line list" +msgstr "" + +msgid "map" +msgstr "" + +msgid "visualization" +msgstr "" + +msgid "Edit" +msgstr "වෙනස් කරන්න" + +msgid "Write a reply" +msgstr "" + +msgid "Post reply" +msgstr "" + +msgid "Could not update comment" +msgstr "" + +msgid "Enter comment text" +msgstr "" + +msgid "Update" +msgstr "" + +msgid "Viewing interpretation: {{- visualisationName}}" +msgstr "" + +msgid "Could not load interpretation" +msgstr "" + +msgid "" +"The interpretation couldn’t be displayed. Try again or contact your system " +"administrator." +msgstr "" + +msgid "Hide interpretation" +msgstr "" + +msgid "Write an interpretation" +msgstr "" + +msgid "Post interpretation" +msgstr "" + +msgid "Interpretations" +msgstr "" + +msgid "Unlike" +msgstr "" + +msgid "Like" +msgstr "" + +msgid "Reply" +msgstr "" + +msgid "Share" +msgstr "" + +msgid "See interpretation" +msgstr "" + +msgid "Manage sharing" +msgstr "" + +msgid "Could not update interpretation" +msgstr "" + +msgid "Enter interpretation text" +msgstr "" + +msgid "Bold text" +msgstr "" + +msgid "Italic text" +msgstr "" + +msgid "Link to a URL" +msgstr "" + +msgid "Mention a user" +msgstr "" + +msgid "Add emoji" +msgstr "" + +msgid "Preview" +msgstr "" + +msgid "Back to write mode" +msgstr "" + +msgid "Too many results. Try refining the search." +msgstr "" + +msgid "Search for a user" +msgstr "" + +msgid "Searching for \"{{- searchText}}\"" +msgstr "" + +msgid "No results found" +msgstr "" + +msgid "Not available offline" +msgstr "මාර්ග අපගතව නොතිබේ" + +msgid "Created by" +msgstr "" + +msgid "Anyone" +msgstr "ඕනෑම කෙනෙක්" + +msgid "Only you" +msgstr "" + +msgid "Others" +msgstr "" + +msgid "Not supported by this app yet" +msgstr "" + +msgid "Filter by name" +msgstr "" + +msgid "Created" +msgstr "" + +msgid "Last updated" +msgstr "" + +msgid "Type" +msgstr "" + +msgid "Clear filters" +msgstr "" + +msgid "{{firstItemIndex}}-{{lastItemIndex}} of {{totalNumberOfItems}}" +msgstr "" + +msgid "Open" +msgstr "" + +msgid "Couldn't load items" +msgstr "" + +msgid "" +"There was a problem loading items. Try again or contact your system " +"administrator." +msgstr "" + +msgid "No items found. Create a new to get started." +msgstr "" + +msgid "" +"No items found. Try adjusting your search or filter options to find what " +"you're looking for." +msgstr "" + +msgid "Create new" +msgstr "" + +msgid "Open a visualization" +msgstr "" + +msgid "Loading visualizations" +msgstr "" + +msgid "Couldn't load visualizations" +msgstr "" + +msgid "" +"There was a problem loading visualizations. Try again or contact your system" +" administrator." +msgstr "" + +msgid "No visualizations found. Click New visualization to get started." +msgstr "" + +msgid "" +"No visualizations found. Try adjusting your search or filter options to find" +" what you're looking for." +msgstr "" + +msgid "New visualization" +msgstr "" + +msgid "Open a map" +msgstr "" + +msgid "Loading maps" +msgstr "" + +msgid "Couldn't load maps" +msgstr "" + +msgid "" +"There was a problem loading maps. Try again or contact your system " +"administrator." +msgstr "" + +msgid "No maps found. Click New map to get started." +msgstr "" + +msgid "" +"No maps found. Try adjusting your search or filter options to find what " +"you're looking for." +msgstr "" + +msgid "New map" +msgstr "" + +msgid "Open a line list" +msgstr "" + +msgid "Loading line lists" +msgstr "" + +msgid "Couldn't load line lists" +msgstr "" + +msgid "" +"There was a problem loading line lists. Try again or contact your system " +"administrator." +msgstr "" + +msgid "No line lists found. Click New line list to get started." +msgstr "" + +msgid "" +"No line lists found. Try adjusting your search or filter options to find " +"what you're looking for." +msgstr "" + +msgid "New line list" +msgstr "" + +msgid "Options" +msgstr "" + +msgid "Hide" +msgstr "" + +msgid "{{count}} org units" +msgid_plural "{{count}} org units" +msgstr[0] "" +msgstr[1] "" + +msgid "{{count}} levels" +msgid_plural "{{count}} levels" +msgstr[0] "" +msgstr[1] "" + +msgid "{{count}} groups" +msgid_plural "{{count}} groups" +msgstr[0] "" +msgstr[1] "" + +msgid "Selected: {{commaSeparatedListOfOrganisationUnits}}" +msgstr "" + +msgid "Nothing selected" +msgstr "" + +msgid "User organisation unit" +msgstr "" + +msgid "User sub-units" +msgstr "" + +msgid "User sub-x2-units" +msgstr "" + +msgid "Select a level" +msgstr "" + +msgid "Select a group" +msgstr "" + +msgid "Deselect all" +msgstr "" + +msgid "Period type" +msgstr "" + +msgid "Year" +msgstr "" + +msgid "Select year" +msgstr "" + +msgid "Period" +msgstr "" + +msgid "Relative periods" +msgstr "" + +msgid "Fixed periods" +msgstr "" + +msgid "Selected Periods" +msgstr "" + +msgid "No periods selected" +msgstr "" + +msgid "Daily" +msgstr "දිනපතා" + +msgid "Weekly" +msgstr "සතිපතා" + +msgid "Weekly (Start Wednesday)" +msgstr "" + +msgid "Weekly (Start Thursday)" +msgstr "" + +msgid "Weekly (Start Saturday)" +msgstr "" + +msgid "Weekly (Start Sunday)" +msgstr "" + +msgid "Bi-weekly" +msgstr "" + +msgid "Monthly" +msgstr "මාස්පතා" + +msgid "Bi-monthly" +msgstr "" + +msgid "Quarterly" +msgstr "" + +msgid "Six-monthly" +msgstr "" + +msgid "Six-monthly April" +msgstr "" + +msgid "Yearly" +msgstr "අවුරුදු පතා" + +msgid "Financial year (Start November)" +msgstr "" + +msgid "Financial year (Start October)" +msgstr "" + +msgid "Financial year (Start July)" +msgstr "" + +msgid "Financial year (Start April)" +msgstr "" + +msgid "Today" +msgstr "අද" + +msgid "Yesterday" +msgstr "ඊයේ" + +msgid "Last 3 days" +msgstr "" + +msgid "Last 7 days" +msgstr "" + +msgid "Last 14 days" +msgstr "" + +msgid "Last 30 days" +msgstr "" + +msgid "Last 60 days" +msgstr "" + +msgid "Last 90 days" +msgstr "" + +msgid "Last 180 days" +msgstr "" + +msgid "This week" +msgstr "" + +msgid "Last week" +msgstr "" + +msgid "Last 4 weeks" +msgstr "" + +msgid "Last 12 weeks" +msgstr "" + +msgid "Last 52 weeks" +msgstr "" + +msgid "Weeks this year" +msgstr "" + +msgid "This bi-week" +msgstr "" + +msgid "Last bi-week" +msgstr "" + +msgid "Last 4 bi-weeks" +msgstr "" + +msgid "This month" +msgstr "" + +msgid "Last month" +msgstr "" + +msgid "Last 3 months" +msgstr "" + +msgid "Last 6 months" +msgstr "" + +msgid "Last 12 months" +msgstr "" + +msgid "Months this year" +msgstr "" + +msgid "This bi-month" +msgstr "" + +msgid "Last bi-month" +msgstr "" + +msgid "Last 6 bi-months" +msgstr "" + +msgid "Bi-months this year" +msgstr "" + +msgid "This quarter" +msgstr "" + +msgid "Last quarter" +msgstr "" + +msgid "Last 4 quarters" +msgstr "" + +msgid "Quarters this year" +msgstr "" + +msgid "This six-month" +msgstr "" + +msgid "Last six-month" +msgstr "" + +msgid "Last 2 six-month" +msgstr "" + +msgid "This financial year" +msgstr "" + +msgid "Last financial year" +msgstr "" + +msgid "Last 5 financial years" +msgstr "" + +msgid "This year" +msgstr "" + +msgid "Last year" +msgstr "" + +msgid "Last 5 years" +msgstr "" + +msgid "Last 10 years" +msgstr "" + +msgid "Days" +msgstr "" + +msgid "Weeks" +msgstr "" + +msgid "Bi-weeks" +msgstr "" + +msgid "Months" +msgstr "" + +msgid "Bi-months" +msgstr "" + +msgid "Quarters" +msgstr "" + +msgid "Six-months" +msgstr "" + +msgid "Financial Years" +msgstr "" + +msgid "Years" +msgstr "" + +msgid "Translating to" +msgstr "" + +msgid "Choose a locale" +msgstr "" + +msgid "Base locale reference" +msgstr "" + +msgid "Choose a locale to translate from the menu above" +msgstr "" + +msgid "Translate: {{objectName}}" +msgstr "" + +msgid "Save translations" +msgstr "" + +msgid "Cannot save while offline" +msgstr "" + +msgid "Could not load translations" +msgstr "" + +msgid "Retry" +msgstr "" + +msgid "Series" +msgstr "" + +msgid "Category" +msgstr "" + +msgid "Filter" +msgstr "" + +msgid "Columns" +msgstr "" + +msgid "Rows" +msgstr "" + +msgid "Points" +msgstr "" + +msgid "Reporting rate" +msgstr "" + +msgid "Reporting rate on time" +msgstr "" + +msgid "Actual reports" +msgstr "" + +msgid "Actual reports on time" +msgstr "" + +msgid "Expected reports" +msgstr "" + +msgid "Program" +msgstr "" + +msgid "Select a program" +msgstr "" + +msgid "Indicators" +msgstr "" + +msgid "Indicator group" +msgstr "" + +msgid "All groups" +msgstr "" + +msgid "Indicator" +msgstr "" + +msgid "No indicator groups found" +msgstr "" + +msgid "Loading indicator groups" +msgstr "" + +msgid "Data element group" +msgstr "" + +msgid "Data element" +msgstr "" + +msgid "No data element groups found" +msgstr "" + +msgid "Loading data element groups" +msgstr "" + +msgid "Data sets" +msgstr "" + +msgid "Data set" +msgstr "" + +msgid "All data sets" +msgstr "" + +msgid "Loading data sets" +msgstr "" + +msgid "Event data items" +msgstr "" + +msgid "All programs" +msgstr "" + +msgid "Event data item" +msgstr "" + +msgid "No programs found" +msgstr "" + +msgid "Loading programs" +msgstr "" + +msgid "Program indicators" +msgstr "" + +msgid "Program indicator" +msgstr "" + +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + +msgid "Extra Small" +msgstr "" + +msgid "Small" +msgstr "" + +msgid "Regular" +msgstr "" + +msgid "Large" +msgstr "" + +msgid "Extra Large" +msgstr "" + +msgid "Left" +msgstr "" + +msgid "Center" +msgstr "" + +msgid "Right" +msgstr "" + +msgid "Start" +msgstr "" + +msgid "Middle" +msgstr "" + +msgid "End" +msgstr "" + +msgid "Top" +msgstr "" + +msgid "Bottom" +msgstr "" + +msgid "{{dynamicOuNames}} and {{lastOuName}}" +msgstr "" + +msgid "{{allDynamicOuNames}} levels" +msgstr "" + +msgid "{{allDynamicOuNames}} groups" +msgstr "" + +msgid "{{allDynamicOuNames}} levels in {{staticOuNames}}" +msgstr "" + +msgid "{{allDynamicOuNames}} groups in {{staticOuNames}}" +msgstr "" + +msgid "{{percentage}}% of total x values" +msgstr "" + +msgid "{{percentage}}% of total y values" +msgstr "" + +msgid "{{thresholdFactor}} × IQR Q1" +msgstr "" + +msgid "{{thresholdFactor}} × IQR Q3" +msgstr "" + +msgid "{{thresholdFactor}} × Modified Z-score low" +msgstr "" + +msgid "{{thresholdFactor}} × Modified Z-score high" +msgstr "" + +msgid "{{thresholdFactor}} × Z-score low" +msgstr "" + +msgid "{{thresholdFactor}} × Z-score high" +msgstr "" + +msgid "Data" +msgstr "" + +msgid "Organisation unit" +msgstr "" + +msgid "Assigned Categories" +msgstr "" + +msgid "Pivot table" +msgstr "" + +msgid "Area" +msgstr "" + +msgid "Stacked area" +msgstr "" + +msgid "Bar" +msgstr "" + +msgid "Stacked bar" +msgstr "" + +msgid "Column" +msgstr "" + +msgid "Year over year (column)" +msgstr "" + +msgid "Stacked column" +msgstr "" + +msgid "Gauge" +msgstr "" + +msgid "Line" +msgstr "" + +msgid "Line list" +msgstr "" + +msgid "Year over year (line)" +msgstr "" + +msgid "Pie" +msgstr "" + +msgid "Radar" +msgstr "" + +msgid "Scatter" +msgstr "" + +msgid "Single value" +msgstr "" + +msgid "All charts" +msgstr "" + +msgid "{{seriesName}} (trend)" +msgstr "" + +msgid "Trend" +msgstr "" + +msgid "No legend for this series" +msgstr "" + +msgid "and {{amount}} more..." +msgstr "" + +msgid "Linear Regression" +msgstr "" + +msgid "Target" +msgstr "" + +msgid "Base" +msgstr "" + +msgid "Axis {{axisId}}" +msgstr "" + +msgid "{{count}} items" +msgid_plural "{{count}} items" +msgstr[0] "" +msgstr[1] "" + +msgid "Reset zoom" +msgstr "" From 80d808300c0b4a9bd48f6458c7dbd9ef1155afb8 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 25 May 2023 02:13:24 +0000 Subject: [PATCH 061/285] chore(release): cut 25.1.5 [skip ci] ## [25.1.5](https://github.com/dhis2/analytics/compare/v25.1.4...v25.1.5) (2023-05-25) ### Bug Fixes * **translations:** sync translations from transifex (master) ([9c3165e](https://github.com/dhis2/analytics/commit/9c3165ee58559c6c3601b0e12ee7d4796fa9b12f)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1c0288eb..7a15c6de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.5](https://github.com/dhis2/analytics/compare/v25.1.4...v25.1.5) (2023-05-25) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([9c3165e](https://github.com/dhis2/analytics/commit/9c3165ee58559c6c3601b0e12ee7d4796fa9b12f)) + ## [25.1.4](https://github.com/dhis2/analytics/compare/v25.1.3...v25.1.4) (2023-05-24) diff --git a/package.json b/package.json index fee3ac556..ea25e28d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.4", + "version": "25.1.5", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From c9010067004c5c3061cfa76806ffd991394bda98 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 26 May 2023 03:43:59 +0200 Subject: [PATCH 062/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/lo.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/lo.po b/i18n/lo.po index ec754e79f..31f689fc5 100644 --- a/i18n/lo.po +++ b/i18n/lo.po @@ -655,7 +655,7 @@ msgid "Weekly (Start Sunday)" msgstr "" msgid "Bi-weekly" -msgstr "" +msgstr "ສອງອາທິດຕໍ່ຄັ້ງ" msgid "Monthly" msgstr "ລາຍເດືອນ" @@ -736,10 +736,10 @@ msgid "This bi-week" msgstr "ສອງເທື່ອຕໍ່ອາທິດນີ້" msgid "Last bi-week" -msgstr "" +msgstr "ສອງຄັ້ງລ່າສຸດຕໍ່ທິດ" msgid "Last 4 bi-weeks" -msgstr "" +msgstr "ສອງເດືອນທີ່ຜ່ານມາ" msgid "This month" msgstr "ເດືອນນີ້" From 29eeb4022942fd3363cb659c87d14da15128e64e Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 26 May 2023 01:48:57 +0000 Subject: [PATCH 063/285] chore(release): cut 25.1.6 [skip ci] ## [25.1.6](https://github.com/dhis2/analytics/compare/v25.1.5...v25.1.6) (2023-05-26) ### Bug Fixes * **translations:** sync translations from transifex (master) ([c901006](https://github.com/dhis2/analytics/commit/c9010067004c5c3061cfa76806ffd991394bda98)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a15c6de4..b1f314c32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.6](https://github.com/dhis2/analytics/compare/v25.1.5...v25.1.6) (2023-05-26) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([c901006](https://github.com/dhis2/analytics/commit/c9010067004c5c3061cfa76806ffd991394bda98)) + ## [25.1.5](https://github.com/dhis2/analytics/compare/v25.1.4...v25.1.5) (2023-05-25) diff --git a/package.json b/package.json index ea25e28d3..b33f91e26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.5", + "version": "25.1.6", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 83d2c2bda1160b7c474d9d29596cfccdbeb72c81 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sat, 27 May 2023 03:43:01 +0200 Subject: [PATCH 064/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/si.po | 70 +++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/i18n/si.po b/i18n/si.po index 8f7b8ab05..c17008510 100644 --- a/i18n/si.po +++ b/i18n/si.po @@ -16,10 +16,10 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" msgid "view only" -msgstr "" +msgstr "නැරඹීම පමණයි" msgid "view and edit" -msgstr "" +msgstr "නැරඹීම සහ සංස්කරණය" msgid "all users ({{accessLevel}})" msgstr "" @@ -119,7 +119,7 @@ msgid "" msgstr "" msgid "Yes, delete" -msgstr "" +msgstr "ඔව්, මකන්න" msgid "Totals only" msgstr "" @@ -280,7 +280,7 @@ msgid "Open…" msgstr "" msgid "Save" -msgstr "" +msgstr "සුරකින්න" msgid "Save…" msgstr "" @@ -310,7 +310,7 @@ msgid "Rename {{fileType}}" msgstr "" msgid "Name" -msgstr "" +msgstr "නම" msgid "Description" msgstr "විස්තරය" @@ -471,7 +471,7 @@ msgid "{{firstItemIndex}}-{{lastItemIndex}} of {{totalNumberOfItems}}" msgstr "" msgid "Open" -msgstr "" +msgstr "විවෘත කරන්න" msgid "Couldn't load items" msgstr "" @@ -616,10 +616,10 @@ msgid "Period type" msgstr "" msgid "Year" -msgstr "" +msgstr "වසර" msgid "Select year" -msgstr "" +msgstr "වසර තෝරන්න" msgid "Period" msgstr "" @@ -715,19 +715,19 @@ msgid "Last 180 days" msgstr "" msgid "This week" -msgstr "" +msgstr "මෙම සතිය" msgid "Last week" -msgstr "" +msgstr "පසුගිය සතිය" msgid "Last 4 weeks" -msgstr "" +msgstr "පසුගිය සති 4" msgid "Last 12 weeks" -msgstr "" +msgstr "පසුගිය සති 12" msgid "Last 52 weeks" -msgstr "" +msgstr "පසුගිය සති 52" msgid "Weeks this year" msgstr "" @@ -742,19 +742,19 @@ msgid "Last 4 bi-weeks" msgstr "" msgid "This month" -msgstr "" +msgstr "මෙම මාසය" msgid "Last month" -msgstr "" +msgstr "පසුගිය මාසය" msgid "Last 3 months" -msgstr "" +msgstr "පසුගිය මාස 3" msgid "Last 6 months" -msgstr "" +msgstr "පසුගිය මාස 6" msgid "Last 12 months" -msgstr "" +msgstr "පසුගිය මාස 12" msgid "Months this year" msgstr "" @@ -802,34 +802,34 @@ msgid "Last 5 financial years" msgstr "" msgid "This year" -msgstr "" +msgstr "මෙම වසර" msgid "Last year" -msgstr "" +msgstr "පසුගිය වසර" msgid "Last 5 years" -msgstr "" +msgstr "පසුගිය 5 වසර" msgid "Last 10 years" -msgstr "" +msgstr "පසුගිය 10 වසර" msgid "Days" -msgstr "" +msgstr "දින" msgid "Weeks" -msgstr "" +msgstr "සති" msgid "Bi-weeks" msgstr "" msgid "Months" -msgstr "" +msgstr "මාස" msgid "Bi-months" msgstr "" msgid "Quarters" -msgstr "" +msgstr "කාර්තු" msgid "Six-months" msgstr "" @@ -838,7 +838,7 @@ msgid "Financial Years" msgstr "" msgid "Years" -msgstr "" +msgstr "අවුරුදු" msgid "Translating to" msgstr "" @@ -865,13 +865,13 @@ msgid "Could not load translations" msgstr "" msgid "Retry" -msgstr "" +msgstr "නැවත උත්සාහ කරන්න" msgid "Series" msgstr "" msgid "Category" -msgstr "" +msgstr "කාණ්ඩය" msgid "Filter" msgstr "" @@ -997,16 +997,16 @@ msgid "Missing left parenthesis (" msgstr "" msgid "Extra Small" -msgstr "" +msgstr "ඉතා කුඩා" msgid "Small" -msgstr "" +msgstr "කුඩා" msgid "Regular" -msgstr "" +msgstr "මධ්‍යස්ථ" msgid "Large" -msgstr "" +msgstr "ලොකු" msgid "Extra Large" msgstr "" @@ -1075,7 +1075,7 @@ msgid "{{thresholdFactor}} × Z-score high" msgstr "" msgid "Data" -msgstr "" +msgstr "දත්ත" msgid "Organisation unit" msgstr "" @@ -1150,7 +1150,7 @@ msgid "Linear Regression" msgstr "" msgid "Target" -msgstr "" +msgstr "ඉලක්කය" msgid "Base" msgstr "" From cce68bee178b239a95bd670eae3dad5e57d260fe Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sat, 27 May 2023 01:47:40 +0000 Subject: [PATCH 065/285] chore(release): cut 25.1.7 [skip ci] ## [25.1.7](https://github.com/dhis2/analytics/compare/v25.1.6...v25.1.7) (2023-05-27) ### Bug Fixes * **translations:** sync translations from transifex (master) ([83d2c2b](https://github.com/dhis2/analytics/commit/83d2c2bda1160b7c474d9d29596cfccdbeb72c81)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f314c32..a619f8dac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.7](https://github.com/dhis2/analytics/compare/v25.1.6...v25.1.7) (2023-05-27) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([83d2c2b](https://github.com/dhis2/analytics/commit/83d2c2bda1160b7c474d9d29596cfccdbeb72c81)) + ## [25.1.6](https://github.com/dhis2/analytics/compare/v25.1.5...v25.1.6) (2023-05-26) diff --git a/package.json b/package.json index b33f91e26..6e4db3a99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.6", + "version": "25.1.7", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 184740d3bdc019e8ab9eb8b05f4f473fcf960889 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 28 May 2023 03:49:35 +0200 Subject: [PATCH 066/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/si.po | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/i18n/si.po b/i18n/si.po index c17008510..ef5f5e2c3 100644 --- a/i18n/si.po +++ b/i18n/si.po @@ -223,7 +223,7 @@ msgid "Add Assigned Categories" msgstr "" msgid "Remove" -msgstr "ඉවත් කරන්" +msgstr "ඉවත් කරන්න" msgid "Filter dimensions" msgstr "" @@ -271,7 +271,7 @@ msgid "Delete" msgstr "ඉවත් කරන්න" msgid "File" -msgstr "" +msgstr "ගොනුව" msgid "New" msgstr "නව" @@ -643,34 +643,34 @@ msgid "Weekly" msgstr "සතිපතා" msgid "Weekly (Start Wednesday)" -msgstr "" +msgstr "සතියකට වරක් (Start Wednesday)" msgid "Weekly (Start Thursday)" -msgstr "" +msgstr "සතියකට වරක් (Start Thursday)" msgid "Weekly (Start Saturday)" -msgstr "" +msgstr "සතියකට වරක් (Start Saturday)" msgid "Weekly (Start Sunday)" -msgstr "" +msgstr "සතියකට වරක් (Start Sunday)" msgid "Bi-weekly" -msgstr "" +msgstr "දෙසතියකට වරක්" msgid "Monthly" msgstr "මාස්පතා" msgid "Bi-monthly" -msgstr "" +msgstr "දෙමසකට වරක්" msgid "Quarterly" -msgstr "" +msgstr "කාර්තු පතා" msgid "Six-monthly" -msgstr "" +msgstr "6 මසකට වරක්" msgid "Six-monthly April" -msgstr "" +msgstr "6 මසකට වරක් අප්‍රේල්" msgid "Yearly" msgstr "අවුරුදු පතා" @@ -694,13 +694,13 @@ msgid "Yesterday" msgstr "ඊයේ" msgid "Last 3 days" -msgstr "" +msgstr "පසුගිය දින 3" msgid "Last 7 days" -msgstr "" +msgstr "පසුගිය දින 7" msgid "Last 14 days" -msgstr "" +msgstr "පසුගිය දින 14" msgid "Last 30 days" msgstr "" From a067a7c9ddebf704d3c4d4df289e2c82db0e5d06 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 28 May 2023 01:54:26 +0000 Subject: [PATCH 067/285] chore(release): cut 25.1.8 [skip ci] ## [25.1.8](https://github.com/dhis2/analytics/compare/v25.1.7...v25.1.8) (2023-05-28) ### Bug Fixes * **translations:** sync translations from transifex (master) ([184740d](https://github.com/dhis2/analytics/commit/184740d3bdc019e8ab9eb8b05f4f473fcf960889)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a619f8dac..6b393a27c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.8](https://github.com/dhis2/analytics/compare/v25.1.7...v25.1.8) (2023-05-28) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([184740d](https://github.com/dhis2/analytics/commit/184740d3bdc019e8ab9eb8b05f4f473fcf960889)) + ## [25.1.7](https://github.com/dhis2/analytics/compare/v25.1.6...v25.1.7) (2023-05-27) diff --git a/package.json b/package.json index 6e4db3a99..bb1fd48f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.7", + "version": "25.1.8", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 9466322c7cac471ee1d5bd1e451f911968e30a3d Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 30 May 2023 03:47:00 +0200 Subject: [PATCH 068/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/es.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/es.po b/i18n/es.po index 010aaac50..76cf602fa 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -88,7 +88,7 @@ msgid "This app could not retrieve required data." msgstr "" msgid "Network error" -msgstr "" +msgstr "Error de red" msgid "Data / Edit calculation" msgstr "" @@ -129,7 +129,7 @@ msgid "" msgstr "" msgid "Yes, delete" -msgstr "" +msgstr "si, eliminar" msgid "Totals only" msgstr "" From fef4c5d939abde8983f1b5740adf0b3d3f94bda3 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 30 May 2023 01:52:22 +0000 Subject: [PATCH 069/285] chore(release): cut 25.1.9 [skip ci] ## [25.1.9](https://github.com/dhis2/analytics/compare/v25.1.8...v25.1.9) (2023-05-30) ### Bug Fixes * **translations:** sync translations from transifex (master) ([9466322](https://github.com/dhis2/analytics/commit/9466322c7cac471ee1d5bd1e451f911968e30a3d)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b393a27c..1390425ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.9](https://github.com/dhis2/analytics/compare/v25.1.8...v25.1.9) (2023-05-30) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([9466322](https://github.com/dhis2/analytics/commit/9466322c7cac471ee1d5bd1e451f911968e30a3d)) + ## [25.1.8](https://github.com/dhis2/analytics/compare/v25.1.7...v25.1.8) (2023-05-28) diff --git a/package.json b/package.json index bb1fd48f6..c5075f231 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.8", + "version": "25.1.9", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From d94fe02c7cc85a6b4aca41c85ba60ed37871b645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Henrik=20=C3=98verland?= Date: Tue, 30 May 2023 13:07:51 +0200 Subject: [PATCH 070/285] fix: single value size and position issues (DHIS2-15344) (#1470) * fix: scaling fixes WIP * fix: avoid collision with title/subtitle WIP * fix: adjust title position WIP * fix: icon position WIP * fix: icon position WIP * fix: icon position WIP * fix: width threshold WIP * fix: use constants * fix: icon padding * fix: adjust const values * chore: remove logs * chore: comments and cleanup * fix: adjust width const --- .../config/generators/dhis/singleValue.js | 285 +++++++++++------- 1 file changed, 179 insertions(+), 106 deletions(-) diff --git a/src/visualizations/config/generators/dhis/singleValue.js b/src/visualizations/config/generators/dhis/singleValue.js index bbe3e9e60..199765800 100644 --- a/src/visualizations/config/generators/dhis/singleValue.js +++ b/src/visualizations/config/generators/dhis/singleValue.js @@ -20,13 +20,83 @@ import { const svgNS = 'http://www.w3.org/2000/svg' +// multiply text width with this factor +// to get very close to actual text width +// nb: dependent on viewbox etc +const ACTUAL_TEXT_WIDTH_FACTOR = 0.9 + +// multiply value text size with this factor +// to get very close to the actual number height +// as numbers don't go below the baseline like e.g. "j" and "g" +const ACTUAL_NUMBER_HEIGHT_FACTOR = 0.67 + +// do not allow text width to exceed this threshold +// a threshold >1 does not really make sense but text width vs viewbox is complicated +const TEXT_WIDTH_CONTAINER_WIDTH_FACTOR = 1.3 + +// do not allow text size to exceed this +const TEXT_SIZE_CONTAINER_HEIGHT_FACTOR = 0.6 +const TEXT_SIZE_MAX_THRESHOLD = 400 + +// multiply text size with this factor +// to get an appropriate letter spacing +const LETTER_SPACING_TEXT_SIZE_FACTOR = (1 / 35) * -1 +const LETTER_SPACING_MIN_THRESHOLD = -6 +const LETTER_SPACING_MAX_THRESHOLD = -1 + +// fixed top margin above title/subtitle +const TOP_MARGIN_FIXED = 16 + +// multiply text size with this factor +// to get an appropriate sub text size +const SUB_TEXT_SIZE_FACTOR = 0.5 +const SUB_TEXT_SIZE_MIN_THRESHOLD = 26 +const SUB_TEXT_SIZE_MAX_THRESHOLD = 40 + +// multiply text size with this factor +// to get an appropriate icon padding +const ICON_PADDING_FACTOR = 0.3 + // Compute text width before rendering // Not exactly precise but close enough const getTextWidth = (text, font) => { const canvas = document.createElement('canvas') const context = canvas.getContext('2d') context.font = font - return context.measureText(text).width + return Math.round( + context.measureText(text).width * ACTUAL_TEXT_WIDTH_FACTOR + ) +} + +const getTextHeightForNumbers = (textSize) => + textSize * ACTUAL_NUMBER_HEIGHT_FACTOR + +const getIconPadding = (textSize) => Math.round(textSize * ICON_PADDING_FACTOR) + +const getTextSize = ( + formattedValue, + containerWidth, + containerHeight, + showIcon +) => { + let size = Math.min( + Math.round(containerHeight * TEXT_SIZE_CONTAINER_HEIGHT_FACTOR), + TEXT_SIZE_MAX_THRESHOLD + ) + + const widthThreshold = Math.round( + containerWidth * TEXT_WIDTH_CONTAINER_WIDTH_FACTOR + ) + + const textWidth = + getTextWidth(formattedValue, `${size}px Roboto`) + + (showIcon ? getIconPadding(size) : 0) + + if (textWidth > widthThreshold) { + size = Math.round(size * (widthThreshold / textWidth)) + } + + return size } const generateValueSVG = ({ @@ -38,28 +108,32 @@ const generateValueSVG = ({ noData, containerWidth, containerHeight, + topMargin = 0, }) => { - const ratio = containerHeight / containerWidth - const iconSize = 300 - const iconPadding = 50 - const textSize = iconSize * 0.85 - const textWidth = getTextWidth(formattedValue, `${textSize}px Roboto`) - const subTextSize = 40 - const showIcon = icon && formattedValue !== noData.text - let viewBoxWidth = textWidth + const textSize = getTextSize( + formattedValue, + containerWidth, + containerHeight, + showIcon + ) - if (showIcon) { - viewBoxWidth += iconSize + iconPadding - } + const textWidth = getTextWidth(formattedValue, `${textSize}px Roboto`) + + const iconSize = textSize - const viewBoxHeight = viewBoxWidth * ratio + const subTextSize = + textSize * SUB_TEXT_SIZE_FACTOR > SUB_TEXT_SIZE_MAX_THRESHOLD + ? SUB_TEXT_SIZE_MAX_THRESHOLD + : textSize * SUB_TEXT_SIZE_FACTOR < SUB_TEXT_SIZE_MIN_THRESHOLD + ? SUB_TEXT_SIZE_MIN_THRESHOLD + : textSize * SUB_TEXT_SIZE_FACTOR const svgValue = document.createElementNS(svgNS, 'svg') - svgValue.setAttribute('viewBox', `0 0 ${viewBoxWidth} ${viewBoxHeight}`) - svgValue.setAttribute('width', '95%') - svgValue.setAttribute('height', '95%') + svgValue.setAttribute('viewBox', `0 0 ${containerWidth} ${containerHeight}`) + svgValue.setAttribute('width', '50%') + svgValue.setAttribute('height', '50%') svgValue.setAttribute('x', '50%') svgValue.setAttribute('y', '50%') svgValue.setAttribute('style', 'overflow: visible') @@ -77,13 +151,13 @@ const generateValueSVG = ({ // embed icon to allow changing color // (elements with fill need to use "currentColor" for this to work) const iconSvgNode = document.createElementNS(svgNS, 'svg') + iconSvgNode.setAttribute('viewBox', '0 0 48 48') iconSvgNode.setAttribute('width', iconSize) iconSvgNode.setAttribute('height', iconSize) - iconSvgNode.setAttribute('viewBox', '0 0 48 48') - iconSvgNode.setAttribute('y', `-${iconSize / 2}`) + iconSvgNode.setAttribute('y', (iconSize / 2 - topMargin / 2) * -1) iconSvgNode.setAttribute( 'x', - `-${(iconSize + iconPadding + textWidth) / 2}` + `-${(iconSize + getIconPadding(textSize) + textWidth) / 2}` ) iconSvgNode.setAttribute('style', `color: ${fillColor}`) @@ -97,14 +171,28 @@ const generateValueSVG = ({ svgValue.appendChild(iconSvgNode) } + const letterSpacing = Math.round(textSize * LETTER_SPACING_TEXT_SIZE_FACTOR) + const textNode = document.createElementNS(svgNS, 'text') textNode.setAttribute('font-size', textSize) textNode.setAttribute('font-weight', '300') - textNode.setAttribute('letter-spacing', '-5') + textNode.setAttribute( + 'letter-spacing', + letterSpacing < LETTER_SPACING_MIN_THRESHOLD + ? LETTER_SPACING_MIN_THRESHOLD + : letterSpacing > LETTER_SPACING_MAX_THRESHOLD + ? LETTER_SPACING_MAX_THRESHOLD + : letterSpacing + ) textNode.setAttribute('text-anchor', 'middle') - textNode.setAttribute('x', showIcon ? `${(iconSize + iconPadding) / 2}` : 0) - // vertical align, "alignment-baseline: central" is not supported by Batik - textNode.setAttribute('y', '.35em') + textNode.setAttribute( + 'x', + showIcon ? `${(iconSize + getIconPadding(textSize)) / 2}` : 0 + ) + textNode.setAttribute( + 'y', + topMargin / 2 + getTextHeightForNumbers(textSize) / 2 + ) textNode.setAttribute('fill', fillColor) textNode.setAttribute('data-test', 'visualization-primary-value') @@ -116,8 +204,8 @@ const generateValueSVG = ({ const subTextNode = document.createElementNS(svgNS, 'text') subTextNode.setAttribute('text-anchor', 'middle') subTextNode.setAttribute('font-size', subTextSize) - subTextNode.setAttribute('y', iconSize / 2) - subTextNode.setAttribute('dy', subTextSize) + subTextNode.setAttribute('y', iconSize / 2 + topMargin / 2) + subTextNode.setAttribute('dy', subTextSize * 1.7) subTextNode.setAttribute('fill', textColor) subTextNode.appendChild(document.createTextNode(subText)) @@ -240,115 +328,91 @@ const generateDVItem = ( const svgWrapper = document.createElementNS(svgNS, 'svg') + // title const title = document.createElementNS(svgNS, 'text') + const titleFontStyle = mergeFontStyleWithDefault( fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_TITLE], FONT_STYLE_VISUALIZATION_TITLE ) - const titleYPosition = titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE] - title.setAttribute( - 'x', - getXFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]) - ) - title.setAttribute('y', titleYPosition) - title.setAttribute( - 'text-anchor', - getTextAnchorFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]) - ) - title.setAttribute( - 'font-size', - `${titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE]}px` - ) - title.setAttribute( - 'font-weight', - titleFontStyle[FONT_STYLE_OPTION_BOLD] + const titleYPosition = + TOP_MARGIN_FIXED + + parseInt(titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE]) + + 'px' + + const titleAttributes = { + x: getXFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]), + y: titleYPosition, + 'text-anchor': getTextAnchorFromTextAlign( + titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN] + ), + 'font-size': `${titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE]}px`, + 'font-weight': titleFontStyle[FONT_STYLE_OPTION_BOLD] ? FONT_STYLE_OPTION_BOLD - : 'normal' - ) - title.setAttribute( - 'font-style', - titleFontStyle[FONT_STYLE_OPTION_ITALIC] + : 'normal', + 'font-style': titleFontStyle[FONT_STYLE_OPTION_ITALIC] ? FONT_STYLE_OPTION_ITALIC - : 'normal' - ) - if ( - titleColor && - titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === - defaultFontStyle[FONT_STYLE_VISUALIZATION_TITLE][ - FONT_STYLE_OPTION_TEXT_COLOR - ] - ) { - title.setAttribute('fill', titleColor) - } else { - title.setAttribute('fill', titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR]) + : 'normal', + 'data-test': 'visualization-title', + fill: + titleColor && + titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === + defaultFontStyle[FONT_STYLE_VISUALIZATION_TITLE][ + FONT_STYLE_OPTION_TEXT_COLOR + ] + ? titleColor + : titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR], } - title.setAttribute('data-test', 'visualization-title') + Object.entries(titleAttributes).forEach(([key, value]) => + title.setAttribute(key, value) + ) if (config.title) { title.appendChild(document.createTextNode(config.title)) - svgWrapper.appendChild(title) } + // subtitle + const subtitle = document.createElementNS(svgNS, 'text') + const subtitleFontStyle = mergeFontStyleWithDefault( fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE], FONT_STYLE_VISUALIZATION_SUBTITLE ) - const subtitle = document.createElementNS(svgNS, 'text') - subtitle.setAttribute( - 'x', - getXFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]) - ) - subtitle.setAttribute('y', titleYPosition) - subtitle.setAttribute( - 'dy', - `${subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE] + 10}` - ) - subtitle.setAttribute( - 'text-anchor', - getTextAnchorFromTextAlign( + + const subtitleAttributes = { + x: getXFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]), + y: titleYPosition, + dy: `${subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE] + 10}`, + 'text-anchor': getTextAnchorFromTextAlign( subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN] - ) - ) - subtitle.setAttribute( - 'font-size', - `${subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE]}px` - ) - subtitle.setAttribute( - 'font-weight', - subtitleFontStyle[FONT_STYLE_OPTION_BOLD] + ), + 'font-size': `${subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE]}px`, + 'font-weight': subtitleFontStyle[FONT_STYLE_OPTION_BOLD] ? FONT_STYLE_OPTION_BOLD - : 'normal' - ) - subtitle.setAttribute( - 'font-style', - subtitleFontStyle[FONT_STYLE_OPTION_ITALIC] + : 'normal', + 'font-style': subtitleFontStyle[FONT_STYLE_OPTION_ITALIC] ? FONT_STYLE_OPTION_ITALIC - : 'normal' - ) - - if ( - titleColor && - subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === - defaultFontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE][ - FONT_STYLE_OPTION_TEXT_COLOR - ] - ) { - subtitle.setAttribute('fill', titleColor) - } else { - subtitle.setAttribute( - 'fill', - subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] - ) + : 'normal', + fill: + titleColor && + subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === + defaultFontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE][ + FONT_STYLE_OPTION_TEXT_COLOR + ] + ? titleColor + : subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR], + 'data-test': 'visualization-subtitle', } - subtitle.setAttribute('data-test', 'visualization-subtitle') + Object.entries(subtitleAttributes).forEach(([key, value]) => + subtitle.setAttribute(key, value) + ) if (config.subtitle) { subtitle.appendChild(document.createTextNode(config.subtitle)) - svgWrapper.appendChild(subtitle) } @@ -364,6 +428,15 @@ const generateDVItem = ( icon, containerWidth: width, containerHeight: height, + topMargin: + TOP_MARGIN_FIXED + + ((config.title + ? parseInt(title.getAttribute('font-size')) + : 0) + + (config.subtitle + ? parseInt(subtitle.getAttribute('font-size')) + : 0)) * + 2.5, }) ) From a773bf0e1651d507cea00d168e7314b484b6f7bb Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 30 May 2023 11:19:54 +0000 Subject: [PATCH 071/285] chore(release): cut 25.1.10 [skip ci] ## [25.1.10](https://github.com/dhis2/analytics/compare/v25.1.9...v25.1.10) (2023-05-30) ### Bug Fixes * single value size and position issues (DHIS2-15344) ([#1470](https://github.com/dhis2/analytics/issues/1470)) ([d94fe02](https://github.com/dhis2/analytics/commit/d94fe02c7cc85a6b4aca41c85ba60ed37871b645)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1390425ed..bf5efcefe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.10](https://github.com/dhis2/analytics/compare/v25.1.9...v25.1.10) (2023-05-30) + + +### Bug Fixes + +* single value size and position issues (DHIS2-15344) ([#1470](https://github.com/dhis2/analytics/issues/1470)) ([d94fe02](https://github.com/dhis2/analytics/commit/d94fe02c7cc85a6b4aca41c85ba60ed37871b645)) + ## [25.1.9](https://github.com/dhis2/analytics/compare/v25.1.8...v25.1.9) (2023-05-30) diff --git a/package.json b/package.json index c5075f231..4c78e01a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.9", + "version": "25.1.10", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From df1c7efb3136be99b5026b252a499ba0bb57ecd9 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 5 Jun 2023 11:55:42 +0200 Subject: [PATCH 072/285] fix: add data-test to SV icon (#1479) --- src/visualizations/config/generators/dhis/singleValue.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/visualizations/config/generators/dhis/singleValue.js b/src/visualizations/config/generators/dhis/singleValue.js index 199765800..25ec5bab9 100644 --- a/src/visualizations/config/generators/dhis/singleValue.js +++ b/src/visualizations/config/generators/dhis/singleValue.js @@ -160,6 +160,7 @@ const generateValueSVG = ({ `-${(iconSize + getIconPadding(textSize) + textWidth) / 2}` ) iconSvgNode.setAttribute('style', `color: ${fillColor}`) + iconSvgNode.setAttribute('data-test', 'visualization-icon') const parser = new DOMParser() const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml') From 525479ccfd984c0d08e1bddec36315f1ddd73eb9 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 5 Jun 2023 10:00:02 +0000 Subject: [PATCH 073/285] chore(release): cut 25.1.11 [skip ci] ## [25.1.11](https://github.com/dhis2/analytics/compare/v25.1.10...v25.1.11) (2023-06-05) ### Bug Fixes * add data-test to SV icon ([#1479](https://github.com/dhis2/analytics/issues/1479)) ([df1c7ef](https://github.com/dhis2/analytics/commit/df1c7efb3136be99b5026b252a499ba0bb57ecd9)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf5efcefe..1c9084c28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.11](https://github.com/dhis2/analytics/compare/v25.1.10...v25.1.11) (2023-06-05) + + +### Bug Fixes + +* add data-test to SV icon ([#1479](https://github.com/dhis2/analytics/issues/1479)) ([df1c7ef](https://github.com/dhis2/analytics/commit/df1c7efb3136be99b5026b252a499ba0bb57ecd9)) + ## [25.1.10](https://github.com/dhis2/analytics/compare/v25.1.9...v25.1.10) (2023-05-30) diff --git a/package.json b/package.json index 4c78e01a4..68e772155 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.10", + "version": "25.1.11", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 53fd3007f71cae4bd8dc99d84af44d5c954ebc99 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 9 Jun 2023 03:49:16 +0200 Subject: [PATCH 074/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/es.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/es.po b/i18n/es.po index 76cf602fa..620cc5130 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -344,7 +344,7 @@ msgid "map" msgstr "mapa" msgid "visualization" -msgstr "" +msgstr "visualización" msgid "Edit" msgstr "Editar" @@ -1103,7 +1103,7 @@ msgid "Area" msgstr "Área" msgid "Stacked area" -msgstr "" +msgstr "Área apilada" msgid "Bar" msgstr "Barra" @@ -1139,7 +1139,7 @@ msgid "Radar" msgstr "Radar" msgid "Scatter" -msgstr "" +msgstr "Dispersión" msgid "Single value" msgstr "Valor único" From fa24ec9b0db41f6d76f6fdcdc8f2672f273377bc Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 9 Jun 2023 01:54:59 +0000 Subject: [PATCH 075/285] chore(release): cut 25.1.12 [skip ci] ## [25.1.12](https://github.com/dhis2/analytics/compare/v25.1.11...v25.1.12) (2023-06-09) ### Bug Fixes * **translations:** sync translations from transifex (master) ([53fd300](https://github.com/dhis2/analytics/commit/53fd3007f71cae4bd8dc99d84af44d5c954ebc99)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c9084c28..3198fd197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.12](https://github.com/dhis2/analytics/compare/v25.1.11...v25.1.12) (2023-06-09) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([53fd300](https://github.com/dhis2/analytics/commit/53fd3007f71cae4bd8dc99d84af44d5c954ebc99)) + ## [25.1.11](https://github.com/dhis2/analytics/compare/v25.1.10...v25.1.11) (2023-06-05) diff --git a/package.json b/package.json index 68e772155..d1ff201d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.11", + "version": "25.1.12", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From b8de3e3e90d53214a7ffd662255918bb7e5efc4e Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sat, 10 Jun 2023 03:48:26 +0200 Subject: [PATCH 076/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/cs.po | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/i18n/cs.po b/i18n/cs.po index 043bda071..c6eb2a79d 100644 --- a/i18n/cs.po +++ b/i18n/cs.po @@ -84,42 +84,43 @@ msgid "Network error" msgstr "Chyba sítě" msgid "Data / Edit calculation" -msgstr "" +msgstr "Data / Upravit výpočet" msgid "Data / New calculation" -msgstr "" +msgstr "Data / Nový výpočet" msgid "Remove item" -msgstr "" +msgstr "Odebrat položku" msgid "Check formula" -msgstr "" +msgstr "Zkontrolujte vzorec" msgid "Calculation name" -msgstr "" +msgstr "Název výpočtu" msgid "Shown in table headers and chart axes/legends" -msgstr "" +msgstr "Zobrazeno v záhlaví tabulky a osy/legendy grafu" msgid "Delete calculation" -msgstr "" +msgstr "Smazat výpočet" msgid "Cancel" msgstr "Zrušit" msgid "The calculation can only be saved with a valid formula" -msgstr "" +msgstr "Výpočet lze uložit pouze s platným vzorcem" msgid "Add a name to save this calculation" -msgstr "" +msgstr "Přidejte název pro uložení tohoto výpočtu" msgid "Save calculation" -msgstr "" +msgstr "Uložit výpočet" msgid "" "Are you sure you want to delete this calculation? It may be used by other " "visualizations." msgstr "" +"Opravdu chcete tento výpočet smazat? Může být použit jinými vizualizacemi." msgid "Yes, delete" msgstr "Ano, smazat" @@ -137,7 +138,7 @@ msgid "Data elements" msgstr "Datové prvky" msgid "Search by data element name" -msgstr "" +msgstr "Vyhledávání podle názvu datového prvku" msgid "No data elements found for \"{{- searchTerm}}\"" msgstr "Nebyly nalezeny žádné datové prvky pro „{{- searchTerm}}“" @@ -149,9 +150,11 @@ msgid "" "Drag items here, or double click in the list, to start building a " "calculation formula" msgstr "" +"Přetáhněte položky sem nebo dvakrát klikněte na seznam a začněte vytvářet " +"vzorec výpočtu" msgid "Math operators" -msgstr "" +msgstr "Matematické operátory" msgid "Data Type" msgstr "Typ dat" @@ -202,7 +205,7 @@ msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "Pro dotaz „{{- searchTerm}}“ nebylo nic nalezeno" msgid "Calculation" -msgstr "" +msgstr "Výpočet" msgid "Metric type" msgstr "Metrický typ" @@ -325,7 +328,7 @@ msgid "Rename" msgstr "Přejmenovat" msgid "{{- objectName}} (copy)" -msgstr "" +msgstr "{{- objectName}} (kopie)" msgid "Save {{fileType}} as" msgstr "Uložit {{fileType}} jako" @@ -1003,31 +1006,31 @@ msgid "Program indicator" msgstr "Indikátor programu" msgid "Calculations" -msgstr "" +msgstr "Výpočty" msgid "Number" msgstr "Číslo" msgid "Formula is empty. Add items to the formula from the lists on the left." -msgstr "" +msgstr "Vzorec je prázdný. Přidejte položky do vzorce ze seznamů vlevo." msgid "Consecutive math operators" -msgstr "" +msgstr "Po sobě jdoucí matematické operátory" msgid "Consecutive data elements" -msgstr "" +msgstr "Po sobě jdoucí datové prvky" msgid "Starts or ends with a math operator" -msgstr "" +msgstr "Začíná nebo končí matematickým operátorem" msgid "Empty parentheses" -msgstr "" +msgstr "Prázdné závorky" msgid "Missing right parenthesis )" -msgstr "" +msgstr "Chybí pravá závorka )" msgid "Missing left parenthesis (" -msgstr "" +msgstr "Chybí levá závorka (" msgid "Extra Small" msgstr "Velmi malý" From 3be826d7757167cdba695f6920131afbc9be1ad8 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sat, 10 Jun 2023 01:52:57 +0000 Subject: [PATCH 077/285] chore(release): cut 25.1.13 [skip ci] ## [25.1.13](https://github.com/dhis2/analytics/compare/v25.1.12...v25.1.13) (2023-06-10) ### Bug Fixes * **translations:** sync translations from transifex (master) ([b8de3e3](https://github.com/dhis2/analytics/commit/b8de3e3e90d53214a7ffd662255918bb7e5efc4e)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3198fd197..b14fde7f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.13](https://github.com/dhis2/analytics/compare/v25.1.12...v25.1.13) (2023-06-10) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([b8de3e3](https://github.com/dhis2/analytics/commit/b8de3e3e90d53214a7ffd662255918bb7e5efc4e)) + ## [25.1.12](https://github.com/dhis2/analytics/compare/v25.1.11...v25.1.12) (2023-06-09) diff --git a/package.json b/package.json index d1ff201d2..748a376b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.12", + "version": "25.1.13", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 6706b1e27f243db13e05168ec467828f8f238784 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 11 Jun 2023 03:49:39 +0200 Subject: [PATCH 078/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/es.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/i18n/es.po b/i18n/es.po index 620cc5130..99a2d4bb7 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -4,19 +4,19 @@ # Alison Andrade , 2021 # Juan M Alcantara Acosta , 2021 # Prabhjot Singh, 2021 -# Janeth Cruz, 2022 # Gabriela Rodriguez , 2022 # Viktor Varland , 2022 # Marta Vila , 2023 # phil_dhis2, 2023 # Enzo Nicolas Rossi , 2023 +# Janeth Cruz, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Enzo Nicolas Rossi , 2023\n" +"Last-Translator: Janeth Cruz, 2023\n" "Language-Team: Spanish (https://app.transifex.com/hisp-uio/teams/100509/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -239,7 +239,7 @@ msgid "Filter dimensions" msgstr "Dimensiones del filtro" msgid "Main dimensions" -msgstr "" +msgstr "Dimensiones principales" msgid "Your dimensions" msgstr "Dimensiones propias" @@ -881,7 +881,7 @@ msgid "Retry" msgstr "" msgid "Series" -msgstr "" +msgstr "Series" msgid "Category" msgstr "Categoría" @@ -905,7 +905,7 @@ msgid "Reporting rate on time" msgstr "" msgid "Actual reports" -msgstr "" +msgstr "Informes reales" msgid "Actual reports on time" msgstr "" @@ -1169,7 +1169,7 @@ msgid "Base" msgstr "" msgid "Axis {{axisId}}" -msgstr "" +msgstr "Eje {{axisId}}" msgid "{{count}} items" msgid_plural "{{count}} items" From 8c0bc313ee9cca224d266465544f6a590969cba3 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 11 Jun 2023 01:54:12 +0000 Subject: [PATCH 079/285] chore(release): cut 25.1.14 [skip ci] ## [25.1.14](https://github.com/dhis2/analytics/compare/v25.1.13...v25.1.14) (2023-06-11) ### Bug Fixes * **translations:** sync translations from transifex (master) ([6706b1e](https://github.com/dhis2/analytics/commit/6706b1e27f243db13e05168ec467828f8f238784)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b14fde7f6..48b9aadad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.14](https://github.com/dhis2/analytics/compare/v25.1.13...v25.1.14) (2023-06-11) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([6706b1e](https://github.com/dhis2/analytics/commit/6706b1e27f243db13e05168ec467828f8f238784)) + ## [25.1.13](https://github.com/dhis2/analytics/compare/v25.1.12...v25.1.13) (2023-06-10) diff --git a/package.json b/package.json index 748a376b6..13bfd78fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.13", + "version": "25.1.14", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From f98c7fc8ce624f1ee6d1f32b93f7327b4f8358aa Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 12 Jun 2023 03:49:19 +0200 Subject: [PATCH 080/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/es.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/es.po b/i18n/es.po index 99a2d4bb7..bd7708d7b 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -899,7 +899,7 @@ msgid "Points" msgstr "" msgid "Reporting rate" -msgstr "" +msgstr "Tasa de informes" msgid "Reporting rate on time" msgstr "" @@ -911,7 +911,7 @@ msgid "Actual reports on time" msgstr "" msgid "Expected reports" -msgstr "" +msgstr "Informes esperados" msgid "Program" msgstr "Programa" From ff5ea10adedcc8e1801eb361321833cce14428be Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 12 Jun 2023 01:55:45 +0000 Subject: [PATCH 081/285] chore(release): cut 25.1.15 [skip ci] ## [25.1.15](https://github.com/dhis2/analytics/compare/v25.1.14...v25.1.15) (2023-06-12) ### Bug Fixes * **translations:** sync translations from transifex (master) ([f98c7fc](https://github.com/dhis2/analytics/commit/f98c7fc8ce624f1ee6d1f32b93f7327b4f8358aa)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48b9aadad..7ae1b0ce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.15](https://github.com/dhis2/analytics/compare/v25.1.14...v25.1.15) (2023-06-12) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([f98c7fc](https://github.com/dhis2/analytics/commit/f98c7fc8ce624f1ee6d1f32b93f7327b4f8358aa)) + ## [25.1.14](https://github.com/dhis2/analytics/compare/v25.1.13...v25.1.14) (2023-06-11) diff --git a/package.json b/package.json index 13bfd78fe..da97136d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.14", + "version": "25.1.15", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From f0d63a7e8fc086d23f5c8775a9ffefacb2240657 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 13 Jun 2023 03:48:32 +0200 Subject: [PATCH 082/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/nl.po | 166 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 102 insertions(+), 64 deletions(-) diff --git a/i18n/nl.po b/i18n/nl.po index f5ff7073e..3b12ebeaa 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -3,14 +3,15 @@ # Cherise Beek , 2021 # Yury Rogachev , 2021 # Rica Zamora Duchateau, 2022 +# Charel van den Elsen, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-09-22T13:25:32.620Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Rica Zamora Duchateau, 2022\n" -"Language-Team: Dutch (https://www.transifex.com/hisp-uio/teams/100509/nl/)\n" +"Last-Translator: Charel van den Elsen, 2023\n" +"Language-Team: Dutch (https://app.transifex.com/hisp-uio/teams/100509/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -82,10 +83,45 @@ msgstr "" msgid "Network error" msgstr "" -msgid "Data Type" +msgid "Data / Edit calculation" msgstr "" -msgid "All types" +msgid "Data / New calculation" +msgstr "" + +msgid "Remove item" +msgstr "" + +msgid "Check formula" +msgstr "" + +msgid "Calculation name" +msgstr "" + +msgid "Shown in table headers and chart axes/legends" +msgstr "" + +msgid "Delete calculation" +msgstr "" + +msgid "Cancel" +msgstr "Annuleer" + +msgid "The calculation can only be saved with a valid formula" +msgstr "" + +msgid "Add a name to save this calculation" +msgstr "" + +msgid "Save calculation" +msgstr "" + +msgid "" +"Are you sure you want to delete this calculation? It may be used by other " +"visualizations." +msgstr "" + +msgid "Yes, delete" msgstr "" msgid "Totals only" @@ -94,15 +130,41 @@ msgstr "" msgid "Details only" msgstr "" +msgid "Loading" +msgstr "" + +msgid "Data elements" +msgstr "Gegevenselementen" + +msgid "Search by data element name" +msgstr "" + +msgid "No data elements found for \"{{- searchTerm}}\"" +msgstr "" + +msgid "No data elements found" +msgstr "" + +msgid "" +"Drag items here, or double click in the list, to start building a " +"calculation formula" +msgstr "" + +msgid "Math operators" +msgstr "" + +msgid "Data Type" +msgstr "" + +msgid "All types" +msgstr "" + msgid "Disaggregation" msgstr "" msgid "No data" msgstr "Geen gegevens" -msgid "Loading" -msgstr "" - msgid "Search by data item name" msgstr "" @@ -115,9 +177,6 @@ msgstr "" msgid "No indicators found" msgstr "" -msgid "No data elements found" -msgstr "" - msgid "No data sets found" msgstr "" @@ -130,9 +189,6 @@ msgstr "" msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "" -msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" - msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -145,6 +201,9 @@ msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "" +msgid "Calculation" +msgstr "" + msgid "Metric type" msgstr "" @@ -211,9 +270,6 @@ msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" -msgid "Cancel" -msgstr "Annuleer" - msgid "Delete" msgstr "Verwijderen" @@ -221,7 +277,7 @@ msgid "File" msgstr "" msgid "New" -msgstr "" +msgstr "Nieuw" msgid "Open…" msgstr "" @@ -265,7 +321,7 @@ msgstr "Beschrijving" msgid "Rename" msgstr "" -msgid "{{objectName}} (copy)" +msgid "{{- objectName}} (copy)" msgstr "" msgid "Save {{fileType}} as" @@ -583,48 +639,6 @@ msgstr "" msgid "No periods selected" msgstr "" -msgid "January" -msgstr "" - -msgid "February" -msgstr "" - -msgid "March" -msgstr "" - -msgid "April" -msgstr "" - -msgid "May" -msgstr "" - -msgid "June" -msgstr "" - -msgid "July" -msgstr "" - -msgid "August" -msgstr "" - -msgid "September" -msgstr "" - -msgid "October" -msgstr "" - -msgid "November" -msgstr "" - -msgid "December" -msgstr "" - -msgid "Week {{weekNumber}}" -msgstr "" - -msgid "Bi-Week {{biWeekNumber}}" -msgstr "" - msgid "Daily" msgstr "Dagelijks" @@ -913,9 +927,6 @@ msgstr "" msgid "Loading indicator groups" msgstr "" -msgid "Data elements" -msgstr "Gegevenselementen" - msgid "Data element group" msgstr "Gegevenselementengroep" @@ -961,6 +972,33 @@ msgstr "Programmaindicators" msgid "Program indicator" msgstr "Programmaindicator" +msgid "Calculations" +msgstr "" + +msgid "Number" +msgstr "Nummer" + +msgid "Formula is empty. Add items to the formula from the lists on the left." +msgstr "" + +msgid "Consecutive math operators" +msgstr "" + +msgid "Consecutive data elements" +msgstr "" + +msgid "Starts or ends with a math operator" +msgstr "" + +msgid "Empty parentheses" +msgstr "" + +msgid "Missing right parenthesis )" +msgstr "" + +msgid "Missing left parenthesis (" +msgstr "" + msgid "Extra Small" msgstr "" From 99212e4f9a2917e31e43d252b5899e9bde0b1d33 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 13 Jun 2023 01:52:54 +0000 Subject: [PATCH 083/285] chore(release): cut 25.1.16 [skip ci] ## [25.1.16](https://github.com/dhis2/analytics/compare/v25.1.15...v25.1.16) (2023-06-13) ### Bug Fixes * **translations:** sync translations from transifex (master) ([f0d63a7](https://github.com/dhis2/analytics/commit/f0d63a7e8fc086d23f5c8775a9ffefacb2240657)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ae1b0ce5..817fffac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.16](https://github.com/dhis2/analytics/compare/v25.1.15...v25.1.16) (2023-06-13) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([f0d63a7](https://github.com/dhis2/analytics/commit/f0d63a7e8fc086d23f5c8775a9ffefacb2240657)) + ## [25.1.15](https://github.com/dhis2/analytics/compare/v25.1.14...v25.1.15) (2023-06-12) diff --git a/package.json b/package.json index da97136d1..7c7d6ad7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.15", + "version": "25.1.16", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 1b667dc344457c100da0dec17c0f064fc8d242c9 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 14 Jun 2023 03:48:15 +0200 Subject: [PATCH 084/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/es.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/es.po b/i18n/es.po index bd7708d7b..2dc726057 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -230,7 +230,7 @@ msgid "Remove Assigned Categories" msgstr "" msgid "Add Assigned Categories" -msgstr "" +msgstr "Añadir categorías asignadas" msgid "Remove" msgstr "Eliminar" From 898d085c44482aa7956ff48635c6bce98bd52c07 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 14 Jun 2023 01:52:38 +0000 Subject: [PATCH 085/285] chore(release): cut 25.1.17 [skip ci] ## [25.1.17](https://github.com/dhis2/analytics/compare/v25.1.16...v25.1.17) (2023-06-14) ### Bug Fixes * **translations:** sync translations from transifex (master) ([1b667dc](https://github.com/dhis2/analytics/commit/1b667dc344457c100da0dec17c0f064fc8d242c9)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 817fffac5..1140c077f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.17](https://github.com/dhis2/analytics/compare/v25.1.16...v25.1.17) (2023-06-14) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([1b667dc](https://github.com/dhis2/analytics/commit/1b667dc344457c100da0dec17c0f064fc8d242c9)) + ## [25.1.16](https://github.com/dhis2/analytics/compare/v25.1.15...v25.1.16) (2023-06-13) diff --git a/package.json b/package.json index 7c7d6ad7f..d8410e964 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.16", + "version": "25.1.17", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 8e1516498f76e95b3c10a4c79df979361fb4e7d1 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 15 Jun 2023 03:47:25 +0200 Subject: [PATCH 086/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/nl.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/i18n/nl.po b/i18n/nl.po index 3b12ebeaa..1b6ed6c5b 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -459,7 +459,7 @@ msgid "Filter by name" msgstr "" msgid "Created" -msgstr "" +msgstr "Gemaakt" msgid "Last updated" msgstr "Laatst bijgewerkt" @@ -474,7 +474,7 @@ msgid "{{firstItemIndex}}-{{lastItemIndex}} of {{totalNumberOfItems}}" msgstr "" msgid "Open" -msgstr "" +msgstr "Open" msgid "Couldn't load items" msgstr "" @@ -817,7 +817,7 @@ msgid "Last 10 years" msgstr "" msgid "Days" -msgstr "" +msgstr "Dagen" msgid "Weeks" msgstr "" @@ -826,7 +826,7 @@ msgid "Bi-weeks" msgstr "" msgid "Months" -msgstr "" +msgstr "Maanden" msgid "Bi-months" msgstr "" @@ -841,7 +841,7 @@ msgid "Financial Years" msgstr "" msgid "Years" -msgstr "" +msgstr "Jaren" msgid "Translating to" msgstr "" From ccd13c87a15ae34c0e6b9988b7916dc2f44a394f Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 15 Jun 2023 01:52:06 +0000 Subject: [PATCH 087/285] chore(release): cut 25.1.18 [skip ci] ## [25.1.18](https://github.com/dhis2/analytics/compare/v25.1.17...v25.1.18) (2023-06-15) ### Bug Fixes * **translations:** sync translations from transifex (master) ([8e15164](https://github.com/dhis2/analytics/commit/8e1516498f76e95b3c10a4c79df979361fb4e7d1)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1140c077f..f53119e8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.18](https://github.com/dhis2/analytics/compare/v25.1.17...v25.1.18) (2023-06-15) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([8e15164](https://github.com/dhis2/analytics/commit/8e1516498f76e95b3c10a4c79df979361fb4e7d1)) + ## [25.1.17](https://github.com/dhis2/analytics/compare/v25.1.16...v25.1.17) (2023-06-14) diff --git a/package.json b/package.json index d8410e964..094afc04e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.17", + "version": "25.1.18", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From a0626acbe00ad9e9ed551cd31209267a6814dcce Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 16 Jun 2023 03:48:17 +0200 Subject: [PATCH 088/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/nl.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i18n/nl.po b/i18n/nl.po index 1b6ed6c5b..d8eade5cd 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -37,7 +37,7 @@ msgid "Not shared with any users or groups" msgstr "" msgid "No description" -msgstr "" +msgstr "Geen beschrijving" msgid "Last updated {{time}}" msgstr "" @@ -258,7 +258,7 @@ msgid "Nothing found in {{- dimensionTitle}}" msgstr "" msgid "Search" -msgstr "" +msgstr "Zoek" msgid "Nothing found for {{- searchTerm}}" msgstr "" @@ -390,7 +390,7 @@ msgid "Reply" msgstr "" msgid "Share" -msgstr "" +msgstr "Deel" msgid "See interpretation" msgstr "" @@ -607,7 +607,7 @@ msgid "User sub-x2-units" msgstr "" msgid "Select a level" -msgstr "" +msgstr "Selecteer een niveau" msgid "Select a group" msgstr "" From 1cbda85a550fb851d7bff006f4274c6494a37408 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 16 Jun 2023 01:54:30 +0000 Subject: [PATCH 089/285] chore(release): cut 25.1.19 [skip ci] ## [25.1.19](https://github.com/dhis2/analytics/compare/v25.1.18...v25.1.19) (2023-06-16) ### Bug Fixes * **translations:** sync translations from transifex (master) ([a0626ac](https://github.com/dhis2/analytics/commit/a0626acbe00ad9e9ed551cd31209267a6814dcce)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f53119e8d..fe186485a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.19](https://github.com/dhis2/analytics/compare/v25.1.18...v25.1.19) (2023-06-16) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([a0626ac](https://github.com/dhis2/analytics/commit/a0626acbe00ad9e9ed551cd31209267a6814dcce)) + ## [25.1.18](https://github.com/dhis2/analytics/compare/v25.1.17...v25.1.18) (2023-06-15) diff --git a/package.json b/package.json index 094afc04e..c926c6237 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.18", + "version": "25.1.19", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 7b62a962501d791be3bc2c4d9a71d35f43ab8eb5 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sat, 17 Jun 2023 03:46:36 +0200 Subject: [PATCH 090/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/es.po | 250 +++++++++++++++++++++++++++++------------------------ 1 file changed, 135 insertions(+), 115 deletions(-) diff --git a/i18n/es.po b/i18n/es.po index 2dc726057..a033c7c00 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -25,31 +25,31 @@ msgstr "" "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" msgid "view only" -msgstr "" +msgstr "Solo lectura" msgid "view and edit" -msgstr "" +msgstr "Ver y editar" msgid "all users ({{accessLevel}})" -msgstr "" +msgstr "todos los usuarios ({{accessLevel}})" msgid "{{userOrGroup}} ({{accessLevel}})" -msgstr "" +msgstr "{{userOrGroup}} ({{accessLevel}})" msgid "Shared with {{commaSeparatedListOfUsersAndGroups}}" -msgstr "" +msgstr "Compartido con {{commaSeparatedListOfUsersAndGroups}}" msgid "Not shared with any users or groups" -msgstr "" +msgstr "No compartido con ningún usuario o grupo." msgid "No description" msgstr "Sin descripción" msgid "Last updated {{time}}" -msgstr "" +msgstr "Última actualización {{time}}" msgid "Created {{time}} by {{author}}" -msgstr "" +msgstr "Creado {{time}} por {{author}}" msgid "Created {{time}}" msgstr "" @@ -61,19 +61,21 @@ msgstr[1] "" msgstr[2] "" msgid "Notifications" -msgstr "" +msgstr "Notificaciones" msgid "You're subscribed and getting updates about new interpretations." msgstr "" +"Está suscrito y recibiendo actualizaciones sobre nuevas interpretaciones." msgid "Unsubscribe" -msgstr "" +msgstr "Cancelar la suscripción" msgid "Subscribe to get updates about new interpretations." msgstr "" +"Suscríbase para recibir actualizaciones sobre nuevas interpretaciones." msgid "Subscribe" -msgstr "" +msgstr "Suscribirse" msgid "About this map" msgstr "" @@ -82,10 +84,10 @@ msgid "About this line list" msgstr "" msgid "About this visualization" -msgstr "" +msgstr "Acerca de esta visualización" msgid "This app could not retrieve required data." -msgstr "" +msgstr "Esta aplicación no pudo recuperar los datos requeridos." msgid "Network error" msgstr "Error de red" @@ -132,10 +134,10 @@ msgid "Yes, delete" msgstr "si, eliminar" msgid "Totals only" -msgstr "" +msgstr "Solo totales" msgid "Details only" -msgstr "" +msgstr "Solo detalles" msgid "Loading" msgstr "Cargando" @@ -147,10 +149,10 @@ msgid "Search by data element name" msgstr "" msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "" +msgstr "No se han encontrado elementos de datos para \"{{- searchTerm}}\"" msgid "No data elements found" -msgstr "" +msgstr "No se han encontrado elementos de datos" msgid "" "Drag items here, or double click in the list, to start building a " @@ -173,16 +175,16 @@ msgid "No data" msgstr "No hay datos" msgid "Search by data item name" -msgstr "" +msgstr "Búsqueda por nombre de elemento de datos" msgid "No items selected" -msgstr "" +msgstr "No hay elementos seleccionados" msgid "Selected Items" -msgstr "" +msgstr "Elementos seleccionados" msgid "No indicators found" -msgstr "" +msgstr "No se han encontrado indicadores" msgid "No data sets found" msgstr "" @@ -191,10 +193,10 @@ msgid "No event data items found" msgstr "" msgid "No program indicators found" -msgstr "" +msgstr "No se han encontrado indicadores de programa" msgid "No indicators found for \"{{- searchTerm}}\"" -msgstr "" +msgstr "No se han encontrado indicadores para \"{{- searchTerm}}\"" msgid "No data sets found for \"{{- searchTerm}}\"" msgstr "" @@ -203,16 +205,16 @@ msgid "No event data items found for \"{{- searchTerm}}\"" msgstr "" msgid "No program indicators found for \"{{- searchTerm}}\"" -msgstr "" +msgstr "No se han encontrado indicadores de programa para \"{{- searchTerm}}\"" msgid "Nothing found for \"{{- searchTerm}}\"" -msgstr "" +msgstr "No se encontró nada para \"{{- searchTerm}}\"" msgid "Calculation" msgstr "" msgid "Metric type" -msgstr "" +msgstr "Tipo de métrica" msgid "All metrics" msgstr "Todas las métricas" @@ -224,10 +226,10 @@ msgid "Add to {{axisName}}" msgstr "Añadir a {{Nombredeleje}}" msgid "Not available for {{visualizationType}}" -msgstr "" +msgstr "No disponible para {{visualizationType}}" msgid "Remove Assigned Categories" -msgstr "" +msgstr "Eliminar categorías asignadas" msgid "Add Assigned Categories" msgstr "Añadir categorías asignadas" @@ -245,37 +247,41 @@ msgid "Your dimensions" msgstr "Dimensiones propias" msgid "Dimension recommended with selected data" -msgstr "" +msgstr "Dimensión recomendada con datos seleccionados" msgid "All items" msgstr "Todos los elementos" msgid "Automatically include all items" -msgstr "" +msgstr "Incluir automáticamente todos los elementos" msgid "" "Select all {{- dimensionTitle}} items. With this option, new items added in " "the future will be automatically included." msgstr "" +"Seleccionar todos los elementos {{- dimensionTitle}}. Con esta opción, los " +"nuevos elementos que se añadan en el futuro se incluirán automáticamente." msgid "Manually select items..." -msgstr "" +msgstr "Seleccionar manualmente los elementos..." msgid "Nothing found in {{- dimensionTitle}}" -msgstr "" +msgstr "No se encontró nada en {{- dimensionTitle}}" msgid "Search" msgstr "Buscar" msgid "Nothing found for {{- searchTerm}}" -msgstr "" +msgstr "No se encontró nada para {{- searchTerm}}" msgid "Delete {{fileType}}" -msgstr "" +msgstr "Eliminar {{fileType}}" msgid "" "This {{fileType}} and related interpretations will be deleted. Continue?" msgstr "" +"Este {{fileType}} y las interpretaciones relacionadas se eliminarán. " +"¿Continuar?" msgid "Delete" msgstr "Eliminar" @@ -287,28 +293,28 @@ msgid "New" msgstr "Nuevo" msgid "Open…" -msgstr "" +msgstr "Abrir…" msgid "Save" msgstr "Guardar" msgid "Save…" -msgstr "" +msgstr "Guardar…" msgid "Save as…" -msgstr "" +msgstr "Guardar como…" msgid "Rename…" -msgstr "" +msgstr "Renombrar…" msgid "Translate…" -msgstr "" +msgstr "Traducir…" msgid "Share…" -msgstr "" +msgstr "Compartir…" msgid "Get link…" -msgstr "" +msgstr "Obtener enlace…" msgid "Open in this app" msgstr "Abierto en esta aplicación" @@ -317,7 +323,7 @@ msgid "Close" msgstr "Cerrar" msgid "Rename {{fileType}}" -msgstr "" +msgstr "Renombrar {{fileType}}" msgid "Name" msgstr "Nombre" @@ -332,7 +338,7 @@ msgid "{{- objectName}} (copy)" msgstr "" msgid "Save {{fileType}} as" -msgstr "" +msgstr "Guardar {{fileType}} como" msgid "event report" msgstr "informe de evento" @@ -353,13 +359,13 @@ msgid "Write a reply" msgstr "Escribe una respuesta" msgid "Post reply" -msgstr "" +msgstr "Publicar respuesta" msgid "Could not update comment" -msgstr "" +msgstr "No se pudo actualizar el comentario" msgid "Enter comment text" -msgstr "" +msgstr "Introducir el texto del comentario" msgid "Update" msgstr "Actualizar" @@ -368,7 +374,7 @@ msgid "Viewing interpretation: {{- visualisationName}}" msgstr "" msgid "Could not load interpretation" -msgstr "" +msgstr "No se pudo cargar la interpretación" msgid "" "The interpretation couldn’t be displayed. Try again or contact your system " @@ -454,13 +460,13 @@ msgid "Anyone" msgstr "Alguien" msgid "Only you" -msgstr "" +msgstr "Solo tú" msgid "Others" msgstr "Otros" msgid "Not supported by this app yet" -msgstr "" +msgstr "Todavía no es soportado por esta aplicación" msgid "Filter by name" msgstr "Filtrar por nombre" @@ -478,79 +484,93 @@ msgid "Clear filters" msgstr "Limpiar filtros" msgid "{{firstItemIndex}}-{{lastItemIndex}} of {{totalNumberOfItems}}" -msgstr "" +msgstr "{{firstItemIndex}}-{{lastItemIndex}} de {{totalNumberOfItems}}" msgid "Open" msgstr "Abierto" msgid "Couldn't load items" -msgstr "" +msgstr "No se pudieron cargar los elementos" msgid "" "There was a problem loading items. Try again or contact your system " "administrator." msgstr "" +"Hubo un problema al cargar los elementos. Vuelva a intentarlo o póngase en " +"contacto con el administrador del sistema." msgid "No items found. Create a new to get started." -msgstr "" +msgstr "No se han encontrado elementos. Cree uno nuevo para comenzar." msgid "" "No items found. Try adjusting your search or filter options to find what " "you're looking for." msgstr "" +"No se han encontrado elementos. Intente ajustar las opciones de búsqueda o " +"filtro para encontrar lo que está buscando." msgid "Create new" msgstr "Crear nuevo" msgid "Open a visualization" -msgstr "" +msgstr "Abrir una visualización" msgid "Loading visualizations" -msgstr "" +msgstr "Cargando visualizaciones" msgid "Couldn't load visualizations" -msgstr "" +msgstr "No se pudieron cargar las visualizaciones" msgid "" "There was a problem loading visualizations. Try again or contact your system" " administrator." msgstr "" +"Hubo un problema al cargar las visualizaciones. Vuelva a intentarlo o " +"póngase en contacto con el administrador del sistema." msgid "No visualizations found. Click New visualization to get started." msgstr "" +"No se han encontrado visualizaciones. Haga clic en Nueva visualización para " +"comenzar." msgid "" "No visualizations found. Try adjusting your search or filter options to find" " what you're looking for." msgstr "" +"No se han encontrado visualizaciones. Intente ajustar las opciones de " +"búsqueda o filtro para encontrar lo que está buscando." msgid "New visualization" -msgstr "" +msgstr "Nueva visualización" msgid "Open a map" -msgstr "" +msgstr "Abrir un mapa" msgid "Loading maps" -msgstr "" +msgstr "Cargando mapas" msgid "Couldn't load maps" -msgstr "" +msgstr "No se pudieron cargar los mapas" msgid "" "There was a problem loading maps. Try again or contact your system " "administrator." msgstr "" +"Hubo un problema al cargar los mapas. Vuelva a intentarlo o póngase en " +"contacto con el administrador del sistema." msgid "No maps found. Click New map to get started." -msgstr "" +msgstr "No se han encontrado mapas. Haga clic en Nuevo mapa para comenzar." msgid "" "No maps found. Try adjusting your search or filter options to find what " "you're looking for." msgstr "" +"No se han encontrado mapas. Intente ajustar las opciones de búsqueda o " +"filtro para encontrar lo que está buscando." msgid "New map" -msgstr "" +msgstr "Nuevo mapa" msgid "Open a line list" msgstr "" @@ -602,10 +622,10 @@ msgstr[1] "" msgstr[2] "" msgid "Selected: {{commaSeparatedListOfOrganisationUnits}}" -msgstr "" +msgstr "Seleccionado: {{commaSeparatedListOfOrganisationUnits}}" msgid "Nothing selected" -msgstr "" +msgstr "Nada seleccionado" msgid "User organisation unit" msgstr "Usuario de unidad organizativa" @@ -644,7 +664,7 @@ msgid "Fixed periods" msgstr "Periodos fijos" msgid "Selected Periods" -msgstr "" +msgstr "Periodos seleccionados" msgid "No periods selected" msgstr "No se ha seleccionado ningún periodo" @@ -656,16 +676,16 @@ msgid "Weekly" msgstr "Semanal" msgid "Weekly (Start Wednesday)" -msgstr "" +msgstr "Semanal (Inicio miércoles)" msgid "Weekly (Start Thursday)" -msgstr "" +msgstr "Semanal (Inicio jueves)" msgid "Weekly (Start Saturday)" -msgstr "" +msgstr "Semanal (Inicio sábado)" msgid "Weekly (Start Sunday)" -msgstr "" +msgstr "Semanal (Inicio domingo)" msgid "Bi-weekly" msgstr "Quincenal" @@ -719,13 +739,13 @@ msgid "Last 30 days" msgstr "Últimos 30 días" msgid "Last 60 days" -msgstr "" +msgstr "Últimos 60 días" msgid "Last 90 days" -msgstr "" +msgstr "Últimos 90 días" msgid "Last 180 days" -msgstr "" +msgstr "Últimos 180 días" msgid "This week" msgstr "Esta semana" @@ -803,7 +823,7 @@ msgid "Last six-month" msgstr "Último semestre" msgid "Last 2 six-month" -msgstr "" +msgstr "Últimos 2 semestres" msgid "This financial year" msgstr "Este año fiscal" @@ -824,7 +844,7 @@ msgid "Last 5 years" msgstr "Últimos 5 años" msgid "Last 10 years" -msgstr "" +msgstr "Últimos 10 años" msgid "Days" msgstr "Días" @@ -854,7 +874,7 @@ msgid "Years" msgstr "Años" msgid "Translating to" -msgstr "" +msgstr "Traducir a" msgid "Choose a locale" msgstr "" @@ -866,19 +886,19 @@ msgid "Choose a locale to translate from the menu above" msgstr "" msgid "Translate: {{objectName}}" -msgstr "" +msgstr "Traducir: {{objectName}}" msgid "Save translations" -msgstr "" +msgstr "Guardar traducciones" msgid "Cannot save while offline" msgstr "" msgid "Could not load translations" -msgstr "" +msgstr "No se pudieron cargar las traducciones" msgid "Retry" -msgstr "" +msgstr "Reintentar" msgid "Series" msgstr "Series" @@ -890,13 +910,13 @@ msgid "Filter" msgstr "filtro" msgid "Columns" -msgstr "" +msgstr "Columnas" msgid "Rows" -msgstr "" +msgstr "Filas" msgid "Points" -msgstr "" +msgstr "Puntos" msgid "Reporting rate" msgstr "Tasa de informes" @@ -917,7 +937,7 @@ msgid "Program" msgstr "Programa" msgid "Select a program" -msgstr "" +msgstr "Seleccionar un programa" msgid "Indicators" msgstr "Indicadores" @@ -926,16 +946,16 @@ msgid "Indicator group" msgstr "Grupo de indicadores" msgid "All groups" -msgstr "" +msgstr "Todos los grupos" msgid "Indicator" msgstr "Indicadores" msgid "No indicator groups found" -msgstr "" +msgstr "No se han encontrado grupos de indicadores" msgid "Loading indicator groups" -msgstr "" +msgstr "Cargando grupos de indicadores" msgid "Data element group" msgstr "Grupo de elemento de datos" @@ -944,10 +964,10 @@ msgid "Data element" msgstr "Elemento de datos" msgid "No data element groups found" -msgstr "" +msgstr "No se han encontrado grupos de elementos de datos" msgid "Loading data element groups" -msgstr "" +msgstr "Cargando grupos de elementos de datos" msgid "Data sets" msgstr "Sets de datos" @@ -965,22 +985,22 @@ msgid "Event data items" msgstr "Datos de eventos" msgid "All programs" -msgstr "" +msgstr "Todos los programas" msgid "Event data item" msgstr "Event data item" msgid "No programs found" -msgstr "" +msgstr "No se han encontrado programas" msgid "Loading programs" -msgstr "" +msgstr "Cargando programas" msgid "Program indicators" msgstr "Indicadores de programa" msgid "Program indicator" -msgstr "" +msgstr "Indicador de Programa" msgid "Calculations" msgstr "" @@ -1010,7 +1030,7 @@ msgid "Missing left parenthesis (" msgstr "" msgid "Extra Small" -msgstr "" +msgstr "Extra pequeño" msgid "Small" msgstr "Pequeña" @@ -1022,13 +1042,13 @@ msgid "Large" msgstr "Grande" msgid "Extra Large" -msgstr "" +msgstr "Extra grande" msgid "Left" msgstr "Izquierdo" msgid "Center" -msgstr "" +msgstr "Centrar" msgid "Right" msgstr "Derecha" @@ -1040,7 +1060,7 @@ msgid "Middle" msgstr "" msgid "End" -msgstr "" +msgstr "Fin" msgid "Top" msgstr "Arriba" @@ -1049,43 +1069,43 @@ msgid "Bottom" msgstr "Abajo" msgid "{{dynamicOuNames}} and {{lastOuName}}" -msgstr "" +msgstr "{{dynamicOuNames}} y {{lastOuName}}" msgid "{{allDynamicOuNames}} levels" -msgstr "" +msgstr "{{allDynamicOuNames}} niveles" msgid "{{allDynamicOuNames}} groups" -msgstr "" +msgstr "{{allDynamicOuNames}} grupos" msgid "{{allDynamicOuNames}} levels in {{staticOuNames}}" -msgstr "" +msgstr "{{allDynamicOuNames}} niveles en {{staticOuNames}}" msgid "{{allDynamicOuNames}} groups in {{staticOuNames}}" -msgstr "" +msgstr "{{allDynamicOuNames}} grupos en {{staticOuNames}}" msgid "{{percentage}}% of total x values" -msgstr "" +msgstr "{{percentage}}% del total de valores x" msgid "{{percentage}}% of total y values" -msgstr "" +msgstr "{{percentage}}% del total de valores y" msgid "{{thresholdFactor}} × IQR Q1" -msgstr "" +msgstr "{{thresholdFactor}} × IQR Q1" msgid "{{thresholdFactor}} × IQR Q3" -msgstr "" +msgstr "{{thresholdFactor}} × IQR Q3" msgid "{{thresholdFactor}} × Modified Z-score low" -msgstr "" +msgstr "{{thresholdFactor}} × Modificado Z-score bajo" msgid "{{thresholdFactor}} × Modified Z-score high" -msgstr "" +msgstr "{{thresholdFactor}} × Modificado Z-score alto" msgid "{{thresholdFactor}} × Z-score low" -msgstr "" +msgstr "{{thresholdFactor}} × Z-score bajo" msgid "{{thresholdFactor}} × Z-score high" -msgstr "" +msgstr "{{thresholdFactor}} × Z-score alto" msgid "Data" msgstr "Datos" @@ -1094,7 +1114,7 @@ msgid "Organisation unit" msgstr "Unidad organizativa" msgid "Assigned Categories" -msgstr "" +msgstr "Categorías asignadas" msgid "Pivot table" msgstr "Tabla dinámica" @@ -1145,28 +1165,28 @@ msgid "Single value" msgstr "Valor único" msgid "All charts" -msgstr "" +msgstr "Todos los gráficos" msgid "{{seriesName}} (trend)" -msgstr "" +msgstr "{{seriesName}} (tendencia)" msgid "Trend" msgstr "Tendencia" msgid "No legend for this series" -msgstr "" +msgstr "Ninguna leyenda para esta serie" msgid "and {{amount}} more..." -msgstr "" +msgstr "y {{amount}} más..." msgid "Linear Regression" -msgstr "" +msgstr "Regresión lineal" msgid "Target" msgstr "Objetivo" msgid "Base" -msgstr "" +msgstr "Base" msgid "Axis {{axisId}}" msgstr "Eje {{axisId}}" From 3597b76029789fe988e5b6447e4ce17804e2f28b Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sat, 17 Jun 2023 01:51:09 +0000 Subject: [PATCH 091/285] chore(release): cut 25.1.20 [skip ci] ## [25.1.20](https://github.com/dhis2/analytics/compare/v25.1.19...v25.1.20) (2023-06-17) ### Bug Fixes * **translations:** sync translations from transifex (master) ([7b62a96](https://github.com/dhis2/analytics/commit/7b62a962501d791be3bc2c4d9a71d35f43ab8eb5)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe186485a..bae36dbcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.20](https://github.com/dhis2/analytics/compare/v25.1.19...v25.1.20) (2023-06-17) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([7b62a96](https://github.com/dhis2/analytics/commit/7b62a962501d791be3bc2c4d9a71d35f43ab8eb5)) + ## [25.1.19](https://github.com/dhis2/analytics/compare/v25.1.18...v25.1.19) (2023-06-16) diff --git a/package.json b/package.json index c926c6237..31e0fb073 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.19", + "version": "25.1.20", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 90ed63cb551e4b6f7d5ff8f46e62e2117458f70f Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 18 Jun 2023 03:50:01 +0200 Subject: [PATCH 092/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/nl.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/nl.po b/i18n/nl.po index d8eade5cd..ceb0b5b7d 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -384,7 +384,7 @@ msgid "Unlike" msgstr "" msgid "Like" -msgstr "" +msgstr "Leuk vinden" msgid "Reply" msgstr "" @@ -910,7 +910,7 @@ msgid "Select a program" msgstr "" msgid "Indicators" -msgstr "" +msgstr "Indicatoren" msgid "Indicator group" msgstr "Indicatorgroep" From f9901e01ad0bd3d7227529892dcb2f4d8aebb3ea Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 18 Jun 2023 01:54:55 +0000 Subject: [PATCH 093/285] chore(release): cut 25.1.21 [skip ci] ## [25.1.21](https://github.com/dhis2/analytics/compare/v25.1.20...v25.1.21) (2023-06-18) ### Bug Fixes * **translations:** sync translations from transifex (master) ([90ed63c](https://github.com/dhis2/analytics/commit/90ed63cb551e4b6f7d5ff8f46e62e2117458f70f)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bae36dbcf..833702017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.21](https://github.com/dhis2/analytics/compare/v25.1.20...v25.1.21) (2023-06-18) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([90ed63c](https://github.com/dhis2/analytics/commit/90ed63cb551e4b6f7d5ff8f46e62e2117458f70f)) + ## [25.1.20](https://github.com/dhis2/analytics/compare/v25.1.19...v25.1.20) (2023-06-17) diff --git a/package.json b/package.json index 31e0fb073..5c82277c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.20", + "version": "25.1.21", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From df3fa786b9ac60ba58a018f9c33baa689761a1f7 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 19 Jun 2023 03:48:37 +0200 Subject: [PATCH 094/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/es.po | 96 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 43 deletions(-) diff --git a/i18n/es.po b/i18n/es.po index a033c7c00..57df0b0ff 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -52,7 +52,7 @@ msgid "Created {{time}} by {{author}}" msgstr "Creado {{time}} por {{author}}" msgid "Created {{time}}" -msgstr "" +msgstr "Creado {{time}}" msgid "Viewed {{count}} times" msgid_plural "Viewed {{count}} times" @@ -78,7 +78,7 @@ msgid "Subscribe" msgstr "Suscribirse" msgid "About this map" -msgstr "" +msgstr "Acerca de este mapa" msgid "About this line list" msgstr "" @@ -93,42 +93,46 @@ msgid "Network error" msgstr "Error de red" msgid "Data / Edit calculation" -msgstr "" +msgstr "Datos / Editar cálculo" msgid "Data / New calculation" -msgstr "" +msgstr "Datos / Nuevo cálculo" msgid "Remove item" -msgstr "" +msgstr "Quitar elemento" msgid "Check formula" -msgstr "" +msgstr "Comprobar fórmula" msgid "Calculation name" -msgstr "" +msgstr "Nombre del cálculo" msgid "Shown in table headers and chart axes/legends" msgstr "" +"Se muestra en los encabezados de las tablas y en los ejes/leyendas de los " +"gráficos" msgid "Delete calculation" -msgstr "" +msgstr "Eliminar cálculo" msgid "Cancel" msgstr "Cancelar" msgid "The calculation can only be saved with a valid formula" -msgstr "" +msgstr "El cálculo solo puede guardarse con una fórmula válida" msgid "Add a name to save this calculation" -msgstr "" +msgstr "Añadir un nombre para guardar este cálculo" msgid "Save calculation" -msgstr "" +msgstr "Guardar cálculo" msgid "" "Are you sure you want to delete this calculation? It may be used by other " "visualizations." msgstr "" +"¿Está seguro de que desea eliminar este cálculo? Puede ser utilizado por " +"otras visualizaciones." msgid "Yes, delete" msgstr "si, eliminar" @@ -146,7 +150,7 @@ msgid "Data elements" msgstr "Elementos de datos" msgid "Search by data element name" -msgstr "" +msgstr "Búsqueda por nombre de elemento de datos" msgid "No data elements found for \"{{- searchTerm}}\"" msgstr "No se han encontrado elementos de datos para \"{{- searchTerm}}\"" @@ -158,9 +162,11 @@ msgid "" "Drag items here, or double click in the list, to start building a " "calculation formula" msgstr "" +"Arrastre elementos aquí, o haga doble clic en la lista, para comenzar a " +"crear una fórmula de cálculo" msgid "Math operators" -msgstr "" +msgstr "Operadores matemáticos" msgid "Data Type" msgstr "Tipo de datos" @@ -187,7 +193,7 @@ msgid "No indicators found" msgstr "No se han encontrado indicadores" msgid "No data sets found" -msgstr "" +msgstr "No se han encontrado sets de datos" msgid "No event data items found" msgstr "" @@ -199,7 +205,7 @@ msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "No se han encontrado indicadores para \"{{- searchTerm}}\"" msgid "No data sets found for \"{{- searchTerm}}\"" -msgstr "" +msgstr "No se han encontrado sets de datos para \"{{- searchTerm}}\"" msgid "No event data items found for \"{{- searchTerm}}\"" msgstr "" @@ -211,7 +217,7 @@ msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "No se encontró nada para \"{{- searchTerm}}\"" msgid "Calculation" -msgstr "" +msgstr "Cálculo" msgid "Metric type" msgstr "Tipo de métrica" @@ -335,7 +341,7 @@ msgid "Rename" msgstr "Renombrar" msgid "{{- objectName}} (copy)" -msgstr "" +msgstr "{{- objectName}} (copia)" msgid "Save {{fileType}} as" msgstr "Guardar {{fileType}} como" @@ -371,7 +377,7 @@ msgid "Update" msgstr "Actualizar" msgid "Viewing interpretation: {{- visualisationName}}" -msgstr "" +msgstr "Visualización de la interpretación: {{- visualisationName}}" msgid "Could not load interpretation" msgstr "No se pudo cargar la interpretación" @@ -380,9 +386,11 @@ msgid "" "The interpretation couldn’t be displayed. Try again or contact your system " "administrator." msgstr "" +"No se ha podido mostrar la interpretación. Vuelva a intentarlo o póngase en " +"contacto con el administrador del sistema." msgid "Hide interpretation" -msgstr "" +msgstr "Ocultar interpretación" msgid "Write an interpretation" msgstr "Escribir una interpretación" @@ -406,46 +414,46 @@ msgid "Share" msgstr "Compartir" msgid "See interpretation" -msgstr "" +msgstr "Ver interpretación" msgid "Manage sharing" msgstr "Administrar compartir" msgid "Could not update interpretation" -msgstr "" +msgstr "No se pudo actualizar la interpretación" msgid "Enter interpretation text" -msgstr "" +msgstr "Introducir el texto de la interpretación" msgid "Bold text" -msgstr "" +msgstr "Texto en negrita" msgid "Italic text" -msgstr "" +msgstr "Texto en cursiva" msgid "Link to a URL" -msgstr "" +msgstr "Enlace a una URL" msgid "Mention a user" -msgstr "" +msgstr "Mencionar a un usuario" msgid "Add emoji" -msgstr "" +msgstr "Añadir emoji" msgid "Preview" -msgstr "" +msgstr "Vista previa" msgid "Back to write mode" -msgstr "" +msgstr "Volver al modo de escritura" msgid "Too many results. Try refining the search." -msgstr "" +msgstr "Demasiados resultados. Intenta refinar la búsqueda." msgid "Search for a user" -msgstr "" +msgstr "Buscar un usuario" msgid "Searching for \"{{- searchText}}\"" -msgstr "" +msgstr "Buscando \"{{- searchText}}\"" msgid "No results found" msgstr "No results found" @@ -892,7 +900,7 @@ msgid "Save translations" msgstr "Guardar traducciones" msgid "Cannot save while offline" -msgstr "" +msgstr "No se puede guardar sin conexión a internet" msgid "Could not load translations" msgstr "No se pudieron cargar las traducciones" @@ -976,10 +984,10 @@ msgid "Data set" msgstr "Set de Datos\\:" msgid "All data sets" -msgstr "" +msgstr "Todos los sets de datos" msgid "Loading data sets" -msgstr "" +msgstr "Cargando sets de datos" msgid "Event data items" msgstr "Datos de eventos" @@ -1003,31 +1011,33 @@ msgid "Program indicator" msgstr "Indicador de Programa" msgid "Calculations" -msgstr "" +msgstr "Cálculos" msgid "Number" msgstr "Número" msgid "Formula is empty. Add items to the formula from the lists on the left." msgstr "" +"La fórmula está vacía. Añada elementos a la fórmula desde las listas de la " +"izquierda." msgid "Consecutive math operators" -msgstr "" +msgstr "Operadores matemáticos consecutivos" msgid "Consecutive data elements" -msgstr "" +msgstr "Elementos de datos consecutivos" msgid "Starts or ends with a math operator" -msgstr "" +msgstr "Comienza o termina con un operador matemático" msgid "Empty parentheses" -msgstr "" +msgstr "Paréntesis vacíos" msgid "Missing right parenthesis )" -msgstr "" +msgstr "Falta el paréntesis derecho )" msgid "Missing left parenthesis (" -msgstr "" +msgstr "Falta el paréntesis izquierdo (" msgid "Extra Small" msgstr "Extra pequeño" @@ -1198,4 +1208,4 @@ msgstr[1] "" msgstr[2] "" msgid "Reset zoom" -msgstr "" +msgstr "Restablecer zoom" From eadebb237ae458c97f9f2441f060cc8a7d352b49 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 19 Jun 2023 02:01:38 +0000 Subject: [PATCH 095/285] chore(release): cut 25.1.22 [skip ci] ## [25.1.22](https://github.com/dhis2/analytics/compare/v25.1.21...v25.1.22) (2023-06-19) ### Bug Fixes * **translations:** sync translations from transifex (master) ([df3fa78](https://github.com/dhis2/analytics/commit/df3fa786b9ac60ba58a018f9c33baa689761a1f7)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 833702017..5ad02ce08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.1.22](https://github.com/dhis2/analytics/compare/v25.1.21...v25.1.22) (2023-06-19) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([df3fa78](https://github.com/dhis2/analytics/commit/df3fa786b9ac60ba58a018f9c33baa689761a1f7)) + ## [25.1.21](https://github.com/dhis2/analytics/compare/v25.1.20...v25.1.21) (2023-06-18) diff --git a/package.json b/package.json index 5c82277c4..a4db848d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.21", + "version": "25.1.22", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 552616368677aed228ef3f53eef30d02ec183d72 Mon Sep 17 00:00:00 2001 From: Hendrik de Graaf Date: Mon, 19 Jun 2023 15:32:11 +0200 Subject: [PATCH 096/285] feat: toolbar UI update with hoverable menu (#1478) BREAKING CHANGE The `FileMenu` is now using the new `HoverMenuBar` components which makes this version of the `FileMenu` incompatible with the previous version. Apps will be need to update their toolbar and file menu before using this version of analytics. --- .github/workflows/node-publish.yml | 2 +- .github/workflows/node-test.yml | 2 +- config/setupTestingLibrary.js | 5 + i18n/en.pot | 7 +- jest.config.js | 5 +- package.json | 2 + src/__demo__/FileMenu.stories.js | 29 +- src/__demo__/Toolbar.stories.js | 78 +++ src/components/FileMenu/FileMenu.js | 326 +++++------- .../FileMenu/__tests__/FileMenu.spec.js | 503 +++++++++--------- .../Options/VisualizationOptions.js | 10 +- .../Toolbar/HoverMenuBar/HoverMenuBar.js | 118 ++++ .../Toolbar/HoverMenuBar/HoverMenuDropdown.js | 49 ++ .../Toolbar/HoverMenuBar/HoverMenuList.js | 97 ++++ .../Toolbar/HoverMenuBar/HoverMenuListItem.js | 95 ++++ .../HoverMenuBar/HoverMenuListItem.styles.js | 91 ++++ .../__tests__/HoverMenuBar.spec.js | 256 +++++++++ .../__tests__/HoverMenuDropdown.spec.js | 20 + .../__tests__/HoverMenuList.spec.js | 56 ++ .../__tests__/HoverMenuListItem.spec.js | 39 ++ src/components/Toolbar/HoverMenuBar/index.js | 4 + .../InterpretationsAndDetailsToggler.js | 34 ++ src/components/Toolbar/MenuButton.styles.js | 37 ++ src/components/Toolbar/Toolbar.js | 29 + src/components/Toolbar/ToolbarSidebar.js | 31 ++ src/components/Toolbar/UpdateButton.js | 39 ++ .../InterpretationsAndDetailsToggler.spec.js | 50 ++ .../Toolbar/__tests__/Toolbar.spec.js | 18 + .../Toolbar/__tests__/ToolbarSidebar.spec.js | 23 + .../Toolbar/__tests__/UpdateButton.spec.js | 34 ++ src/components/Toolbar/index.js | 5 + src/index.js | 2 + yarn.lock | 202 ++++++- 33 files changed, 1841 insertions(+), 457 deletions(-) create mode 100644 config/setupTestingLibrary.js create mode 100644 src/__demo__/Toolbar.stories.js create mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuBar.js create mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js create mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuList.js create mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js create mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js create mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js create mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js create mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js create mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js create mode 100644 src/components/Toolbar/HoverMenuBar/index.js create mode 100644 src/components/Toolbar/InterpretationsAndDetailsToggler.js create mode 100644 src/components/Toolbar/MenuButton.styles.js create mode 100644 src/components/Toolbar/Toolbar.js create mode 100644 src/components/Toolbar/ToolbarSidebar.js create mode 100644 src/components/Toolbar/UpdateButton.js create mode 100644 src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js create mode 100644 src/components/Toolbar/__tests__/Toolbar.spec.js create mode 100644 src/components/Toolbar/__tests__/ToolbarSidebar.spec.js create mode 100644 src/components/Toolbar/__tests__/UpdateButton.spec.js create mode 100644 src/components/Toolbar/index.js diff --git a/.github/workflows/node-publish.yml b/.github/workflows/node-publish.yml index 5e04dd7ec..76aaddfaf 100644 --- a/.github/workflows/node-publish.yml +++ b/.github/workflows/node-publish.yml @@ -32,7 +32,7 @@ jobs: token: ${{env.GH_TOKEN}} - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 16.x - name: Install run: yarn install --frozen-lockfile diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 27930a8f7..efa5999b7 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 16.x - name: Install run: yarn install --frozen-lockfile diff --git a/config/setupTestingLibrary.js b/config/setupTestingLibrary.js new file mode 100644 index 000000000..446c378d5 --- /dev/null +++ b/config/setupTestingLibrary.js @@ -0,0 +1,5 @@ +import { configure } from '@testing-library/dom' + +configure({ + testIdAttribute: 'data-test', +}) diff --git a/i18n/en.pot b/i18n/en.pot index 549b42735..2ceaf2ec9 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: 2023-04-18T08:41:27.838Z\n" -"PO-Revision-Date: 2023-04-18T08:41:27.838Z\n" +"POT-Creation-Date: 2023-05-24T12:55:52.925Z\n" +"PO-Revision-Date: 2023-05-24T12:55:52.925Z\n" msgid "view only" msgstr "view only" @@ -856,6 +856,9 @@ msgstr "Financial Years" msgid "Years" msgstr "Years" +msgid "Interpretations and details" +msgstr "Interpretations and details" + msgid "Translating to" msgstr "Translating to" diff --git a/jest.config.js b/jest.config.js index 83db82633..59289738e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,7 @@ module.exports = { testPathIgnorePatterns: ['/node_modules/', '/build/'], - setupFilesAfterEnv: ['/config/setupEnzyme.js'], + setupFilesAfterEnv: [ + '/config/setupEnzyme.js', + '/config/setupTestingLibrary.js', + ], } diff --git a/package.json b/package.json index a4db848d2..f6b84fd20 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "@storybook/addons": "^6.5.9", "@storybook/preset-create-react-app": "^3.1.7", "@storybook/react": "^6.1.14", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^12.1.5", "enzyme": "^3.9.0", "enzyme-adapter-react-16": "^1.15.6", "fs-extra": "^10.1.0", diff --git a/src/__demo__/FileMenu.stories.js b/src/__demo__/FileMenu.stories.js index f8b821002..98092feac 100644 --- a/src/__demo__/FileMenu.stories.js +++ b/src/__demo__/FileMenu.stories.js @@ -2,6 +2,7 @@ import { Provider } from '@dhis2/app-runtime' import { storiesOf } from '@storybook/react' import React from 'react' import { FileMenu } from '../components/FileMenu/FileMenu.js' +import { HoverMenuBar } from '../components/Toolbar/index.js' const configMock = { baseUrl: 'http://localhost:8080', @@ -61,24 +62,30 @@ const visReadonlyObject = { storiesOf('FileMenu', module) .add('Simple', () => ( - + + + )) .add('With AO', () => ( - + + + )) .add('With readonly AO', () => ( - + + + )) diff --git a/src/__demo__/Toolbar.stories.js b/src/__demo__/Toolbar.stories.js new file mode 100644 index 000000000..14d50fb34 --- /dev/null +++ b/src/__demo__/Toolbar.stories.js @@ -0,0 +1,78 @@ +import { storiesOf } from '@storybook/react' +import React, { useState } from 'react' +import { + HoverMenuBar, + HoverMenuDropdown, + HoverMenuList, + HoverMenuListItem, + InterpretationsAndDetailsToggler, + Toolbar, + ToolbarSidebar, + UpdateButton, +} from '../components/Toolbar/index.js' + +function ToolbarWithState() { + const [isHidden, setIsHidden] = useState(false) + const [isSidebarShowing, setIsSidebarShowing] = useState(false) + return ( + + + Toolbar side bar + setIsHidden(true)} + > + click to hide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setIsSidebarShowing((current) => !current)} + /> + + ) +} + +storiesOf('Toolbar', module).add('default', () => { + return +}) diff --git a/src/components/FileMenu/FileMenu.js b/src/components/FileMenu/FileMenu.js index 588639a2d..bc0c3766a 100644 --- a/src/components/FileMenu/FileMenu.js +++ b/src/components/FileMenu/FileMenu.js @@ -9,19 +9,19 @@ import { IconDelete24, SharingDialog, colors, - FlyoutMenu, - Layer, - MenuItem, MenuDivider, - Popper, } from '@dhis2/ui' import PropTypes from 'prop-types' -import React, { createRef, useState } from 'react' +import React, { useState } from 'react' import i18n from '../../locales/index.js' import { OpenFileDialog } from '../OpenFileDialog/OpenFileDialog.js' +import { + HoverMenuListItem, + HoverMenuList, + HoverMenuDropdown, +} from '../Toolbar/index.js' import { TranslationDialog } from '../TranslationDialog/index.js' import { DeleteDialog } from './DeleteDialog.js' -import { fileMenuStyles } from './FileMenu.styles.js' import { GetLinkDialog } from './GetLinkDialog.js' import { RenameDialog } from './RenameDialog.js' import { SaveAsDialog } from './SaveAsDialog.js' @@ -43,21 +43,11 @@ export const FileMenu = ({ onError, onTranslate, }) => { - const [menuIsOpen, setMenuIsOpen] = useState(false) const [currentDialog, setCurrentDialog] = useState(null) - - // Escape key press closes the menu - const onKeyDown = (e) => { - if (e?.keyCode === 27) { - setMenuIsOpen(false) - } - } const onMenuItemClick = (dialogToOpen) => () => { - setMenuIsOpen(false) setCurrentDialog(dialogToOpen) } const onDialogClose = () => setCurrentDialog(null) - const toggleMenu = () => setMenuIsOpen(!menuIsOpen) const onDeleteConfirm = () => { // The dialog must be closed before calling the callback // otherwise the fileObject is changed to null before the @@ -67,8 +57,6 @@ export const FileMenu = ({ onDelete() } - const buttonRef = createRef() - const renderDialog = () => { switch (currentDialog) { case 'rename': @@ -138,17 +126,7 @@ export const FileMenu = ({ const iconInactiveColor = colors.grey500 return ( -
    - -
    - -
    + <> - {menuIsOpen && ( - - - - } - onClick={() => { - toggleMenu() - onNew() - }} - dataTest="file-menu-new" - /> - - } - onClick={onMenuItemClick('open')} - dataTest="file-menu-open" - /> - - } - disabled={ + + + } + onClick={onNew} + dataTest="file-menu-new" + /> + + } + onClick={onMenuItemClick('open')} + dataTest="file-menu-open" + /> + { - toggleMenu() - onSave() - } - : onMenuItemClick('saveas') - } - dataTest="file-menu-save" /> - + } + disabled={ + !onSave || + !(!fileObject?.id || fileObject?.access?.update) + } + onClick={ + fileObject?.id ? onSave : onMenuItemClick('saveas') + } + dataTest="file-menu-save" + /> + - - } - disabled={ - !( - fileObject?.id && - fileObject?.access?.update - ) + } + disabled={!(onSaveAs && fileObject?.id)} + onClick={onMenuItemClick('saveas')} + dataTest="file-menu-saveas" + /> + - - } - disabled={ - !( - fileObject?.id && - fileObject?.access?.update - ) + } + disabled={ + !(fileObject?.id && fileObject?.access?.update) + } + onClick={onMenuItemClick('rename')} + dataTest="file-menu-rename" + /> + - - - } - disabled={ - !( - fileObject?.id && - fileObject?.access?.manage - ) + } + disabled={ + !(fileObject?.id && fileObject?.access?.update) + } + onClick={onMenuItemClick('translate')} + dataTest="file-menu-translate" + /> + + - + } + disabled={ + !(fileObject?.id && fileObject?.access?.manage) + } + onClick={onMenuItemClick('sharing')} + dataTest="file-menu-sharing" + /> + - - - } - disabled={ - !( - fileObject?.id && - fileObject?.access?.delete - ) + } + disabled={!fileObject?.id} + onClick={onMenuItemClick('getlink')} + dataTest="file-menu-getlink" + /> + + - - - - )} + } + disabled={ + !(fileObject?.id && fileObject?.access?.delete) + } + onClick={onMenuItemClick('delete')} + dataTest="file-menu-delete" + /> + + {renderDialog()} -
    + ) } diff --git a/src/components/FileMenu/__tests__/FileMenu.spec.js b/src/components/FileMenu/__tests__/FileMenu.spec.js index 3e56fdacc..fefe5b5a3 100644 --- a/src/components/FileMenu/__tests__/FileMenu.spec.js +++ b/src/components/FileMenu/__tests__/FileMenu.spec.js @@ -1,18 +1,24 @@ -import { SharingDialog } from '@dhis2/ui' -import { shallow } from 'enzyme' +import { CustomDataProvider } from '@dhis2/app-runtime' +import { render, fireEvent, screen, getByText } from '@testing-library/react' +import '@testing-library/jest-dom' import React from 'react' -import { OpenFileDialog } from '../../OpenFileDialog/OpenFileDialog.js' -import { TranslationDialog } from '../../TranslationDialog/index.js' -import { DeleteDialog } from '../DeleteDialog.js' +import { HoverMenuBar } from '../../Toolbar/index.js' import { FileMenu } from '../FileMenu.js' -import { GetLinkDialog } from '../GetLinkDialog.js' -import { RenameDialog } from '../RenameDialog.js' -import { SaveAsDialog } from '../SaveAsDialog.js' -describe('The FileMenu component ', () => { - let shallowFileMenu - let props +jest.mock( + '../../TranslationDialog/TranslationModal/useTranslationsResults.js', + () => ({ + /* This will keep the translation dialog in + * a loading state, which prevents it from + * throwing other errors */ + useTranslationsResults: () => ({ + translationsData: undefined, + fetching: true, + }), + }) +) +describe('The FileMenu component ', () => { const onDelete = jest.fn() const onError = jest.fn() const onNew = jest.fn() @@ -23,308 +29,317 @@ describe('The FileMenu component ', () => { const onShare = jest.fn() const onTranslate = jest.fn() - const getFileMenuComponent = (props) => { - if (!shallowFileMenu) { - shallowFileMenu = shallow() - } - return shallowFileMenu + const baseProps = { + currentUser: { id: 'u1', displayName: 'Test user' }, + fileType: 'visualization', + fileObject: undefined, + onDelete, + onError, + onNew, + onOpen, + onRename, + onSave, + onSaveAs, + onShare, + onTranslate, } - beforeEach(() => { - shallowFileMenu = undefined - props = { - currentUser: { id: 'u1', displayName: 'Test user' }, - fileType: 'visualization', - fileObject: undefined, - onDelete, - onError, - onNew, - onOpen, - onRename, - onSave, - onSaveAs, - onShare, - onTranslate, - } - }) - - it('renders a button', () => { - expect(getFileMenuComponent(props).find('button')).toHaveLength(1) - }) - - it('renders some enabled buttons regardless of the access settings', () => { - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - const buttonLabels = ['New', 'Open…'] - - buttonLabels.forEach((buttonLabel) => - expect( - fileMenuComponent - .findWhere((n) => n.prop('label') === buttonLabel) - .prop('disabled') - ).toBe(undefined) - ) - }) - - it('renders some disabled buttons when no fileObject is present', () => { - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - const buttonLabels = [ - 'Save as…', - 'Rename…', - 'Translate…', - 'Share…', - 'Get link…', - 'Delete', - ] - - buttonLabels.forEach((buttonLabel) => - expect( - fileMenuComponent - .findWhere((n) => n.prop('label') === buttonLabel) - .prop('disabled') - ).toBe(true) - ) - }) - - it('renders some enabled buttons when update access is granted', () => { - props.fileObject = { + const fullAccessProps = { + fileObject: { id: 'test', access: { - delete: false, - manage: false, + delete: true, + manage: true, update: true, }, + href: 'http://dhis2.org', + }, + } + + const renderFileMenu = (customProps = {}) => { + const props = { ...baseProps, ...customProps } + const providerData = { + translations: { + translations: {}, + }, + sharing: { + meta: { + allowPublicAccess: true, + }, + object: { + userAccesses: [], + userGroupAccesses: [], + }, + }, } - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + return render( + + + + + + ) + } - const buttonLabels = ['Save', 'Rename…', 'Translate…'] + const openDropdown = async () => { + fireEvent.click(screen.getByTestId('dhis2-analytics-hovermenudropdown')) + expect(await screen.findByTestId('file-menu-container')).toBeVisible() + } - buttonLabels.forEach((buttonLabel) => - expect( - fileMenuComponent - .findWhere((n) => n.prop('label') === buttonLabel) - .prop('disabled') - ).toBe(false) - ) - }) + const MENU_ITEMS = { + NEW: { testId: 'file-menu-new', text: 'New' }, + OPEN: { testId: 'file-menu-open', text: 'Open…' }, + SAVE: { testId: 'file-menu-save', text: 'Save' }, + SAVE_AS: { testId: 'file-menu-saveas', text: 'Save as…' }, + RENAME: { testId: 'file-menu-rename', text: 'Rename…' }, + TRANSLATE: { testId: 'file-menu-translate', text: 'Translate…' }, + SHARE: { testId: 'file-menu-sharing', text: 'Share…' }, + GET_LINK: { testId: 'file-menu-getlink', text: 'Get link…' }, + DELETE: { testId: 'file-menu-delete', text: 'Delete' }, + } - it('renders enabled Delete button when delete access is granted', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: false, - update: false, - }, - } + const assertMenuItemsDisabledState = (menuItems) => { + for (const menuTitem of menuItems) { + const li = screen.getByTestId(menuTitem.testId) + expect(getByText(li, menuTitem.text)).toBeVisible() - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + if (menuTitem.disabled) { + expect(li).toHaveClass('disabled') + } else { + expect(li).not.toHaveClass('disabled') + } + } + } + it('renders a button', () => { + renderFileMenu() expect( - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Delete') - .prop('disabled') - ).toBe(false) - }) + screen.getAllByTestId('dhis2-analytics-hovermenudropdown') + ).toHaveLength(1) - it('renders enabled Share button when manage access is granted', () => { - props.fileObject = { - id: 'test', - access: { - delete: false, - manage: true, - update: false, - }, - } + const button = screen.getByTestId('dhis2-analytics-hovermenudropdown') + expect(button).toBeVisible() + expect(button).toHaveTextContent('File') + }) - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + it('opens when clicking the button', async () => { + renderFileMenu() expect( - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Share…') - .prop('disabled') - ).toBe(false) + screen.queryByTestId('file-menu-container') + ).not.toBeInTheDocument() + + await openDropdown() + + expect(await screen.findByTestId('file-menu-container')).toBeVisible() }) - it('renders the OpenFileDialog component when the Open button is clicked', () => { - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + it('renders some enabled buttons regardless of the access settings', async () => { + renderFileMenu() + await openDropdown() - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Open…') - .simulate('click') + assertMenuItemsDisabledState([ + { ...MENU_ITEMS.NEW, disabled: false }, + { ...MENU_ITEMS.OPEN, disabled: false }, + ]) + }) - expect(fileMenuComponent.find(OpenFileDialog)).toHaveLength(1) + it('renders some disabled buttons when no fileObject is present', async () => { + renderFileMenu() + await openDropdown() + + assertMenuItemsDisabledState([ + { ...MENU_ITEMS.SAVE_AS, disabled: true }, + { ...MENU_ITEMS.RENAME, disabled: true }, + { ...MENU_ITEMS.TRANSLATE, disabled: true }, + { ...MENU_ITEMS.SHARE, disabled: true }, + { ...MENU_ITEMS.GET_LINK, disabled: true }, + { ...MENU_ITEMS.DELETE, disabled: true }, + ]) }) - it('renders the RenameDialog when the Rename button is clicked', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, + it('renders some enabled buttons when update access is granted', async () => { + const customProps = { + fileObject: { + id: 'test', + access: { + delete: false, + manage: false, + update: true, + }, }, } - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + renderFileMenu(customProps) + await openDropdown() - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Rename…') - .simulate('click') - - expect(fileMenuComponent.find(RenameDialog)).toHaveLength(1) + assertMenuItemsDisabledState([ + { ...MENU_ITEMS.SAVE, disabled: false }, + { ...MENU_ITEMS.RENAME, disabled: false }, + { ...MENU_ITEMS.TRANSLATE, disabled: false }, + ]) }) - it('renders the TranslationDialog when the Translate button is clicked', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, + it('renders enabled Delete button when delete access is granted', async () => { + const customProps = { + fileObject: { + id: 'test', + access: { + delete: true, + manage: false, + update: false, + }, }, } - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Translate…') - .simulate('click') + renderFileMenu(customProps) + await openDropdown() - expect(fileMenuComponent.find(TranslationDialog)).toHaveLength(1) + assertMenuItemsDisabledState([ + { ...MENU_ITEMS.DELETE, disabled: false }, + ]) }) - it('renders the SharingDialog when the Share button is clicked', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, + it('renders enabled Share button when manage access is granted', async () => { + const customProps = { + fileObject: { + id: 'test', + access: { + delete: false, + manage: true, + update: false, + }, }, } - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + renderFileMenu(customProps) + await openDropdown() + + assertMenuItemsDisabledState([{ ...MENU_ITEMS.SHARE, disabled: false }]) + }) - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Share…') - .simulate('click') + it('renders the OpenFileDialog component when the Open button is clicked', async () => { + renderFileMenu() + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.OPEN.testId)) - expect(fileMenuComponent.find(SharingDialog)).toHaveLength(1) + expect( + await screen.findByText('Open a visualization', { selector: 'h1' }) + ).toBeVisible() }) - it('renders the GetLinkDialog when the Get link button is clicked', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, - }, - } + it('renders the RenameDialog when the Rename button is clicked', async () => { + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.RENAME.testId)) - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + expect( + await screen.findByText('Rename visualization', { selector: 'h1' }) + ).toBeVisible() + }) - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Get link…') - .simulate('click') + it('renders the TranslationDialog when the Translate button is clicked', async () => { + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.TRANSLATE.testId)) - expect(fileMenuComponent.find(GetLinkDialog)).toHaveLength(1) + expect( + await screen.findByText('Translate', { + exact: false, + selector: 'h1', + }) + ).toBeVisible() }) - it('renders the DeleteDialog when the Delete button is clicked', () => { - props.fileObject = { - id: 'delete-test', - access: { - delete: true, - manage: true, - update: true, - }, - } + it('renders the SharingDialog when the Share button is clicked', async () => { + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.SHARE.testId)) - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + expect( + await screen.findByText('Sharing and access', { selector: 'h1' }) + ).toBeVisible() + }) - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Delete') - .simulate('click') + it('renders the GetLinkDialog when the Get link button is clicked', async () => { + const url = 'http://localhost/dhis-web-data-visualizer/#/test' - expect(fileMenuComponent.find(DeleteDialog)).toHaveLength(1) + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.GET_LINK.testId)) + + expect(await screen.findByTestId('dhis2-uicore-modal')).toBeVisible() + expect(screen.getByRole('link', { name: url })).toHaveAttribute( + 'href', + url + ) }) - it('renders the SaveAsDialog when the Save as… button is clicked', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, + it('renders the DeleteDialog when the Delete button is clicked', async () => { + const customProps = { + fileObject: { + id: 'delete-test', + access: { + delete: true, + manage: true, + update: true, + }, }, } - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Save as…') - .simulate('click') + renderFileMenu(customProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.DELETE.testId)) - expect(fileMenuComponent.find(SaveAsDialog)).toHaveLength(1) + expect( + await screen.findByText('Delete visualization', { selector: 'h1' }) + ).toBeVisible() }) - it('renders the SaveAsDialog when the Save… button is clicked but no fileObject is present', () => { - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Save…') - .simulate('click') + it('renders the SaveAsDialog when the Save as… button is clicked', async () => { + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE_AS.testId)) - expect(fileMenuComponent.find(SaveAsDialog)).toHaveLength(1) + expect( + await screen.findByText('Save visualization as', { selector: 'h1' }) + ).toBeVisible() }) - it('calls the onSave callback when the Save button is clicked and a fileObject is present', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, + it('renders the SaveAsDialog when the Save… button is clicked but no fileObject is present', async () => { + const customProps = { + fileObject: { + // NOTE: no `id` field + access: { + update: true, + }, }, } + renderFileMenu(customProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE.testId)) - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + expect( + await screen.findByText('Save visualization as', { selector: 'h1' }) + ).toBeVisible() + }) - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Save') - .simulate('click') + it('calls the onSave callback when the Save button is clicked and a fileObject is present', async () => { + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE.testId)) - expect(fileMenuComponent.find(OpenFileDialog).prop('open')).toBe(false) - expect(onSave).toHaveBeenCalled() + expect(screen.queryByText('Open a visualization')).not.toBeVisible() + expect(onSave).toHaveBeenCalledTimes(1) }) - it('calls the onNew callback when the New button is clicked', () => { - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - fileMenuComponent - .findWhere((n) => n.prop('label') === 'New') - .simulate('click') + it('calls the onNew callback when the New button is clicked', async () => { + renderFileMenu() + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.NEW.testId)) - expect(fileMenuComponent.find(OpenFileDialog).prop('open')).toBe(false) - expect(onNew).toHaveBeenCalled() + expect(screen.queryByText('Open a visualization')).not.toBeVisible() + expect(onNew).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/Options/VisualizationOptions.js b/src/components/Options/VisualizationOptions.js index 7edc2c2f1..3bb3e3174 100644 --- a/src/components/Options/VisualizationOptions.js +++ b/src/components/Options/VisualizationOptions.js @@ -30,8 +30,13 @@ import { tabSectionOptionIcon, } from './styles/VisualizationOptions.style.js' -const VisualizationOptions = ({ optionsConfig, onClose, onUpdate }) => { - const [activeTabKey, setActiveTabKey] = useState() +const VisualizationOptions = ({ + initiallyActiveTabKey, + optionsConfig, + onClose, + onUpdate, +}) => { + const [activeTabKey, setActiveTabKey] = useState(initiallyActiveTabKey) const generateTabContent = (sections) => sections.map(({ key, label, content, helpText }) => ( @@ -144,6 +149,7 @@ const VisualizationOptions = ({ optionsConfig, onClose, onUpdate }) => { VisualizationOptions.propTypes = { optionsConfig: PropTypes.array.isRequired, + initiallyActiveTabKey: PropTypes.string, onClose: PropTypes.func, onUpdate: PropTypes.func, } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuBar.js b/src/components/Toolbar/HoverMenuBar/HoverMenuBar.js new file mode 100644 index 000000000..055694e78 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuBar.js @@ -0,0 +1,118 @@ +import PropTypes from 'prop-types' +import React, { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from 'react' + +const throwErrorIfNotInitialized = () => { + throw new Error('`HoverMenubarContext` has not been initialised') +} + +const HoverMenubarContext = createContext({ + closeMenu: throwErrorIfNotInitialized, + onDropDownButtonClick: throwErrorIfNotInitialized, + onDropDownButtonMouseOver: throwErrorIfNotInitialized, + setLastHoveredSubMenuEl: throwErrorIfNotInitialized, + openedDropdownEl: null, +}) + +const useHoverMenubarContext = () => useContext(HoverMenubarContext) + +const HoverMenuBar = ({ children, dataTest }) => { + const [openedDropdownEl, setOpenedDropdownEl] = useState(null) + const [lastHoveredSubMenuEl, setLastHoveredSubMenuEl] = useState(null) + const [isInHoverMode, setIsInHoverMode] = useState(false) + + const closeMenu = useCallback(() => { + setIsInHoverMode(false) + setOpenedDropdownEl(null) + }, []) + + const onDocumentClick = useCallback( + (event) => { + const isClickOnOpenedSubMenuAnchor = + lastHoveredSubMenuEl && + (lastHoveredSubMenuEl === event.target || + lastHoveredSubMenuEl.contains(event.target)) + + if (!isClickOnOpenedSubMenuAnchor) { + closeMenu() + } + }, + [closeMenu, lastHoveredSubMenuEl] + ) + + const onDropDownButtonClick = useCallback( + (event) => { + if (!isInHoverMode) { + setIsInHoverMode(true) + setOpenedDropdownEl(event.currentTarget) + } else { + closeMenu() + } + }, + [closeMenu, isInHoverMode] + ) + + const onDropDownButtonMouseOver = useCallback( + (event) => { + if (isInHoverMode) { + setOpenedDropdownEl(event.currentTarget) + } + }, + [isInHoverMode] + ) + + const closeMenuWithEsc = useCallback( + (event) => { + if (event.keyCode === 27) { + closeMenu() + } + }, + [closeMenu] + ) + + useEffect(() => { + if (isInHoverMode) { + document.addEventListener('click', onDocumentClick, { + once: true, + }) + } + + return () => { + document.removeEventListener('click', onDocumentClick) + } + }, [onDocumentClick, isInHoverMode]) + + return ( + +
    + {children} + +
    +
    + ) +} + +HoverMenuBar.defaultProps = { + dataTest: 'dhis2-analytics-hovermenubar', +} + +HoverMenuBar.propTypes = { + children: PropTypes.node.isRequired, + dataTest: PropTypes.string, +} +export { HoverMenuBar, useHoverMenubarContext } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js new file mode 100644 index 000000000..1664d1473 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js @@ -0,0 +1,49 @@ +import { Popper } from '@dhis2-ui/popper' +import { Portal } from '@dhis2-ui/portal' +import PropTypes from 'prop-types' +import React, { useRef } from 'react' +import menuButtonStyles from '../MenuButton.styles.js' +import { useHoverMenubarContext } from './HoverMenuBar.js' + +export const HoverMenuDropdown = ({ children, label, dataTest, disabled }) => { + const buttonRef = useRef() + const { + onDropDownButtonClick, + onDropDownButtonMouseOver, + openedDropdownEl, + } = useHoverMenubarContext() + const isOpen = openedDropdownEl === buttonRef.current + + return ( + <> + + {isOpen && ( + + + {children} + + + )} + + ) +} + +HoverMenuDropdown.defaultProps = { + dataTest: 'dhis2-analytics-hovermenudropdown', +} + +HoverMenuDropdown.propTypes = { + children: PropTypes.node.isRequired, + label: PropTypes.node.isRequired, + dataTest: PropTypes.string, + disabled: PropTypes.bool, +} diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuList.js b/src/components/Toolbar/HoverMenuBar/HoverMenuList.js new file mode 100644 index 000000000..a9ae02b84 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuList.js @@ -0,0 +1,97 @@ +import { colors, elevations, spacers } from '@dhis2/ui-constants' +import PropTypes from 'prop-types' +import React, { createContext, useCallback, useContext, useState } from 'react' +import { useHoverMenubarContext } from './HoverMenuBar.js' + +const throwErrorIfNotInitialized = () => { + throw new Error('`HoverMenuListContext` has not been initialised') +} + +const HoverMenuListContext = createContext({ + onSubmenuAnchorMouseEnter: throwErrorIfNotInitialized, + onMenuItemMouseEnter: throwErrorIfNotInitialized, + openedSubMenuEl: null, + dense: false, +}) + +const useHoverMenuListContext = () => useContext(HoverMenuListContext) + +const HoverMenuList = ({ + children, + className, + dataTest, + dense, + maxHeight, + maxWidth, +}) => { + const { setLastHoveredSubMenuEl } = useHoverMenubarContext() + const [openedSubMenuEl, setOpenedSubMenuEl] = useState(null) + + const onSubmenuAnchorMouseEnter = useCallback( + (event) => { + if (openedSubMenuEl !== event.currentTarget) { + setOpenedSubMenuEl(event.currentTarget) + setLastHoveredSubMenuEl(event.currentTarget) + } + }, + [openedSubMenuEl, setLastHoveredSubMenuEl] + ) + + const onMenuItemMouseEnter = useCallback(() => { + setOpenedSubMenuEl(null) + setLastHoveredSubMenuEl(null) + }, [setLastHoveredSubMenuEl]) + + return ( + +
      + {children} + +
    +
    + ) +} + +HoverMenuList.defaultProps = { + dataTest: 'dhis2-analytics-hovermenulist', + maxWidth: '380px', + maxHeight: 'auto', +} + +HoverMenuList.propTypes = { + /** Typically `MenuItem`, `MenuDivider`, and `MenuSectionHeader` */ + children: PropTypes.node, + className: PropTypes.string, + dataTest: PropTypes.string, + /** Gives all HoverMenuListItem children a dense style */ + dense: PropTypes.bool, + maxHeight: PropTypes.string, + maxWidth: PropTypes.string, +} + +export { HoverMenuList, useHoverMenuListContext } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js new file mode 100644 index 000000000..a99bd4cc5 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js @@ -0,0 +1,95 @@ +import { Popper } from '@dhis2-ui/popper' +import { Portal } from '@dhis2-ui/portal' +import { IconChevronRight24 } from '@dhis2/ui-icons' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React, { useRef } from 'react' +import { HoverMenuList, useHoverMenuListContext } from './HoverMenuList.js' +import styles from './HoverMenuListItem.styles.js' + +const HoverMenuListItem = ({ + onClick, + children, + icon, + className, + destructive, + disabled, + dataTest, + label, +}) => { + const ref = useRef() + const { + onSubmenuAnchorMouseEnter, + onMenuItemMouseEnter, + openedSubMenuEl, + dense, + } = useHoverMenuListContext() + + const isSubMenuOpen = openedSubMenuEl === ref.current + + return ( + <> +
  • + {icon && {icon}} + + {label} + + {!!children && ( + + + + )} + + +
  • + {children && isSubMenuOpen && ( + + + {children} + + + )} + + ) +} + +HoverMenuListItem.defaultProps = { + dataTest: 'dhis2-uicore-hovermenulistitem', +} + +HoverMenuListItem.propTypes = { + // Nested menu items become submenus + children: PropTypes.node, + className: PropTypes.string, + dataTest: PropTypes.string, + destructive: PropTypes.bool, + disabled: PropTypes.bool, + /** An icon for the left side of the menu item */ + icon: PropTypes.node, + /** Text in the menu item */ + label: PropTypes.node, + /** Click handler */ + onClick: PropTypes.func, +} + +export { HoverMenuListItem } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js new file mode 100644 index 000000000..31748d2a5 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js @@ -0,0 +1,91 @@ +import { colors, spacers } from '@dhis2/ui-constants' +import css from 'styled-jsx/css' + +export default css` + li { + display: flex; + align-items: center; + padding: 0px ${spacers.dp24}; + cursor: pointer; + list-style: none; + background-color: ${colors.white}; + color: ${colors.grey900}; + fill: ${colors.grey900}; + font-size: 14px; + line-height: 16px; + user-select: none; + } + + li:hover { + background-color: ${colors.grey200}; + } + + li:active, + li.active { + background-color: ${colors.grey300}; + } + + li.destructive { + color: ${colors.red700}; + fill: ${colors.red600}; + } + + li.destructive:hover { + background-color: ${colors.red050}; + } + + li.destructive:active, + li.destructive.active { + background-color: ${colors.red100}; + } + + li.disabled { + cursor: not-allowed; + color: ${colors.grey500}; + fill: ${colors.grey500}; + } + + li.disabled:hover { + background-color: ${colors.white}; + } + + .label { + flex-grow: 1; + padding: ${spacers.dp12} 0; + } + + li.dense .label { + padding: ${spacers.dp8} 0; + } + + .icon { + flex-grow: 0; + margin-right: ${spacers.dp12}; + width: 24px; + height: 24px; + } + + .chevron { + display: flex; + align-items: center; + flex-grow: 0; + margin-left: ${spacers.dp24}; + } + + li.dense .icon { + margin-right: ${spacers.dp8}; + width: 16px; + height: 16px; + } + + li .icon > :global(svg) { + width: 24px; + height: 24px; + } + + li.dense .icon > :global(svg), + li .chevron > :global(svg) { + width: 16px; + height: 16px; + } +` diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js new file mode 100644 index 000000000..9ae361ca4 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js @@ -0,0 +1,256 @@ +import '@testing-library/jest-dom' +import { render, fireEvent, screen } from '@testing-library/react' +import { shallow } from 'enzyme' +import React from 'react' +import { + HoverMenuBar, + HoverMenuDropdown, + HoverMenuList, + HoverMenuListItem, +} from '../index.js' + +describe('', () => { + it('renders children', () => { + const childNode = 'text node' + const wrapper = shallow({childNode}) + + expect(wrapper.containsMatchingElement(childNode)).toBe(true) + }) + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow( + children + ) + + expect(wrapper.find('div').prop('data-test')).toBe(dataTest) + }) + + describe('mouse interactions', () => { + it('does not open on hover before a dropdown anchor is clicked', async () => { + createFullMenuBarWrapper() + fireEvent.mouseOver(screen.getByText('Menu A')) + + await expectMenuItemsInDocument([ + ['Menu item A.1', false], + ['Menu item A.2', false], + ['Menu item A.3', false], + ]) + }) + it('does not open when a disabled dropdown anchor is clicked', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu C')) + + await expectMenuItemsInDocument([ + ['Menu item A.1', false], + ['Menu item A.2', false], + ['Menu item A.3', false], + ]) + }) + it('opens menu list when clicked', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu A')) + + await expectMenuItemsInDocument([ + ['Menu item A.1', true], + ['Menu item B.1', false], + ['Menu item C.1', false], + ]) + }) + it('responds to hover once open', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu A')) + fireEvent.mouseOver(screen.getByText('Menu B')) + + await expectMenuItemsInDocument([ + ['Menu item A.1', false], + ['Menu item B.1', true], + ['Menu item C.1', false], + ]) + }) + it('does not open disabled dropdown on hover in hover mode', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu B')) + fireEvent.mouseOver(screen.getByText('Menu C')) + + await expectMenuItemsInDocument([ + ['Menu item B.1', true], + ['Menu item C.1', false], + ]) + }) + it('opens submenus when in hover mode', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu B')) + fireEvent.mouseOver(screen.getByText('Menu item B.1')) + + await expectMenuItemsInDocument([ + ['Menu item B.1.1', true], + ['Menu item B.1.2', true], + ['Menu item B.1.3', true], + ['Menu item B.2.1', false], + ['Menu item B.2.2', false], + ['Menu item B.2.3', false], + ]) + + fireEvent.mouseOver(screen.getByText('Menu item B.2')) + + await expectMenuItemsInDocument([ + ['Menu item B.1.1', false], + ['Menu item B.1.2', false], + ['Menu item B.1.3', false], + ['Menu item B.2.1', true], + ['Menu item B.2.2', true], + ['Menu item B.2.3', true], + ]) + }) + it('does not open disabled submenus when in hover mode', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu B')) + fireEvent.mouseOver(screen.getByText('Menu item B.2')) + + await expectMenuItemsInDocument([ + ['Menu item B.2.1', true], + ['Menu item B.2.2', true], + ['Menu item B.2.3', true], + ['Menu item B.3.1', false], + ['Menu item B.3.2', false], + ['Menu item B.3.3', false], + ]) + + fireEvent.mouseOver(screen.getByText('Menu item B.3')) + + await expectMenuItemsInDocument([ + ['Menu item B.2.1', true], + ['Menu item B.2.2', true], + ['Menu item B.2.3', true], + ['Menu item B.3.1', false], + ['Menu item B.3.2', false], + ['Menu item B.3.3', false], + ]) + }) + it('closes when clicking on then document', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu A')) + + await expectMenuItemsInDocument([['Menu item A.1', true]]) + + fireEvent.click(document) + + await expectMenuItemsInDocument([['Menu item A.1', false]]) + }) + it('stays open when clicking a open submenu anchor', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu B')) + + await expectMenuItemsInDocument([['Menu item B.1', true]]) + + fireEvent.mouseOver(screen.getByText('Menu item B.1')) + + await expectMenuItemsInDocument([ + ['Menu item B.1', true], + ['Menu item B.1.1', true], + ['Menu item B.1.2', true], + ['Menu item B.1.3', true], + ]) + + fireEvent.click(screen.getByText('Menu item B.1')) + + await expectMenuItemsInDocument([ + ['Menu item B.1', true], + ['Menu item B.1.1', true], + ['Menu item B.1.2', true], + ['Menu item B.1.3', true], + ]) + }) + it('calls the onClick of the menu item and closes when clicking a menu item', async () => { + const menuItemOnClickSpy = jest.fn() + createFullMenuBarWrapper({ menuItemOnClickSpy }) + fireEvent.click(screen.getByText('Menu A')) + + await expectMenuItemsInDocument([['Menu item A.1', true]]) + + fireEvent.click(screen.getByText('Menu item A.1')) + + expect(menuItemOnClickSpy).toHaveBeenCalledTimes(1) + await expectMenuItemsInDocument([['Menu item A.1', false]]) + }) + + it('calls the onClick of the menu item and closes when clicking a submenu item', async () => { + const subMenuItemOnClickSpy = jest.fn() + createFullMenuBarWrapper({ subMenuItemOnClickSpy }) + + fireEvent.click(screen.getByText('Menu B')) + await expectMenuItemsInDocument([['Menu item B.1', true]]) + + fireEvent.mouseOver(screen.getByText('Menu item B.1')) + await expectMenuItemsInDocument([['Menu item B.1.1', true]]) + + fireEvent.click(screen.getByText('Menu item B.1.1')) + + expect(subMenuItemOnClickSpy).toHaveBeenCalledTimes(1) + await expectMenuItemsInDocument([ + ['Menu item B.1', false], + ['Menu item B.1.1', false], + ['Menu item B.1.1', false], + ]) + }) + }) +}) + +function createFullMenuBarWrapper({ + menuItemOnClickSpy, + subMenuItemOnClickSpy, +} = {}) { + return render( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +async function expectMenuItemsInDocument(items) { + for (const [text, inDocument] of items) { + if (inDocument) { + expect(await screen.findByText(text)).toBeInTheDocument() + } else { + expect(screen.queryByText(text)).not.toBeInTheDocument() + } + } +} diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js new file mode 100644 index 000000000..fa050d1f4 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js @@ -0,0 +1,20 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { HoverMenuDropdown } from '../index.js' + +describe('', () => { + /* Most of the props for this component are included + * in the mouse interaction tests for the HoverMenuBar. + * Only the `dataTest` prop needs to be verified here. */ + + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow( + + children + + ) + + expect(wrapper.find('button').prop('data-test')).toBe(dataTest) + }) +}) diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js new file mode 100644 index 000000000..9b79d9222 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js @@ -0,0 +1,56 @@ +import { shallow, mount } from 'enzyme' +import React from 'react' +import { HoverMenuList, HoverMenuListItem } from '../index.js' + +describe('', () => { + const dataTest = 'test' + const childNode = 'children' + + it('renders children', () => { + const wrapper = shallow({childNode}) + expect(wrapper.containsMatchingElement(childNode)).toBe(true) + }) + it('accept a `className` prop', () => { + const className = 'className' + const wrapper = shallow( + {childNode} + ) + expect(wrapper.find('ul')).toHaveClassName(className) + }) + + it('accepts a `dataTest` prop', () => { + const wrapper = shallow( + {childNode} + ) + + expect(wrapper.find('ul').prop('data-test')).toBe(dataTest) + }) + + it('accept a `dense` prop', () => { + const wrapper = mount( + + + + + ) + + expect(wrapper.find('li').first()).toHaveClassName('dense') + expect(wrapper.find('li').last()).toHaveClassName('dense') + }) + it('accept a `maxHeight` prop', () => { + const maxHeight = '100000px' + const wrapper = shallow( + {childNode} + ) + expect(wrapper.find('style').text()).toContain( + `max-height: ${maxHeight}` + ) + }) + it('accept a `maxWidth` prop', () => { + const maxWidth = '100000px' + const wrapper = shallow( + {childNode} + ) + expect(wrapper.find('style').text()).toContain(`max-width: ${maxWidth}`) + }) +}) diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js new file mode 100644 index 000000000..4c2503619 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js @@ -0,0 +1,39 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { HoverMenuListItem } from '../index.js' + +describe('', () => { + /* Some of the props for this component are included + * in the mouse interaction tests for the HoverMenuBar. + * Only the `className`, `dataTest`, `destructive` and + * `icon` prop need to be verified here. */ + + it('accepts a `className` prop', () => { + const className = 'className' + const wrapper = shallow() + + expect(wrapper.find('li')).toHaveClassName(className) + }) + + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow() + + expect(wrapper.find('li').prop('data-test')).toBe(dataTest) + }) + + it('accepts a `destructive` prop', () => { + const wrapper = shallow() + + expect(wrapper.find('li')).toHaveClassName('destructive') + }) + it('accepts an `icon` prop', () => { + const iconText = 'I am an icon' + const icon = {iconText} + const wrapper = shallow() + + expect(wrapper.find('span.icon')).toExist() + expect(wrapper.find('span#testicon')).toExist() + expect(wrapper.find('span#testicon').text()).toBe(iconText) + }) +}) diff --git a/src/components/Toolbar/HoverMenuBar/index.js b/src/components/Toolbar/HoverMenuBar/index.js new file mode 100644 index 000000000..8fb29dba3 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/index.js @@ -0,0 +1,4 @@ +export { HoverMenuBar } from './HoverMenuBar.js' +export { HoverMenuDropdown } from './HoverMenuDropdown.js' +export { HoverMenuList } from './HoverMenuList.js' +export { HoverMenuListItem } from './HoverMenuListItem.js' diff --git a/src/components/Toolbar/InterpretationsAndDetailsToggler.js b/src/components/Toolbar/InterpretationsAndDetailsToggler.js new file mode 100644 index 000000000..8bb8abeb3 --- /dev/null +++ b/src/components/Toolbar/InterpretationsAndDetailsToggler.js @@ -0,0 +1,34 @@ +import i18n from '@dhis2/d2-i18n' +import { IconChevronRight24, IconChevronLeft24 } from '@dhis2/ui' +import PropTypes from 'prop-types' +import React from 'react' +import menuButtonStyles from './MenuButton.styles.js' + +export const InterpretationsAndDetailsToggler = ({ + onClick, + dataTest, + disabled, + isShowing, +}) => ( + +) + +InterpretationsAndDetailsToggler.defaultProps = { + dataTest: 'dhis2-analytics-interpretationsanddetailstoggler', +} + +InterpretationsAndDetailsToggler.propTypes = { + onClick: PropTypes.func.isRequired, + dataTest: PropTypes.string, + disabled: PropTypes.bool, + isShowing: PropTypes.bool, +} diff --git a/src/components/Toolbar/MenuButton.styles.js b/src/components/Toolbar/MenuButton.styles.js new file mode 100644 index 000000000..7522ca1d7 --- /dev/null +++ b/src/components/Toolbar/MenuButton.styles.js @@ -0,0 +1,37 @@ +import { colors, spacers, theme } from '@dhis2/ui-constants' +import css from 'styled-jsx/css' + +export default css` + button { + all: unset; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 14px; + line-height: 14px; + padding: 0 ${spacers.dp12}; + color: ${colors.grey900}; + transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + cursor: pointer; + } + + button:hover:enabled, + button:active { + background-color: ${colors.grey200}; + } + + button:focus { + outline: 3px solid ${theme.focus}; + outline-offset: -3px; + } + + /* Prevent focus styles when mouse clicking */ + button:focus:not(:focus-visible) { + outline: none; + } + + button:disabled { + color: ${colors.grey500}; + cursor: not-allowed; + } +` diff --git a/src/components/Toolbar/Toolbar.js b/src/components/Toolbar/Toolbar.js new file mode 100644 index 000000000..f6dcce5e7 --- /dev/null +++ b/src/components/Toolbar/Toolbar.js @@ -0,0 +1,29 @@ +import { colors } from '@dhis2/ui-constants' +import PropTypes from 'prop-types' +import React from 'react' + +export const Toolbar = ({ children, dataTest }) => ( +
    + {children} + +
    +) + +Toolbar.defaultProps = { + dataTest: 'dhis2-analytics-toolbar', +} + +Toolbar.propTypes = { + children: PropTypes.node, + dataTest: PropTypes.string, +} diff --git a/src/components/Toolbar/ToolbarSidebar.js b/src/components/Toolbar/ToolbarSidebar.js new file mode 100644 index 000000000..948a9fbed --- /dev/null +++ b/src/components/Toolbar/ToolbarSidebar.js @@ -0,0 +1,31 @@ +import { colors } from '@dhis2/ui-constants' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React from 'react' + +export const ToolbarSidebar = ({ children, dataTest, isHidden }) => ( +
    + {children} + +
    +) + +ToolbarSidebar.defaultProps = { + dataTest: 'dhis2-analytics-toolbarsidebar', +} + +ToolbarSidebar.propTypes = { + children: PropTypes.node, + dataTest: PropTypes.string, + isHidden: PropTypes.bool, +} diff --git a/src/components/Toolbar/UpdateButton.js b/src/components/Toolbar/UpdateButton.js new file mode 100644 index 000000000..fdfaa06e7 --- /dev/null +++ b/src/components/Toolbar/UpdateButton.js @@ -0,0 +1,39 @@ +import { CircularLoader } from '@dhis2-ui/loader' +import i18n from '@dhis2/d2-i18n' +import { colors } from '@dhis2/ui-constants' +import { IconSync16 } from '@dhis2/ui-icons' +import PropTypes from 'prop-types' +import React from 'react' +import menuButtonStyles from './MenuButton.styles.js' + +export const UpdateButton = ({ onClick, disabled, loading, dataTest }) => ( + +) + +UpdateButton.defaultProps = { + dataTest: 'dhis2-analytics-updatebutton', +} + +UpdateButton.propTypes = { + onClick: PropTypes.func.isRequired, + dataTest: PropTypes.string, + disabled: PropTypes.bool, + loading: PropTypes.bool, +} diff --git a/src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js b/src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js new file mode 100644 index 000000000..4477e19d8 --- /dev/null +++ b/src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js @@ -0,0 +1,50 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { InterpretationsAndDetailsToggler } from '../index.js' + +describe('', () => { + const noop = () => {} + + it('accepts an `onClick` prop', () => { + const onClick = jest.fn() + const wrapper = shallow( + + ) + + wrapper.simulate('click') + + expect(onClick).toHaveBeenCalledTimes(1) + }) + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow( + + ) + + expect(wrapper.prop('data-test')).toBe(dataTest) + }) + it('accepts a `disabled` prop', () => { + const wrapper = shallow( + + ) + + expect(wrapper.find('button').prop('disabled')).toEqual(true) + }) + it('accepts an `isShowing` prop', () => { + const wrapper = shallow( + + ) + const wrapperWithIsShowing = shallow( + + ) + + expect(wrapper.find('SvgChevronRight24')).toHaveLength(0) + expect(wrapper.find('SvgChevronLeft24')).toHaveLength(1) + + expect(wrapperWithIsShowing.find('SvgChevronRight24')).toHaveLength(1) + expect(wrapperWithIsShowing.find('SvgChevronLeft24')).toHaveLength(0) + }) +}) diff --git a/src/components/Toolbar/__tests__/Toolbar.spec.js b/src/components/Toolbar/__tests__/Toolbar.spec.js new file mode 100644 index 000000000..bfd65785a --- /dev/null +++ b/src/components/Toolbar/__tests__/Toolbar.spec.js @@ -0,0 +1,18 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { Toolbar } from '../index.js' + +describe('', () => { + it('renders children', () => { + const childNode = 'text node' + const wrapper = shallow({childNode}) + + expect(wrapper.containsMatchingElement(childNode)).toBe(true) + }) + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow() + + expect(wrapper.prop('data-test')).toBe(dataTest) + }) +}) diff --git a/src/components/Toolbar/__tests__/ToolbarSidebar.spec.js b/src/components/Toolbar/__tests__/ToolbarSidebar.spec.js new file mode 100644 index 000000000..7801ec550 --- /dev/null +++ b/src/components/Toolbar/__tests__/ToolbarSidebar.spec.js @@ -0,0 +1,23 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { ToolbarSidebar } from '../index.js' + +describe('', () => { + it('renders children', () => { + const childNode = 'text node' + const wrapper = shallow({childNode}) + + expect(wrapper.containsMatchingElement(childNode)).toBe(true) + }) + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow() + + expect(wrapper.prop('data-test')).toBe(dataTest) + }) + it('accepts a `isHidden` prop', () => { + const wrapper = shallow() + + expect(wrapper.find('div').hasClass('isHidden')).toEqual(true) + }) +}) diff --git a/src/components/Toolbar/__tests__/UpdateButton.spec.js b/src/components/Toolbar/__tests__/UpdateButton.spec.js new file mode 100644 index 000000000..3be73c6b4 --- /dev/null +++ b/src/components/Toolbar/__tests__/UpdateButton.spec.js @@ -0,0 +1,34 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { UpdateButton } from '../index.js' + +describe('', () => { + const noop = () => {} + + it('accepts an `onClick` prop', () => { + const onClick = jest.fn() + const wrapper = shallow() + + wrapper.simulate('click') + + expect(onClick).toHaveBeenCalledTimes(1) + }) + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow( + + ) + + expect(wrapper.prop('data-test')).toBe(dataTest) + }) + it('accepts a `disabled` prop', () => { + const wrapper = shallow() + + expect(wrapper.find('button').prop('disabled')).toEqual(true) + }) + it('accepts an `loading` prop', () => { + const wrapper = shallow() + + expect(wrapper.find('CircularLoader')).toHaveLength(1) + }) +}) diff --git a/src/components/Toolbar/index.js b/src/components/Toolbar/index.js new file mode 100644 index 000000000..86d6df30a --- /dev/null +++ b/src/components/Toolbar/index.js @@ -0,0 +1,5 @@ +export { InterpretationsAndDetailsToggler } from './InterpretationsAndDetailsToggler.js' +export { Toolbar } from './Toolbar.js' +export { ToolbarSidebar } from './ToolbarSidebar.js' +export { UpdateButton } from './UpdateButton.js' +export * from './HoverMenuBar/index.js' diff --git a/src/index.js b/src/index.js index e21a38df7..069656b77 100644 --- a/src/index.js +++ b/src/index.js @@ -32,6 +32,8 @@ export { default as AboutAOUnit } from './components/AboutAOUnit/AboutAOUnit.js' export { InterpretationsUnit } from './components/Interpretations/InterpretationsUnit/InterpretationsUnit.js' export { InterpretationModal } from './components/Interpretations/InterpretationModal/InterpretationModal.js' +export * from './components/Toolbar/index.js' + export { TranslationDialog } from './components/TranslationDialog/index.js' export { OfflineTooltip } from './components/OfflineTooltip.js' diff --git a/yarn.lock b/yarn.lock index 77e2352c9..496096711 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.0.1": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.2.0.tgz#e1a84fca468f4b337816fcb7f0964beb620ba855" + integrity sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA== + "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -2658,6 +2663,13 @@ "@types/node" "*" jest-mock "^27.5.1" +"@jest/expect-utils@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" + integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== + dependencies: + jest-get-type "^29.4.3" + "@jest/fake-timers@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" @@ -2772,6 +2784,13 @@ terminal-link "^2.0.0" v8-to-istanbul "^8.1.0" +"@jest/schemas@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" + integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== + dependencies: + "@sinclair/typebox" "^0.25.16" + "@jest/source-map@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" @@ -2944,6 +2963,18 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jest/types@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" + integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== + dependencies: + "@jest/schemas" "^29.4.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -3157,6 +3188,11 @@ "@storybook/react" "^5.3.3" uuid "^3.1.0" +"@sinclair/typebox@^0.25.16": + version "0.25.24" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" + integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -4291,6 +4327,21 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/jest-dom@^5.16.5": + version "5.16.5" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" + integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== + dependencies: + "@adobe/css-tools" "^4.0.1" + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + "@testing-library/react@^12.1.2": version "12.1.2" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.2.tgz#f1bc9a45943461fa2a598bb4597df1ae044cfc76" @@ -4299,6 +4350,15 @@ "@babel/runtime" "^7.12.5" "@testing-library/dom" "^8.0.0" +"@testing-library/react@^12.1.5": + version "12.1.5" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" + integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^8.0.0" + "@types/react-dom" "<18.0.0" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -4460,6 +4520,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@*": + version "29.5.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.1.tgz#83c818aa9a87da27d6da85d3378e5a34d2f31a47" + integrity sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -4568,6 +4636,13 @@ "@types/react" "*" "@types/reactcss" "*" +"@types/react-dom@<18.0.0": + version "17.0.20" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.20.tgz#e0c8901469d732b36d8473b40b679ad899da1b53" + integrity sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA== + dependencies: + "@types/react" "^17" + "@types/react-syntax-highlighter@11.0.4": version "11.0.4" resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz#d86d17697db62f98046874f62fdb3e53a0bbc4cd" @@ -4590,6 +4665,15 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/react@^17": + version "17.0.60" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.60.tgz#a4a97dcdbebad76612c188fc06440e4995fd8ad2" + integrity sha512-pCH7bqWIfzHs3D+PDs3O/COCQJka+Kcw3RnO9rFA2zalqoXg7cNjJDh6mZ7oRtY1wmY4LVwDdAbA1F7Z8tv3BQ== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/reactcss@*": version "1.2.3" resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834" @@ -4611,6 +4695,11 @@ dependencies: "@types/node" "*" +"@types/scheduler@*": + version "0.16.3" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" + integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -4631,6 +4720,13 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA== +"@types/testing-library__jest-dom@^5.9.1": + version "5.14.6" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.6.tgz#4887f6e1af11215428ab02777873bcede98a53b0" + integrity sha512-FkHXCb+ikSoUP4Y4rOslzTdX5sqYwMxfefKh1GmZ8ce1GOkEHntSp6b5cGadmNfp5e4BMEWOMx+WSKd5/MqlDA== + dependencies: + "@types/jest" "*" + "@types/trusted-types@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" @@ -4700,6 +4796,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.8": + version "17.0.24" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" + integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@^4.5.0": version "4.13.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.13.0.tgz#5f580ea520fa46442deb82c038460c3dd3524bb6" @@ -7863,6 +7966,11 @@ css-what@^3.2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + css@^2.0.0: version "2.2.4" resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" @@ -7992,6 +8100,11 @@ csstype@^2.2.0, csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== +csstype@^3.0.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -8284,6 +8397,11 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== +diff-sequences@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" + integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== + diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -8367,6 +8485,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.5.6: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + dom-accessibility-api@^0.5.9: version "0.5.11" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.11.tgz#79d5846c4f90eba3e617d9031e921de9324f84ed" @@ -9347,6 +9470,17 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" +expect@^29.0.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" + integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== + dependencies: + "@jest/expect-utils" "^29.5.0" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-util "^29.5.0" + express@^4.17.0, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -11943,6 +12077,16 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-diff@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" + integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" + jest-docblock@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" @@ -12067,6 +12211,11 @@ jest-get-type@^27.5.1: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== +jest-get-type@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" + integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== + jest-haste-map@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" @@ -12210,6 +12359,16 @@ jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-matcher-utils@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" + integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== + dependencies: + chalk "^4.0.0" + jest-diff "^29.5.0" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" + jest-message-util@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" @@ -12254,6 +12413,21 @@ jest-message-util@^27.5.1: slash "^3.0.0" stack-utils "^2.0.3" +jest-message-util@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" + integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.5.0" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.5.0" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-mock@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" @@ -12586,6 +12760,18 @@ jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-util@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" + integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== + dependencies: + "@jest/types" "^29.5.0" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" @@ -15722,6 +15908,15 @@ pretty-format@^27.0.2, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^29.0.0, pretty-format@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" + integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== + dependencies: + "@jest/schemas" "^29.4.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -16279,6 +16474,11 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -19362,10 +19562,8 @@ watchpack@^1.7.4: resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: - chokidar "^3.4.1" graceful-fs "^4.1.2" neo-async "^2.5.0" - watchpack-chokidar2 "^2.0.1" optionalDependencies: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" From fe44753ab27303e50b812b2dcc8484930ec74cd2 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 19 Jun 2023 13:38:47 +0000 Subject: [PATCH 097/285] chore(release): cut 25.2.0 [skip ci] # [25.2.0](https://github.com/dhis2/analytics/compare/v25.1.22...v25.2.0) (2023-06-19) ### Features * toolbar UI update with hoverable menu ([#1478](https://github.com/dhis2/analytics/issues/1478)) ([5526163](https://github.com/dhis2/analytics/commit/552616368677aed228ef3f53eef30d02ec183d72)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ad02ce08..b7bea7d84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [25.2.0](https://github.com/dhis2/analytics/compare/v25.1.22...v25.2.0) (2023-06-19) + + +### Features + +* toolbar UI update with hoverable menu ([#1478](https://github.com/dhis2/analytics/issues/1478)) ([5526163](https://github.com/dhis2/analytics/commit/552616368677aed228ef3f53eef30d02ec183d72)) + ## [25.1.22](https://github.com/dhis2/analytics/compare/v25.1.21...v25.1.22) (2023-06-19) diff --git a/package.json b/package.json index f6b84fd20..e826c682d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.1.22", + "version": "25.2.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From dca78c07410845f13b7b8a51d7d0dacbc462d428 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 20 Jun 2023 03:45:47 +0200 Subject: [PATCH 098/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/nl.po | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/i18n/nl.po b/i18n/nl.po index ceb0b5b7d..ddad2f6b7 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" +"POT-Creation-Date: 2023-05-24T12:55:52.925Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" "Last-Translator: Charel van den Elsen, 2023\n" "Language-Team: Dutch (https://app.transifex.com/hisp-uio/teams/100509/nl/)\n" @@ -157,7 +157,7 @@ msgid "Data Type" msgstr "" msgid "All types" -msgstr "" +msgstr "Alle typen" msgid "Disaggregation" msgstr "" @@ -169,7 +169,7 @@ msgid "Search by data item name" msgstr "" msgid "No items selected" -msgstr "" +msgstr "Geen items geselecteerd" msgid "Selected Items" msgstr "" @@ -199,7 +199,7 @@ msgid "No program indicators found for \"{{- searchTerm}}\"" msgstr "" msgid "Nothing found for \"{{- searchTerm}}\"" -msgstr "" +msgstr "Niets gevonden voor \"{{- zoekTerm}}\"" msgid "Calculation" msgstr "" @@ -211,7 +211,7 @@ msgid "All metrics" msgstr "" msgid "Move to {{axisName}}" -msgstr "" +msgstr "Ga naar {{axisNaam}}" msgid "Add to {{axisName}}" msgstr "Voeg toe aan {{axisName}}" @@ -843,6 +843,9 @@ msgstr "" msgid "Years" msgstr "Jaren" +msgid "Interpretations and details" +msgstr "" + msgid "Translating to" msgstr "" From 62f18e6c41f6de3685cff84de02668abe44c8c47 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 20 Jun 2023 01:50:42 +0000 Subject: [PATCH 099/285] chore(release): cut 25.2.1 [skip ci] ## [25.2.1](https://github.com/dhis2/analytics/compare/v25.2.0...v25.2.1) (2023-06-20) ### Bug Fixes * **translations:** sync translations from transifex (master) ([dca78c0](https://github.com/dhis2/analytics/commit/dca78c07410845f13b7b8a51d7d0dacbc462d428)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7bea7d84..d9729807a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.2.1](https://github.com/dhis2/analytics/compare/v25.2.0...v25.2.1) (2023-06-20) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([dca78c0](https://github.com/dhis2/analytics/commit/dca78c07410845f13b7b8a51d7d0dacbc462d428)) + # [25.2.0](https://github.com/dhis2/analytics/compare/v25.1.22...v25.2.0) (2023-06-19) diff --git a/package.json b/package.json index e826c682d..5f8d6cefc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.2.0", + "version": "25.2.1", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From c55346c3e91dee98337d129056545afc1e4e712f Mon Sep 17 00:00:00 2001 From: HendrikThePendric Date: Tue, 20 Jun 2023 10:40:13 +0200 Subject: [PATCH 100/285] Revert "feat: toolbar UI update with hoverable menu (#1478)" This reverts commit 552616368677aed228ef3f53eef30d02ec183d72. --- .github/workflows/node-publish.yml | 2 +- .github/workflows/node-test.yml | 2 +- config/setupTestingLibrary.js | 5 - i18n/en.pot | 7 +- jest.config.js | 5 +- package.json | 2 - src/__demo__/FileMenu.stories.js | 29 +- src/__demo__/Toolbar.stories.js | 78 --- src/components/FileMenu/FileMenu.js | 326 +++++++----- .../FileMenu/__tests__/FileMenu.spec.js | 503 +++++++++--------- .../Options/VisualizationOptions.js | 10 +- .../Toolbar/HoverMenuBar/HoverMenuBar.js | 118 ---- .../Toolbar/HoverMenuBar/HoverMenuDropdown.js | 49 -- .../Toolbar/HoverMenuBar/HoverMenuList.js | 97 ---- .../Toolbar/HoverMenuBar/HoverMenuListItem.js | 95 ---- .../HoverMenuBar/HoverMenuListItem.styles.js | 91 ---- .../__tests__/HoverMenuBar.spec.js | 256 --------- .../__tests__/HoverMenuDropdown.spec.js | 20 - .../__tests__/HoverMenuList.spec.js | 56 -- .../__tests__/HoverMenuListItem.spec.js | 39 -- src/components/Toolbar/HoverMenuBar/index.js | 4 - .../InterpretationsAndDetailsToggler.js | 34 -- src/components/Toolbar/MenuButton.styles.js | 37 -- src/components/Toolbar/Toolbar.js | 29 - src/components/Toolbar/ToolbarSidebar.js | 31 -- src/components/Toolbar/UpdateButton.js | 39 -- .../InterpretationsAndDetailsToggler.spec.js | 50 -- .../Toolbar/__tests__/Toolbar.spec.js | 18 - .../Toolbar/__tests__/ToolbarSidebar.spec.js | 23 - .../Toolbar/__tests__/UpdateButton.spec.js | 34 -- src/components/Toolbar/index.js | 5 - src/index.js | 2 - yarn.lock | 202 +------ 33 files changed, 457 insertions(+), 1841 deletions(-) delete mode 100644 config/setupTestingLibrary.js delete mode 100644 src/__demo__/Toolbar.stories.js delete mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuBar.js delete mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js delete mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuList.js delete mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js delete mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js delete mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js delete mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js delete mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js delete mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js delete mode 100644 src/components/Toolbar/HoverMenuBar/index.js delete mode 100644 src/components/Toolbar/InterpretationsAndDetailsToggler.js delete mode 100644 src/components/Toolbar/MenuButton.styles.js delete mode 100644 src/components/Toolbar/Toolbar.js delete mode 100644 src/components/Toolbar/ToolbarSidebar.js delete mode 100644 src/components/Toolbar/UpdateButton.js delete mode 100644 src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js delete mode 100644 src/components/Toolbar/__tests__/Toolbar.spec.js delete mode 100644 src/components/Toolbar/__tests__/ToolbarSidebar.spec.js delete mode 100644 src/components/Toolbar/__tests__/UpdateButton.spec.js delete mode 100644 src/components/Toolbar/index.js diff --git a/.github/workflows/node-publish.yml b/.github/workflows/node-publish.yml index 76aaddfaf..5e04dd7ec 100644 --- a/.github/workflows/node-publish.yml +++ b/.github/workflows/node-publish.yml @@ -32,7 +32,7 @@ jobs: token: ${{env.GH_TOKEN}} - uses: actions/setup-node@v1 with: - node-version: 16.x + node-version: 12.x - name: Install run: yarn install --frozen-lockfile diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index efa5999b7..27930a8f7 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 16.x + node-version: 12.x - name: Install run: yarn install --frozen-lockfile diff --git a/config/setupTestingLibrary.js b/config/setupTestingLibrary.js deleted file mode 100644 index 446c378d5..000000000 --- a/config/setupTestingLibrary.js +++ /dev/null @@ -1,5 +0,0 @@ -import { configure } from '@testing-library/dom' - -configure({ - testIdAttribute: 'data-test', -}) diff --git a/i18n/en.pot b/i18n/en.pot index 2ceaf2ec9..549b42735 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: 2023-05-24T12:55:52.925Z\n" -"PO-Revision-Date: 2023-05-24T12:55:52.925Z\n" +"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" +"PO-Revision-Date: 2023-04-18T08:41:27.838Z\n" msgid "view only" msgstr "view only" @@ -856,9 +856,6 @@ msgstr "Financial Years" msgid "Years" msgstr "Years" -msgid "Interpretations and details" -msgstr "Interpretations and details" - msgid "Translating to" msgstr "Translating to" diff --git a/jest.config.js b/jest.config.js index 59289738e..83db82633 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,4 @@ module.exports = { testPathIgnorePatterns: ['/node_modules/', '/build/'], - setupFilesAfterEnv: [ - '/config/setupEnzyme.js', - '/config/setupTestingLibrary.js', - ], + setupFilesAfterEnv: ['/config/setupEnzyme.js'], } diff --git a/package.json b/package.json index 5f8d6cefc..8581733f9 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,6 @@ "@storybook/addons": "^6.5.9", "@storybook/preset-create-react-app": "^3.1.7", "@storybook/react": "^6.1.14", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^12.1.5", "enzyme": "^3.9.0", "enzyme-adapter-react-16": "^1.15.6", "fs-extra": "^10.1.0", diff --git a/src/__demo__/FileMenu.stories.js b/src/__demo__/FileMenu.stories.js index 98092feac..f8b821002 100644 --- a/src/__demo__/FileMenu.stories.js +++ b/src/__demo__/FileMenu.stories.js @@ -2,7 +2,6 @@ import { Provider } from '@dhis2/app-runtime' import { storiesOf } from '@storybook/react' import React from 'react' import { FileMenu } from '../components/FileMenu/FileMenu.js' -import { HoverMenuBar } from '../components/Toolbar/index.js' const configMock = { baseUrl: 'http://localhost:8080', @@ -62,30 +61,24 @@ const visReadonlyObject = { storiesOf('FileMenu', module) .add('Simple', () => ( - - - + )) .add('With AO', () => ( - - - + )) .add('With readonly AO', () => ( - - - + )) diff --git a/src/__demo__/Toolbar.stories.js b/src/__demo__/Toolbar.stories.js deleted file mode 100644 index 14d50fb34..000000000 --- a/src/__demo__/Toolbar.stories.js +++ /dev/null @@ -1,78 +0,0 @@ -import { storiesOf } from '@storybook/react' -import React, { useState } from 'react' -import { - HoverMenuBar, - HoverMenuDropdown, - HoverMenuList, - HoverMenuListItem, - InterpretationsAndDetailsToggler, - Toolbar, - ToolbarSidebar, - UpdateButton, -} from '../components/Toolbar/index.js' - -function ToolbarWithState() { - const [isHidden, setIsHidden] = useState(false) - const [isSidebarShowing, setIsSidebarShowing] = useState(false) - return ( - - - Toolbar side bar -
    setIsHidden(true)} - > - click to hide - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setIsSidebarShowing((current) => !current)} - /> - - ) -} - -storiesOf('Toolbar', module).add('default', () => { - return -}) diff --git a/src/components/FileMenu/FileMenu.js b/src/components/FileMenu/FileMenu.js index bc0c3766a..588639a2d 100644 --- a/src/components/FileMenu/FileMenu.js +++ b/src/components/FileMenu/FileMenu.js @@ -9,19 +9,19 @@ import { IconDelete24, SharingDialog, colors, + FlyoutMenu, + Layer, + MenuItem, MenuDivider, + Popper, } from '@dhis2/ui' import PropTypes from 'prop-types' -import React, { useState } from 'react' +import React, { createRef, useState } from 'react' import i18n from '../../locales/index.js' import { OpenFileDialog } from '../OpenFileDialog/OpenFileDialog.js' -import { - HoverMenuListItem, - HoverMenuList, - HoverMenuDropdown, -} from '../Toolbar/index.js' import { TranslationDialog } from '../TranslationDialog/index.js' import { DeleteDialog } from './DeleteDialog.js' +import { fileMenuStyles } from './FileMenu.styles.js' import { GetLinkDialog } from './GetLinkDialog.js' import { RenameDialog } from './RenameDialog.js' import { SaveAsDialog } from './SaveAsDialog.js' @@ -43,11 +43,21 @@ export const FileMenu = ({ onError, onTranslate, }) => { + const [menuIsOpen, setMenuIsOpen] = useState(false) const [currentDialog, setCurrentDialog] = useState(null) + + // Escape key press closes the menu + const onKeyDown = (e) => { + if (e?.keyCode === 27) { + setMenuIsOpen(false) + } + } const onMenuItemClick = (dialogToOpen) => () => { + setMenuIsOpen(false) setCurrentDialog(dialogToOpen) } const onDialogClose = () => setCurrentDialog(null) + const toggleMenu = () => setMenuIsOpen(!menuIsOpen) const onDeleteConfirm = () => { // The dialog must be closed before calling the callback // otherwise the fileObject is changed to null before the @@ -57,6 +67,8 @@ export const FileMenu = ({ onDelete() } + const buttonRef = createRef() + const renderDialog = () => { switch (currentDialog) { case 'rename': @@ -126,7 +138,17 @@ export const FileMenu = ({ const iconInactiveColor = colors.grey500 return ( - <> +
    + +
    + +
    - - - } - onClick={onNew} - dataTest="file-menu-new" - /> - - } - onClick={onMenuItemClick('open')} - dataTest="file-menu-open" - /> - + + + } + onClick={() => { + toggleMenu() + onNew() + }} + dataTest="file-menu-new" + /> + + } + onClick={onMenuItemClick('open')} + dataTest="file-menu-open" + /> + + } + disabled={ !onSave || !( !fileObject?.id || fileObject?.access?.update ) - ? iconInactiveColor - : iconActiveColor } + onClick={ + fileObject?.id + ? () => { + toggleMenu() + onSave() + } + : onMenuItemClick('saveas') + } + dataTest="file-menu-save" /> - } - disabled={ - !onSave || - !(!fileObject?.id || fileObject?.access?.update) - } - onClick={ - fileObject?.id ? onSave : onMenuItemClick('saveas') - } - dataTest="file-menu-save" - /> - } + disabled={!(onSaveAs && fileObject?.id)} + onClick={onMenuItemClick('saveas')} + dataTest="file-menu-saveas" /> - } - disabled={!(onSaveAs && fileObject?.id)} - onClick={onMenuItemClick('saveas')} - dataTest="file-menu-saveas" - /> - + } + disabled={ + !( + fileObject?.id && + fileObject?.access?.update + ) } + onClick={onMenuItemClick('rename')} + dataTest="file-menu-rename" /> - } - disabled={ - !(fileObject?.id && fileObject?.access?.update) - } - onClick={onMenuItemClick('rename')} - dataTest="file-menu-rename" - /> - } + disabled={ + !( + fileObject?.id && + fileObject?.access?.update + ) + } + onClick={onMenuItemClick('translate')} + dataTest="file-menu-translate" /> - } - disabled={ - !(fileObject?.id && fileObject?.access?.update) - } - onClick={onMenuItemClick('translate')} - dataTest="file-menu-translate" - /> - - + + } + disabled={ + !( + fileObject?.id && + fileObject?.access?.manage + ) } + onClick={onMenuItemClick('sharing')} + dataTest="file-menu-sharing" /> - } - disabled={ - !(fileObject?.id && fileObject?.access?.manage) - } - onClick={onMenuItemClick('sharing')} - dataTest="file-menu-sharing" - /> - } + disabled={!fileObject?.id} + onClick={onMenuItemClick('getlink')} + dataTest="file-menu-getlink" /> - } - disabled={!fileObject?.id} - onClick={onMenuItemClick('getlink')} - dataTest="file-menu-getlink" - /> - - + } + disabled={ + !( + fileObject?.id && + fileObject?.access?.delete + ) + } + onClick={onMenuItemClick('delete')} + dataTest="file-menu-delete" /> - } - disabled={ - !(fileObject?.id && fileObject?.access?.delete) - } - onClick={onMenuItemClick('delete')} - dataTest="file-menu-delete" - /> - - + + + + )} {renderDialog()} - +
    ) } diff --git a/src/components/FileMenu/__tests__/FileMenu.spec.js b/src/components/FileMenu/__tests__/FileMenu.spec.js index fefe5b5a3..3e56fdacc 100644 --- a/src/components/FileMenu/__tests__/FileMenu.spec.js +++ b/src/components/FileMenu/__tests__/FileMenu.spec.js @@ -1,24 +1,18 @@ -import { CustomDataProvider } from '@dhis2/app-runtime' -import { render, fireEvent, screen, getByText } from '@testing-library/react' -import '@testing-library/jest-dom' +import { SharingDialog } from '@dhis2/ui' +import { shallow } from 'enzyme' import React from 'react' -import { HoverMenuBar } from '../../Toolbar/index.js' +import { OpenFileDialog } from '../../OpenFileDialog/OpenFileDialog.js' +import { TranslationDialog } from '../../TranslationDialog/index.js' +import { DeleteDialog } from '../DeleteDialog.js' import { FileMenu } from '../FileMenu.js' - -jest.mock( - '../../TranslationDialog/TranslationModal/useTranslationsResults.js', - () => ({ - /* This will keep the translation dialog in - * a loading state, which prevents it from - * throwing other errors */ - useTranslationsResults: () => ({ - translationsData: undefined, - fetching: true, - }), - }) -) +import { GetLinkDialog } from '../GetLinkDialog.js' +import { RenameDialog } from '../RenameDialog.js' +import { SaveAsDialog } from '../SaveAsDialog.js' describe('The FileMenu component ', () => { + let shallowFileMenu + let props + const onDelete = jest.fn() const onError = jest.fn() const onNew = jest.fn() @@ -29,317 +23,308 @@ describe('The FileMenu component ', () => { const onShare = jest.fn() const onTranslate = jest.fn() - const baseProps = { - currentUser: { id: 'u1', displayName: 'Test user' }, - fileType: 'visualization', - fileObject: undefined, - onDelete, - onError, - onNew, - onOpen, - onRename, - onSave, - onSaveAs, - onShare, - onTranslate, + const getFileMenuComponent = (props) => { + if (!shallowFileMenu) { + shallowFileMenu = shallow() + } + return shallowFileMenu } - const fullAccessProps = { - fileObject: { + beforeEach(() => { + shallowFileMenu = undefined + props = { + currentUser: { id: 'u1', displayName: 'Test user' }, + fileType: 'visualization', + fileObject: undefined, + onDelete, + onError, + onNew, + onOpen, + onRename, + onSave, + onSaveAs, + onShare, + onTranslate, + } + }) + + it('renders a button', () => { + expect(getFileMenuComponent(props).find('button')).toHaveLength(1) + }) + + it('renders some enabled buttons regardless of the access settings', () => { + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') + + const buttonLabels = ['New', 'Open…'] + + buttonLabels.forEach((buttonLabel) => + expect( + fileMenuComponent + .findWhere((n) => n.prop('label') === buttonLabel) + .prop('disabled') + ).toBe(undefined) + ) + }) + + it('renders some disabled buttons when no fileObject is present', () => { + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') + + const buttonLabels = [ + 'Save as…', + 'Rename…', + 'Translate…', + 'Share…', + 'Get link…', + 'Delete', + ] + + buttonLabels.forEach((buttonLabel) => + expect( + fileMenuComponent + .findWhere((n) => n.prop('label') === buttonLabel) + .prop('disabled') + ).toBe(true) + ) + }) + + it('renders some enabled buttons when update access is granted', () => { + props.fileObject = { id: 'test', access: { - delete: true, - manage: true, + delete: false, + manage: false, update: true, }, - href: 'http://dhis2.org', - }, - } - - const renderFileMenu = (customProps = {}) => { - const props = { ...baseProps, ...customProps } - const providerData = { - translations: { - translations: {}, - }, - sharing: { - meta: { - allowPublicAccess: true, - }, - object: { - userAccesses: [], - userGroupAccesses: [], - }, - }, } - return render( - - - - - - ) - } + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - const openDropdown = async () => { - fireEvent.click(screen.getByTestId('dhis2-analytics-hovermenudropdown')) - expect(await screen.findByTestId('file-menu-container')).toBeVisible() - } + const buttonLabels = ['Save', 'Rename…', 'Translate…'] - const MENU_ITEMS = { - NEW: { testId: 'file-menu-new', text: 'New' }, - OPEN: { testId: 'file-menu-open', text: 'Open…' }, - SAVE: { testId: 'file-menu-save', text: 'Save' }, - SAVE_AS: { testId: 'file-menu-saveas', text: 'Save as…' }, - RENAME: { testId: 'file-menu-rename', text: 'Rename…' }, - TRANSLATE: { testId: 'file-menu-translate', text: 'Translate…' }, - SHARE: { testId: 'file-menu-sharing', text: 'Share…' }, - GET_LINK: { testId: 'file-menu-getlink', text: 'Get link…' }, - DELETE: { testId: 'file-menu-delete', text: 'Delete' }, - } - - const assertMenuItemsDisabledState = (menuItems) => { - for (const menuTitem of menuItems) { - const li = screen.getByTestId(menuTitem.testId) - expect(getByText(li, menuTitem.text)).toBeVisible() + buttonLabels.forEach((buttonLabel) => + expect( + fileMenuComponent + .findWhere((n) => n.prop('label') === buttonLabel) + .prop('disabled') + ).toBe(false) + ) + }) - if (menuTitem.disabled) { - expect(li).toHaveClass('disabled') - } else { - expect(li).not.toHaveClass('disabled') - } + it('renders enabled Delete button when delete access is granted', () => { + props.fileObject = { + id: 'test', + access: { + delete: true, + manage: false, + update: false, + }, } - } - it('renders a button', () => { - renderFileMenu() - expect( - screen.getAllByTestId('dhis2-analytics-hovermenudropdown') - ).toHaveLength(1) + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - const button = screen.getByTestId('dhis2-analytics-hovermenudropdown') - expect(button).toBeVisible() - expect(button).toHaveTextContent('File') + expect( + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Delete') + .prop('disabled') + ).toBe(false) }) - it('opens when clicking the button', async () => { - renderFileMenu() - - expect( - screen.queryByTestId('file-menu-container') - ).not.toBeInTheDocument() + it('renders enabled Share button when manage access is granted', () => { + props.fileObject = { + id: 'test', + access: { + delete: false, + manage: true, + update: false, + }, + } - await openDropdown() + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - expect(await screen.findByTestId('file-menu-container')).toBeVisible() + expect( + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Share…') + .prop('disabled') + ).toBe(false) }) - it('renders some enabled buttons regardless of the access settings', async () => { - renderFileMenu() - await openDropdown() + it('renders the OpenFileDialog component when the Open button is clicked', () => { + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - assertMenuItemsDisabledState([ - { ...MENU_ITEMS.NEW, disabled: false }, - { ...MENU_ITEMS.OPEN, disabled: false }, - ]) - }) + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Open…') + .simulate('click') - it('renders some disabled buttons when no fileObject is present', async () => { - renderFileMenu() - await openDropdown() - - assertMenuItemsDisabledState([ - { ...MENU_ITEMS.SAVE_AS, disabled: true }, - { ...MENU_ITEMS.RENAME, disabled: true }, - { ...MENU_ITEMS.TRANSLATE, disabled: true }, - { ...MENU_ITEMS.SHARE, disabled: true }, - { ...MENU_ITEMS.GET_LINK, disabled: true }, - { ...MENU_ITEMS.DELETE, disabled: true }, - ]) + expect(fileMenuComponent.find(OpenFileDialog)).toHaveLength(1) }) - it('renders some enabled buttons when update access is granted', async () => { - const customProps = { - fileObject: { - id: 'test', - access: { - delete: false, - manage: false, - update: true, - }, + it('renders the RenameDialog when the Rename button is clicked', () => { + props.fileObject = { + id: 'test', + access: { + delete: true, + manage: true, + update: true, }, } - renderFileMenu(customProps) - await openDropdown() + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - assertMenuItemsDisabledState([ - { ...MENU_ITEMS.SAVE, disabled: false }, - { ...MENU_ITEMS.RENAME, disabled: false }, - { ...MENU_ITEMS.TRANSLATE, disabled: false }, - ]) + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Rename…') + .simulate('click') + + expect(fileMenuComponent.find(RenameDialog)).toHaveLength(1) }) - it('renders enabled Delete button when delete access is granted', async () => { - const customProps = { - fileObject: { - id: 'test', - access: { - delete: true, - manage: false, - update: false, - }, + it('renders the TranslationDialog when the Translate button is clicked', () => { + props.fileObject = { + id: 'test', + access: { + delete: true, + manage: true, + update: true, }, } - renderFileMenu(customProps) - await openDropdown() + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') + + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Translate…') + .simulate('click') - assertMenuItemsDisabledState([ - { ...MENU_ITEMS.DELETE, disabled: false }, - ]) + expect(fileMenuComponent.find(TranslationDialog)).toHaveLength(1) }) - it('renders enabled Share button when manage access is granted', async () => { - const customProps = { - fileObject: { - id: 'test', - access: { - delete: false, - manage: true, - update: false, - }, + it('renders the SharingDialog when the Share button is clicked', () => { + props.fileObject = { + id: 'test', + access: { + delete: true, + manage: true, + update: true, }, } - renderFileMenu(customProps) - await openDropdown() - - assertMenuItemsDisabledState([{ ...MENU_ITEMS.SHARE, disabled: false }]) - }) + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - it('renders the OpenFileDialog component when the Open button is clicked', async () => { - renderFileMenu() - await openDropdown() - fireEvent.click(screen.getByTestId(MENU_ITEMS.OPEN.testId)) + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Share…') + .simulate('click') - expect( - await screen.findByText('Open a visualization', { selector: 'h1' }) - ).toBeVisible() + expect(fileMenuComponent.find(SharingDialog)).toHaveLength(1) }) - it('renders the RenameDialog when the Rename button is clicked', async () => { - renderFileMenu(fullAccessProps) - await openDropdown() - fireEvent.click(screen.getByTestId(MENU_ITEMS.RENAME.testId)) + it('renders the GetLinkDialog when the Get link button is clicked', () => { + props.fileObject = { + id: 'test', + access: { + delete: true, + manage: true, + update: true, + }, + } - expect( - await screen.findByText('Rename visualization', { selector: 'h1' }) - ).toBeVisible() - }) + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - it('renders the TranslationDialog when the Translate button is clicked', async () => { - renderFileMenu(fullAccessProps) - await openDropdown() - fireEvent.click(screen.getByTestId(MENU_ITEMS.TRANSLATE.testId)) + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Get link…') + .simulate('click') - expect( - await screen.findByText('Translate', { - exact: false, - selector: 'h1', - }) - ).toBeVisible() + expect(fileMenuComponent.find(GetLinkDialog)).toHaveLength(1) }) - it('renders the SharingDialog when the Share button is clicked', async () => { - renderFileMenu(fullAccessProps) - await openDropdown() - fireEvent.click(screen.getByTestId(MENU_ITEMS.SHARE.testId)) - - expect( - await screen.findByText('Sharing and access', { selector: 'h1' }) - ).toBeVisible() - }) + it('renders the DeleteDialog when the Delete button is clicked', () => { + props.fileObject = { + id: 'delete-test', + access: { + delete: true, + manage: true, + update: true, + }, + } - it('renders the GetLinkDialog when the Get link button is clicked', async () => { - const url = 'http://localhost/dhis-web-data-visualizer/#/test' + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - renderFileMenu(fullAccessProps) - await openDropdown() - fireEvent.click(screen.getByTestId(MENU_ITEMS.GET_LINK.testId)) + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Delete') + .simulate('click') - expect(await screen.findByTestId('dhis2-uicore-modal')).toBeVisible() - expect(screen.getByRole('link', { name: url })).toHaveAttribute( - 'href', - url - ) + expect(fileMenuComponent.find(DeleteDialog)).toHaveLength(1) }) - it('renders the DeleteDialog when the Delete button is clicked', async () => { - const customProps = { - fileObject: { - id: 'delete-test', - access: { - delete: true, - manage: true, - update: true, - }, + it('renders the SaveAsDialog when the Save as… button is clicked', () => { + props.fileObject = { + id: 'test', + access: { + delete: true, + manage: true, + update: true, }, } - renderFileMenu(customProps) - await openDropdown() - fireEvent.click(screen.getByTestId(MENU_ITEMS.DELETE.testId)) + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - expect( - await screen.findByText('Delete visualization', { selector: 'h1' }) - ).toBeVisible() + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Save as…') + .simulate('click') + + expect(fileMenuComponent.find(SaveAsDialog)).toHaveLength(1) }) - it('renders the SaveAsDialog when the Save as… button is clicked', async () => { - renderFileMenu(fullAccessProps) - await openDropdown() - fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE_AS.testId)) + it('renders the SaveAsDialog when the Save… button is clicked but no fileObject is present', () => { + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - expect( - await screen.findByText('Save visualization as', { selector: 'h1' }) - ).toBeVisible() + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Save…') + .simulate('click') + + expect(fileMenuComponent.find(SaveAsDialog)).toHaveLength(1) }) - it('renders the SaveAsDialog when the Save… button is clicked but no fileObject is present', async () => { - const customProps = { - fileObject: { - // NOTE: no `id` field - access: { - update: true, - }, + it('calls the onSave callback when the Save button is clicked and a fileObject is present', () => { + props.fileObject = { + id: 'test', + access: { + delete: true, + manage: true, + update: true, }, } - renderFileMenu(customProps) - await openDropdown() - fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE.testId)) - expect( - await screen.findByText('Save visualization as', { selector: 'h1' }) - ).toBeVisible() - }) + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') - it('calls the onSave callback when the Save button is clicked and a fileObject is present', async () => { - renderFileMenu(fullAccessProps) - await openDropdown() - fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE.testId)) + fileMenuComponent + .findWhere((n) => n.prop('label') === 'Save') + .simulate('click') - expect(screen.queryByText('Open a visualization')).not.toBeVisible() - expect(onSave).toHaveBeenCalledTimes(1) + expect(fileMenuComponent.find(OpenFileDialog).prop('open')).toBe(false) + expect(onSave).toHaveBeenCalled() }) - it('calls the onNew callback when the New button is clicked', async () => { - renderFileMenu() - await openDropdown() - fireEvent.click(screen.getByTestId(MENU_ITEMS.NEW.testId)) + it('calls the onNew callback when the New button is clicked', () => { + const fileMenuComponent = getFileMenuComponent(props) + fileMenuComponent.find('button').simulate('click') + + fileMenuComponent + .findWhere((n) => n.prop('label') === 'New') + .simulate('click') - expect(screen.queryByText('Open a visualization')).not.toBeVisible() - expect(onNew).toHaveBeenCalledTimes(1) + expect(fileMenuComponent.find(OpenFileDialog).prop('open')).toBe(false) + expect(onNew).toHaveBeenCalled() }) }) diff --git a/src/components/Options/VisualizationOptions.js b/src/components/Options/VisualizationOptions.js index 3bb3e3174..7edc2c2f1 100644 --- a/src/components/Options/VisualizationOptions.js +++ b/src/components/Options/VisualizationOptions.js @@ -30,13 +30,8 @@ import { tabSectionOptionIcon, } from './styles/VisualizationOptions.style.js' -const VisualizationOptions = ({ - initiallyActiveTabKey, - optionsConfig, - onClose, - onUpdate, -}) => { - const [activeTabKey, setActiveTabKey] = useState(initiallyActiveTabKey) +const VisualizationOptions = ({ optionsConfig, onClose, onUpdate }) => { + const [activeTabKey, setActiveTabKey] = useState() const generateTabContent = (sections) => sections.map(({ key, label, content, helpText }) => ( @@ -149,7 +144,6 @@ const VisualizationOptions = ({ VisualizationOptions.propTypes = { optionsConfig: PropTypes.array.isRequired, - initiallyActiveTabKey: PropTypes.string, onClose: PropTypes.func, onUpdate: PropTypes.func, } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuBar.js b/src/components/Toolbar/HoverMenuBar/HoverMenuBar.js deleted file mode 100644 index 055694e78..000000000 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuBar.js +++ /dev/null @@ -1,118 +0,0 @@ -import PropTypes from 'prop-types' -import React, { - createContext, - useCallback, - useContext, - useEffect, - useState, -} from 'react' - -const throwErrorIfNotInitialized = () => { - throw new Error('`HoverMenubarContext` has not been initialised') -} - -const HoverMenubarContext = createContext({ - closeMenu: throwErrorIfNotInitialized, - onDropDownButtonClick: throwErrorIfNotInitialized, - onDropDownButtonMouseOver: throwErrorIfNotInitialized, - setLastHoveredSubMenuEl: throwErrorIfNotInitialized, - openedDropdownEl: null, -}) - -const useHoverMenubarContext = () => useContext(HoverMenubarContext) - -const HoverMenuBar = ({ children, dataTest }) => { - const [openedDropdownEl, setOpenedDropdownEl] = useState(null) - const [lastHoveredSubMenuEl, setLastHoveredSubMenuEl] = useState(null) - const [isInHoverMode, setIsInHoverMode] = useState(false) - - const closeMenu = useCallback(() => { - setIsInHoverMode(false) - setOpenedDropdownEl(null) - }, []) - - const onDocumentClick = useCallback( - (event) => { - const isClickOnOpenedSubMenuAnchor = - lastHoveredSubMenuEl && - (lastHoveredSubMenuEl === event.target || - lastHoveredSubMenuEl.contains(event.target)) - - if (!isClickOnOpenedSubMenuAnchor) { - closeMenu() - } - }, - [closeMenu, lastHoveredSubMenuEl] - ) - - const onDropDownButtonClick = useCallback( - (event) => { - if (!isInHoverMode) { - setIsInHoverMode(true) - setOpenedDropdownEl(event.currentTarget) - } else { - closeMenu() - } - }, - [closeMenu, isInHoverMode] - ) - - const onDropDownButtonMouseOver = useCallback( - (event) => { - if (isInHoverMode) { - setOpenedDropdownEl(event.currentTarget) - } - }, - [isInHoverMode] - ) - - const closeMenuWithEsc = useCallback( - (event) => { - if (event.keyCode === 27) { - closeMenu() - } - }, - [closeMenu] - ) - - useEffect(() => { - if (isInHoverMode) { - document.addEventListener('click', onDocumentClick, { - once: true, - }) - } - - return () => { - document.removeEventListener('click', onDocumentClick) - } - }, [onDocumentClick, isInHoverMode]) - - return ( - -
    - {children} - -
    -
    - ) -} - -HoverMenuBar.defaultProps = { - dataTest: 'dhis2-analytics-hovermenubar', -} - -HoverMenuBar.propTypes = { - children: PropTypes.node.isRequired, - dataTest: PropTypes.string, -} -export { HoverMenuBar, useHoverMenubarContext } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js deleted file mode 100644 index 1664d1473..000000000 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js +++ /dev/null @@ -1,49 +0,0 @@ -import { Popper } from '@dhis2-ui/popper' -import { Portal } from '@dhis2-ui/portal' -import PropTypes from 'prop-types' -import React, { useRef } from 'react' -import menuButtonStyles from '../MenuButton.styles.js' -import { useHoverMenubarContext } from './HoverMenuBar.js' - -export const HoverMenuDropdown = ({ children, label, dataTest, disabled }) => { - const buttonRef = useRef() - const { - onDropDownButtonClick, - onDropDownButtonMouseOver, - openedDropdownEl, - } = useHoverMenubarContext() - const isOpen = openedDropdownEl === buttonRef.current - - return ( - <> - - {isOpen && ( - - - {children} - - - )} - - ) -} - -HoverMenuDropdown.defaultProps = { - dataTest: 'dhis2-analytics-hovermenudropdown', -} - -HoverMenuDropdown.propTypes = { - children: PropTypes.node.isRequired, - label: PropTypes.node.isRequired, - dataTest: PropTypes.string, - disabled: PropTypes.bool, -} diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuList.js b/src/components/Toolbar/HoverMenuBar/HoverMenuList.js deleted file mode 100644 index a9ae02b84..000000000 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuList.js +++ /dev/null @@ -1,97 +0,0 @@ -import { colors, elevations, spacers } from '@dhis2/ui-constants' -import PropTypes from 'prop-types' -import React, { createContext, useCallback, useContext, useState } from 'react' -import { useHoverMenubarContext } from './HoverMenuBar.js' - -const throwErrorIfNotInitialized = () => { - throw new Error('`HoverMenuListContext` has not been initialised') -} - -const HoverMenuListContext = createContext({ - onSubmenuAnchorMouseEnter: throwErrorIfNotInitialized, - onMenuItemMouseEnter: throwErrorIfNotInitialized, - openedSubMenuEl: null, - dense: false, -}) - -const useHoverMenuListContext = () => useContext(HoverMenuListContext) - -const HoverMenuList = ({ - children, - className, - dataTest, - dense, - maxHeight, - maxWidth, -}) => { - const { setLastHoveredSubMenuEl } = useHoverMenubarContext() - const [openedSubMenuEl, setOpenedSubMenuEl] = useState(null) - - const onSubmenuAnchorMouseEnter = useCallback( - (event) => { - if (openedSubMenuEl !== event.currentTarget) { - setOpenedSubMenuEl(event.currentTarget) - setLastHoveredSubMenuEl(event.currentTarget) - } - }, - [openedSubMenuEl, setLastHoveredSubMenuEl] - ) - - const onMenuItemMouseEnter = useCallback(() => { - setOpenedSubMenuEl(null) - setLastHoveredSubMenuEl(null) - }, [setLastHoveredSubMenuEl]) - - return ( - -
      - {children} - -
    -
    - ) -} - -HoverMenuList.defaultProps = { - dataTest: 'dhis2-analytics-hovermenulist', - maxWidth: '380px', - maxHeight: 'auto', -} - -HoverMenuList.propTypes = { - /** Typically `MenuItem`, `MenuDivider`, and `MenuSectionHeader` */ - children: PropTypes.node, - className: PropTypes.string, - dataTest: PropTypes.string, - /** Gives all HoverMenuListItem children a dense style */ - dense: PropTypes.bool, - maxHeight: PropTypes.string, - maxWidth: PropTypes.string, -} - -export { HoverMenuList, useHoverMenuListContext } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js deleted file mode 100644 index a99bd4cc5..000000000 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js +++ /dev/null @@ -1,95 +0,0 @@ -import { Popper } from '@dhis2-ui/popper' -import { Portal } from '@dhis2-ui/portal' -import { IconChevronRight24 } from '@dhis2/ui-icons' -import cx from 'classnames' -import PropTypes from 'prop-types' -import React, { useRef } from 'react' -import { HoverMenuList, useHoverMenuListContext } from './HoverMenuList.js' -import styles from './HoverMenuListItem.styles.js' - -const HoverMenuListItem = ({ - onClick, - children, - icon, - className, - destructive, - disabled, - dataTest, - label, -}) => { - const ref = useRef() - const { - onSubmenuAnchorMouseEnter, - onMenuItemMouseEnter, - openedSubMenuEl, - dense, - } = useHoverMenuListContext() - - const isSubMenuOpen = openedSubMenuEl === ref.current - - return ( - <> -
  • - {icon && {icon}} - - {label} - - {!!children && ( - - - - )} - - -
  • - {children && isSubMenuOpen && ( - - - {children} - - - )} - - ) -} - -HoverMenuListItem.defaultProps = { - dataTest: 'dhis2-uicore-hovermenulistitem', -} - -HoverMenuListItem.propTypes = { - // Nested menu items become submenus - children: PropTypes.node, - className: PropTypes.string, - dataTest: PropTypes.string, - destructive: PropTypes.bool, - disabled: PropTypes.bool, - /** An icon for the left side of the menu item */ - icon: PropTypes.node, - /** Text in the menu item */ - label: PropTypes.node, - /** Click handler */ - onClick: PropTypes.func, -} - -export { HoverMenuListItem } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js deleted file mode 100644 index 31748d2a5..000000000 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js +++ /dev/null @@ -1,91 +0,0 @@ -import { colors, spacers } from '@dhis2/ui-constants' -import css from 'styled-jsx/css' - -export default css` - li { - display: flex; - align-items: center; - padding: 0px ${spacers.dp24}; - cursor: pointer; - list-style: none; - background-color: ${colors.white}; - color: ${colors.grey900}; - fill: ${colors.grey900}; - font-size: 14px; - line-height: 16px; - user-select: none; - } - - li:hover { - background-color: ${colors.grey200}; - } - - li:active, - li.active { - background-color: ${colors.grey300}; - } - - li.destructive { - color: ${colors.red700}; - fill: ${colors.red600}; - } - - li.destructive:hover { - background-color: ${colors.red050}; - } - - li.destructive:active, - li.destructive.active { - background-color: ${colors.red100}; - } - - li.disabled { - cursor: not-allowed; - color: ${colors.grey500}; - fill: ${colors.grey500}; - } - - li.disabled:hover { - background-color: ${colors.white}; - } - - .label { - flex-grow: 1; - padding: ${spacers.dp12} 0; - } - - li.dense .label { - padding: ${spacers.dp8} 0; - } - - .icon { - flex-grow: 0; - margin-right: ${spacers.dp12}; - width: 24px; - height: 24px; - } - - .chevron { - display: flex; - align-items: center; - flex-grow: 0; - margin-left: ${spacers.dp24}; - } - - li.dense .icon { - margin-right: ${spacers.dp8}; - width: 16px; - height: 16px; - } - - li .icon > :global(svg) { - width: 24px; - height: 24px; - } - - li.dense .icon > :global(svg), - li .chevron > :global(svg) { - width: 16px; - height: 16px; - } -` diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js deleted file mode 100644 index 9ae361ca4..000000000 --- a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js +++ /dev/null @@ -1,256 +0,0 @@ -import '@testing-library/jest-dom' -import { render, fireEvent, screen } from '@testing-library/react' -import { shallow } from 'enzyme' -import React from 'react' -import { - HoverMenuBar, - HoverMenuDropdown, - HoverMenuList, - HoverMenuListItem, -} from '../index.js' - -describe('', () => { - it('renders children', () => { - const childNode = 'text node' - const wrapper = shallow({childNode}) - - expect(wrapper.containsMatchingElement(childNode)).toBe(true) - }) - it('accepts a `dataTest` prop', () => { - const dataTest = 'test' - const wrapper = shallow( - children - ) - - expect(wrapper.find('div').prop('data-test')).toBe(dataTest) - }) - - describe('mouse interactions', () => { - it('does not open on hover before a dropdown anchor is clicked', async () => { - createFullMenuBarWrapper() - fireEvent.mouseOver(screen.getByText('Menu A')) - - await expectMenuItemsInDocument([ - ['Menu item A.1', false], - ['Menu item A.2', false], - ['Menu item A.3', false], - ]) - }) - it('does not open when a disabled dropdown anchor is clicked', async () => { - createFullMenuBarWrapper() - fireEvent.click(screen.getByText('Menu C')) - - await expectMenuItemsInDocument([ - ['Menu item A.1', false], - ['Menu item A.2', false], - ['Menu item A.3', false], - ]) - }) - it('opens menu list when clicked', async () => { - createFullMenuBarWrapper() - fireEvent.click(screen.getByText('Menu A')) - - await expectMenuItemsInDocument([ - ['Menu item A.1', true], - ['Menu item B.1', false], - ['Menu item C.1', false], - ]) - }) - it('responds to hover once open', async () => { - createFullMenuBarWrapper() - fireEvent.click(screen.getByText('Menu A')) - fireEvent.mouseOver(screen.getByText('Menu B')) - - await expectMenuItemsInDocument([ - ['Menu item A.1', false], - ['Menu item B.1', true], - ['Menu item C.1', false], - ]) - }) - it('does not open disabled dropdown on hover in hover mode', async () => { - createFullMenuBarWrapper() - fireEvent.click(screen.getByText('Menu B')) - fireEvent.mouseOver(screen.getByText('Menu C')) - - await expectMenuItemsInDocument([ - ['Menu item B.1', true], - ['Menu item C.1', false], - ]) - }) - it('opens submenus when in hover mode', async () => { - createFullMenuBarWrapper() - fireEvent.click(screen.getByText('Menu B')) - fireEvent.mouseOver(screen.getByText('Menu item B.1')) - - await expectMenuItemsInDocument([ - ['Menu item B.1.1', true], - ['Menu item B.1.2', true], - ['Menu item B.1.3', true], - ['Menu item B.2.1', false], - ['Menu item B.2.2', false], - ['Menu item B.2.3', false], - ]) - - fireEvent.mouseOver(screen.getByText('Menu item B.2')) - - await expectMenuItemsInDocument([ - ['Menu item B.1.1', false], - ['Menu item B.1.2', false], - ['Menu item B.1.3', false], - ['Menu item B.2.1', true], - ['Menu item B.2.2', true], - ['Menu item B.2.3', true], - ]) - }) - it('does not open disabled submenus when in hover mode', async () => { - createFullMenuBarWrapper() - fireEvent.click(screen.getByText('Menu B')) - fireEvent.mouseOver(screen.getByText('Menu item B.2')) - - await expectMenuItemsInDocument([ - ['Menu item B.2.1', true], - ['Menu item B.2.2', true], - ['Menu item B.2.3', true], - ['Menu item B.3.1', false], - ['Menu item B.3.2', false], - ['Menu item B.3.3', false], - ]) - - fireEvent.mouseOver(screen.getByText('Menu item B.3')) - - await expectMenuItemsInDocument([ - ['Menu item B.2.1', true], - ['Menu item B.2.2', true], - ['Menu item B.2.3', true], - ['Menu item B.3.1', false], - ['Menu item B.3.2', false], - ['Menu item B.3.3', false], - ]) - }) - it('closes when clicking on then document', async () => { - createFullMenuBarWrapper() - fireEvent.click(screen.getByText('Menu A')) - - await expectMenuItemsInDocument([['Menu item A.1', true]]) - - fireEvent.click(document) - - await expectMenuItemsInDocument([['Menu item A.1', false]]) - }) - it('stays open when clicking a open submenu anchor', async () => { - createFullMenuBarWrapper() - fireEvent.click(screen.getByText('Menu B')) - - await expectMenuItemsInDocument([['Menu item B.1', true]]) - - fireEvent.mouseOver(screen.getByText('Menu item B.1')) - - await expectMenuItemsInDocument([ - ['Menu item B.1', true], - ['Menu item B.1.1', true], - ['Menu item B.1.2', true], - ['Menu item B.1.3', true], - ]) - - fireEvent.click(screen.getByText('Menu item B.1')) - - await expectMenuItemsInDocument([ - ['Menu item B.1', true], - ['Menu item B.1.1', true], - ['Menu item B.1.2', true], - ['Menu item B.1.3', true], - ]) - }) - it('calls the onClick of the menu item and closes when clicking a menu item', async () => { - const menuItemOnClickSpy = jest.fn() - createFullMenuBarWrapper({ menuItemOnClickSpy }) - fireEvent.click(screen.getByText('Menu A')) - - await expectMenuItemsInDocument([['Menu item A.1', true]]) - - fireEvent.click(screen.getByText('Menu item A.1')) - - expect(menuItemOnClickSpy).toHaveBeenCalledTimes(1) - await expectMenuItemsInDocument([['Menu item A.1', false]]) - }) - - it('calls the onClick of the menu item and closes when clicking a submenu item', async () => { - const subMenuItemOnClickSpy = jest.fn() - createFullMenuBarWrapper({ subMenuItemOnClickSpy }) - - fireEvent.click(screen.getByText('Menu B')) - await expectMenuItemsInDocument([['Menu item B.1', true]]) - - fireEvent.mouseOver(screen.getByText('Menu item B.1')) - await expectMenuItemsInDocument([['Menu item B.1.1', true]]) - - fireEvent.click(screen.getByText('Menu item B.1.1')) - - expect(subMenuItemOnClickSpy).toHaveBeenCalledTimes(1) - await expectMenuItemsInDocument([ - ['Menu item B.1', false], - ['Menu item B.1.1', false], - ['Menu item B.1.1', false], - ]) - }) - }) -}) - -function createFullMenuBarWrapper({ - menuItemOnClickSpy, - subMenuItemOnClickSpy, -} = {}) { - return render( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) -} - -async function expectMenuItemsInDocument(items) { - for (const [text, inDocument] of items) { - if (inDocument) { - expect(await screen.findByText(text)).toBeInTheDocument() - } else { - expect(screen.queryByText(text)).not.toBeInTheDocument() - } - } -} diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js deleted file mode 100644 index fa050d1f4..000000000 --- a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js +++ /dev/null @@ -1,20 +0,0 @@ -import { shallow } from 'enzyme' -import React from 'react' -import { HoverMenuDropdown } from '../index.js' - -describe('', () => { - /* Most of the props for this component are included - * in the mouse interaction tests for the HoverMenuBar. - * Only the `dataTest` prop needs to be verified here. */ - - it('accepts a `dataTest` prop', () => { - const dataTest = 'test' - const wrapper = shallow( - - children - - ) - - expect(wrapper.find('button').prop('data-test')).toBe(dataTest) - }) -}) diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js deleted file mode 100644 index 9b79d9222..000000000 --- a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js +++ /dev/null @@ -1,56 +0,0 @@ -import { shallow, mount } from 'enzyme' -import React from 'react' -import { HoverMenuList, HoverMenuListItem } from '../index.js' - -describe('', () => { - const dataTest = 'test' - const childNode = 'children' - - it('renders children', () => { - const wrapper = shallow({childNode}) - expect(wrapper.containsMatchingElement(childNode)).toBe(true) - }) - it('accept a `className` prop', () => { - const className = 'className' - const wrapper = shallow( - {childNode} - ) - expect(wrapper.find('ul')).toHaveClassName(className) - }) - - it('accepts a `dataTest` prop', () => { - const wrapper = shallow( - {childNode} - ) - - expect(wrapper.find('ul').prop('data-test')).toBe(dataTest) - }) - - it('accept a `dense` prop', () => { - const wrapper = mount( - - - - - ) - - expect(wrapper.find('li').first()).toHaveClassName('dense') - expect(wrapper.find('li').last()).toHaveClassName('dense') - }) - it('accept a `maxHeight` prop', () => { - const maxHeight = '100000px' - const wrapper = shallow( - {childNode} - ) - expect(wrapper.find('style').text()).toContain( - `max-height: ${maxHeight}` - ) - }) - it('accept a `maxWidth` prop', () => { - const maxWidth = '100000px' - const wrapper = shallow( - {childNode} - ) - expect(wrapper.find('style').text()).toContain(`max-width: ${maxWidth}`) - }) -}) diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js deleted file mode 100644 index 4c2503619..000000000 --- a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import { shallow } from 'enzyme' -import React from 'react' -import { HoverMenuListItem } from '../index.js' - -describe('', () => { - /* Some of the props for this component are included - * in the mouse interaction tests for the HoverMenuBar. - * Only the `className`, `dataTest`, `destructive` and - * `icon` prop need to be verified here. */ - - it('accepts a `className` prop', () => { - const className = 'className' - const wrapper = shallow() - - expect(wrapper.find('li')).toHaveClassName(className) - }) - - it('accepts a `dataTest` prop', () => { - const dataTest = 'test' - const wrapper = shallow() - - expect(wrapper.find('li').prop('data-test')).toBe(dataTest) - }) - - it('accepts a `destructive` prop', () => { - const wrapper = shallow() - - expect(wrapper.find('li')).toHaveClassName('destructive') - }) - it('accepts an `icon` prop', () => { - const iconText = 'I am an icon' - const icon = {iconText} - const wrapper = shallow() - - expect(wrapper.find('span.icon')).toExist() - expect(wrapper.find('span#testicon')).toExist() - expect(wrapper.find('span#testicon').text()).toBe(iconText) - }) -}) diff --git a/src/components/Toolbar/HoverMenuBar/index.js b/src/components/Toolbar/HoverMenuBar/index.js deleted file mode 100644 index 8fb29dba3..000000000 --- a/src/components/Toolbar/HoverMenuBar/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export { HoverMenuBar } from './HoverMenuBar.js' -export { HoverMenuDropdown } from './HoverMenuDropdown.js' -export { HoverMenuList } from './HoverMenuList.js' -export { HoverMenuListItem } from './HoverMenuListItem.js' diff --git a/src/components/Toolbar/InterpretationsAndDetailsToggler.js b/src/components/Toolbar/InterpretationsAndDetailsToggler.js deleted file mode 100644 index 8bb8abeb3..000000000 --- a/src/components/Toolbar/InterpretationsAndDetailsToggler.js +++ /dev/null @@ -1,34 +0,0 @@ -import i18n from '@dhis2/d2-i18n' -import { IconChevronRight24, IconChevronLeft24 } from '@dhis2/ui' -import PropTypes from 'prop-types' -import React from 'react' -import menuButtonStyles from './MenuButton.styles.js' - -export const InterpretationsAndDetailsToggler = ({ - onClick, - dataTest, - disabled, - isShowing, -}) => ( - -) - -InterpretationsAndDetailsToggler.defaultProps = { - dataTest: 'dhis2-analytics-interpretationsanddetailstoggler', -} - -InterpretationsAndDetailsToggler.propTypes = { - onClick: PropTypes.func.isRequired, - dataTest: PropTypes.string, - disabled: PropTypes.bool, - isShowing: PropTypes.bool, -} diff --git a/src/components/Toolbar/MenuButton.styles.js b/src/components/Toolbar/MenuButton.styles.js deleted file mode 100644 index 7522ca1d7..000000000 --- a/src/components/Toolbar/MenuButton.styles.js +++ /dev/null @@ -1,37 +0,0 @@ -import { colors, spacers, theme } from '@dhis2/ui-constants' -import css from 'styled-jsx/css' - -export default css` - button { - all: unset; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 14px; - line-height: 14px; - padding: 0 ${spacers.dp12}; - color: ${colors.grey900}; - transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; - cursor: pointer; - } - - button:hover:enabled, - button:active { - background-color: ${colors.grey200}; - } - - button:focus { - outline: 3px solid ${theme.focus}; - outline-offset: -3px; - } - - /* Prevent focus styles when mouse clicking */ - button:focus:not(:focus-visible) { - outline: none; - } - - button:disabled { - color: ${colors.grey500}; - cursor: not-allowed; - } -` diff --git a/src/components/Toolbar/Toolbar.js b/src/components/Toolbar/Toolbar.js deleted file mode 100644 index f6dcce5e7..000000000 --- a/src/components/Toolbar/Toolbar.js +++ /dev/null @@ -1,29 +0,0 @@ -import { colors } from '@dhis2/ui-constants' -import PropTypes from 'prop-types' -import React from 'react' - -export const Toolbar = ({ children, dataTest }) => ( -
    - {children} - -
    -) - -Toolbar.defaultProps = { - dataTest: 'dhis2-analytics-toolbar', -} - -Toolbar.propTypes = { - children: PropTypes.node, - dataTest: PropTypes.string, -} diff --git a/src/components/Toolbar/ToolbarSidebar.js b/src/components/Toolbar/ToolbarSidebar.js deleted file mode 100644 index 948a9fbed..000000000 --- a/src/components/Toolbar/ToolbarSidebar.js +++ /dev/null @@ -1,31 +0,0 @@ -import { colors } from '@dhis2/ui-constants' -import cx from 'classnames' -import PropTypes from 'prop-types' -import React from 'react' - -export const ToolbarSidebar = ({ children, dataTest, isHidden }) => ( -
    - {children} - -
    -) - -ToolbarSidebar.defaultProps = { - dataTest: 'dhis2-analytics-toolbarsidebar', -} - -ToolbarSidebar.propTypes = { - children: PropTypes.node, - dataTest: PropTypes.string, - isHidden: PropTypes.bool, -} diff --git a/src/components/Toolbar/UpdateButton.js b/src/components/Toolbar/UpdateButton.js deleted file mode 100644 index fdfaa06e7..000000000 --- a/src/components/Toolbar/UpdateButton.js +++ /dev/null @@ -1,39 +0,0 @@ -import { CircularLoader } from '@dhis2-ui/loader' -import i18n from '@dhis2/d2-i18n' -import { colors } from '@dhis2/ui-constants' -import { IconSync16 } from '@dhis2/ui-icons' -import PropTypes from 'prop-types' -import React from 'react' -import menuButtonStyles from './MenuButton.styles.js' - -export const UpdateButton = ({ onClick, disabled, loading, dataTest }) => ( - -) - -UpdateButton.defaultProps = { - dataTest: 'dhis2-analytics-updatebutton', -} - -UpdateButton.propTypes = { - onClick: PropTypes.func.isRequired, - dataTest: PropTypes.string, - disabled: PropTypes.bool, - loading: PropTypes.bool, -} diff --git a/src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js b/src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js deleted file mode 100644 index 4477e19d8..000000000 --- a/src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -import { shallow } from 'enzyme' -import React from 'react' -import { InterpretationsAndDetailsToggler } from '../index.js' - -describe('', () => { - const noop = () => {} - - it('accepts an `onClick` prop', () => { - const onClick = jest.fn() - const wrapper = shallow( - - ) - - wrapper.simulate('click') - - expect(onClick).toHaveBeenCalledTimes(1) - }) - it('accepts a `dataTest` prop', () => { - const dataTest = 'test' - const wrapper = shallow( - - ) - - expect(wrapper.prop('data-test')).toBe(dataTest) - }) - it('accepts a `disabled` prop', () => { - const wrapper = shallow( - - ) - - expect(wrapper.find('button').prop('disabled')).toEqual(true) - }) - it('accepts an `isShowing` prop', () => { - const wrapper = shallow( - - ) - const wrapperWithIsShowing = shallow( - - ) - - expect(wrapper.find('SvgChevronRight24')).toHaveLength(0) - expect(wrapper.find('SvgChevronLeft24')).toHaveLength(1) - - expect(wrapperWithIsShowing.find('SvgChevronRight24')).toHaveLength(1) - expect(wrapperWithIsShowing.find('SvgChevronLeft24')).toHaveLength(0) - }) -}) diff --git a/src/components/Toolbar/__tests__/Toolbar.spec.js b/src/components/Toolbar/__tests__/Toolbar.spec.js deleted file mode 100644 index bfd65785a..000000000 --- a/src/components/Toolbar/__tests__/Toolbar.spec.js +++ /dev/null @@ -1,18 +0,0 @@ -import { shallow } from 'enzyme' -import React from 'react' -import { Toolbar } from '../index.js' - -describe('', () => { - it('renders children', () => { - const childNode = 'text node' - const wrapper = shallow({childNode}) - - expect(wrapper.containsMatchingElement(childNode)).toBe(true) - }) - it('accepts a `dataTest` prop', () => { - const dataTest = 'test' - const wrapper = shallow() - - expect(wrapper.prop('data-test')).toBe(dataTest) - }) -}) diff --git a/src/components/Toolbar/__tests__/ToolbarSidebar.spec.js b/src/components/Toolbar/__tests__/ToolbarSidebar.spec.js deleted file mode 100644 index 7801ec550..000000000 --- a/src/components/Toolbar/__tests__/ToolbarSidebar.spec.js +++ /dev/null @@ -1,23 +0,0 @@ -import { shallow } from 'enzyme' -import React from 'react' -import { ToolbarSidebar } from '../index.js' - -describe('', () => { - it('renders children', () => { - const childNode = 'text node' - const wrapper = shallow({childNode}) - - expect(wrapper.containsMatchingElement(childNode)).toBe(true) - }) - it('accepts a `dataTest` prop', () => { - const dataTest = 'test' - const wrapper = shallow() - - expect(wrapper.prop('data-test')).toBe(dataTest) - }) - it('accepts a `isHidden` prop', () => { - const wrapper = shallow() - - expect(wrapper.find('div').hasClass('isHidden')).toEqual(true) - }) -}) diff --git a/src/components/Toolbar/__tests__/UpdateButton.spec.js b/src/components/Toolbar/__tests__/UpdateButton.spec.js deleted file mode 100644 index 3be73c6b4..000000000 --- a/src/components/Toolbar/__tests__/UpdateButton.spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import { shallow } from 'enzyme' -import React from 'react' -import { UpdateButton } from '../index.js' - -describe('', () => { - const noop = () => {} - - it('accepts an `onClick` prop', () => { - const onClick = jest.fn() - const wrapper = shallow() - - wrapper.simulate('click') - - expect(onClick).toHaveBeenCalledTimes(1) - }) - it('accepts a `dataTest` prop', () => { - const dataTest = 'test' - const wrapper = shallow( - - ) - - expect(wrapper.prop('data-test')).toBe(dataTest) - }) - it('accepts a `disabled` prop', () => { - const wrapper = shallow() - - expect(wrapper.find('button').prop('disabled')).toEqual(true) - }) - it('accepts an `loading` prop', () => { - const wrapper = shallow() - - expect(wrapper.find('CircularLoader')).toHaveLength(1) - }) -}) diff --git a/src/components/Toolbar/index.js b/src/components/Toolbar/index.js deleted file mode 100644 index 86d6df30a..000000000 --- a/src/components/Toolbar/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { InterpretationsAndDetailsToggler } from './InterpretationsAndDetailsToggler.js' -export { Toolbar } from './Toolbar.js' -export { ToolbarSidebar } from './ToolbarSidebar.js' -export { UpdateButton } from './UpdateButton.js' -export * from './HoverMenuBar/index.js' diff --git a/src/index.js b/src/index.js index 069656b77..e21a38df7 100644 --- a/src/index.js +++ b/src/index.js @@ -32,8 +32,6 @@ export { default as AboutAOUnit } from './components/AboutAOUnit/AboutAOUnit.js' export { InterpretationsUnit } from './components/Interpretations/InterpretationsUnit/InterpretationsUnit.js' export { InterpretationModal } from './components/Interpretations/InterpretationModal/InterpretationModal.js' -export * from './components/Toolbar/index.js' - export { TranslationDialog } from './components/TranslationDialog/index.js' export { OfflineTooltip } from './components/OfflineTooltip.js' diff --git a/yarn.lock b/yarn.lock index 496096711..77e2352c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@adobe/css-tools@^4.0.1": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.2.0.tgz#e1a84fca468f4b337816fcb7f0964beb620ba855" - integrity sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA== - "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -2663,13 +2658,6 @@ "@types/node" "*" jest-mock "^27.5.1" -"@jest/expect-utils@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" - integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== - dependencies: - jest-get-type "^29.4.3" - "@jest/fake-timers@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" @@ -2784,13 +2772,6 @@ terminal-link "^2.0.0" v8-to-istanbul "^8.1.0" -"@jest/schemas@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" - integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== - dependencies: - "@sinclair/typebox" "^0.25.16" - "@jest/source-map@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" @@ -2963,18 +2944,6 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" -"@jest/types@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" - integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== - dependencies: - "@jest/schemas" "^29.4.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -3188,11 +3157,6 @@ "@storybook/react" "^5.3.3" uuid "^3.1.0" -"@sinclair/typebox@^0.25.16": - version "0.25.24" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" - integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== - "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -4327,21 +4291,6 @@ lz-string "^1.4.4" pretty-format "^27.0.2" -"@testing-library/jest-dom@^5.16.5": - version "5.16.5" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" - integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== - dependencies: - "@adobe/css-tools" "^4.0.1" - "@babel/runtime" "^7.9.2" - "@types/testing-library__jest-dom" "^5.9.1" - aria-query "^5.0.0" - chalk "^3.0.0" - css.escape "^1.5.1" - dom-accessibility-api "^0.5.6" - lodash "^4.17.15" - redent "^3.0.0" - "@testing-library/react@^12.1.2": version "12.1.2" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.2.tgz#f1bc9a45943461fa2a598bb4597df1ae044cfc76" @@ -4350,15 +4299,6 @@ "@babel/runtime" "^7.12.5" "@testing-library/dom" "^8.0.0" -"@testing-library/react@^12.1.5": - version "12.1.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" - integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== - dependencies: - "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^8.0.0" - "@types/react-dom" "<18.0.0" - "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -4520,14 +4460,6 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@*": - version "29.5.1" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.1.tgz#83c818aa9a87da27d6da85d3378e5a34d2f31a47" - integrity sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - "@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -4636,13 +4568,6 @@ "@types/react" "*" "@types/reactcss" "*" -"@types/react-dom@<18.0.0": - version "17.0.20" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.20.tgz#e0c8901469d732b36d8473b40b679ad899da1b53" - integrity sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA== - dependencies: - "@types/react" "^17" - "@types/react-syntax-highlighter@11.0.4": version "11.0.4" resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz#d86d17697db62f98046874f62fdb3e53a0bbc4cd" @@ -4665,15 +4590,6 @@ "@types/prop-types" "*" csstype "^2.2.0" -"@types/react@^17": - version "17.0.60" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.60.tgz#a4a97dcdbebad76612c188fc06440e4995fd8ad2" - integrity sha512-pCH7bqWIfzHs3D+PDs3O/COCQJka+Kcw3RnO9rFA2zalqoXg7cNjJDh6mZ7oRtY1wmY4LVwDdAbA1F7Z8tv3BQ== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - "@types/reactcss@*": version "1.2.3" resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834" @@ -4695,11 +4611,6 @@ dependencies: "@types/node" "*" -"@types/scheduler@*": - version "0.16.3" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" - integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== - "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -4720,13 +4631,6 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA== -"@types/testing-library__jest-dom@^5.9.1": - version "5.14.6" - resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.6.tgz#4887f6e1af11215428ab02777873bcede98a53b0" - integrity sha512-FkHXCb+ikSoUP4Y4rOslzTdX5sqYwMxfefKh1GmZ8ce1GOkEHntSp6b5cGadmNfp5e4BMEWOMx+WSKd5/MqlDA== - dependencies: - "@types/jest" "*" - "@types/trusted-types@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" @@ -4796,13 +4700,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yargs@^17.0.8": - version "17.0.24" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" - integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== - dependencies: - "@types/yargs-parser" "*" - "@typescript-eslint/eslint-plugin@^4.5.0": version "4.13.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.13.0.tgz#5f580ea520fa46442deb82c038460c3dd3524bb6" @@ -7966,11 +7863,6 @@ css-what@^3.2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== -css.escape@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" - integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== - css@^2.0.0: version "2.2.4" resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" @@ -8100,11 +7992,6 @@ csstype@^2.2.0, csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== -csstype@^3.0.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== - cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -8397,11 +8284,6 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== -diff-sequences@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" - integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== - diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -8485,11 +8367,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.6: - version "0.5.16" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" - integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== - dom-accessibility-api@^0.5.9: version "0.5.11" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.11.tgz#79d5846c4f90eba3e617d9031e921de9324f84ed" @@ -9470,17 +9347,6 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" -expect@^29.0.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" - integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== - dependencies: - "@jest/expect-utils" "^29.5.0" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - express@^4.17.0, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -12077,16 +11943,6 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -jest-diff@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" - integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.5.0" - jest-docblock@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" @@ -12211,11 +12067,6 @@ jest-get-type@^27.5.1: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== -jest-get-type@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" - integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== - jest-haste-map@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" @@ -12359,16 +12210,6 @@ jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -jest-matcher-utils@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" - integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== - dependencies: - chalk "^4.0.0" - jest-diff "^29.5.0" - jest-get-type "^29.4.3" - pretty-format "^29.5.0" - jest-message-util@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" @@ -12413,21 +12254,6 @@ jest-message-util@^27.5.1: slash "^3.0.0" stack-utils "^2.0.3" -jest-message-util@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" - integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.5.0" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.5.0" - slash "^3.0.0" - stack-utils "^2.0.3" - jest-mock@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" @@ -12760,18 +12586,6 @@ jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" - integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== - dependencies: - "@jest/types" "^29.5.0" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - jest-validate@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" @@ -15908,15 +15722,6 @@ pretty-format@^27.0.2, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^29.0.0, pretty-format@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" - integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== - dependencies: - "@jest/schemas" "^29.4.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -16474,11 +16279,6 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== -react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -19562,8 +19362,10 @@ watchpack@^1.7.4: resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: + chokidar "^3.4.1" graceful-fs "^4.1.2" neo-async "^2.5.0" + watchpack-chokidar2 "^2.0.1" optionalDependencies: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" From cb51518a23b8fa50865d24bc43cd5827d8688962 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 20 Jun 2023 08:59:09 +0000 Subject: [PATCH 101/285] chore(release): cut 25.2.2 [skip ci] ## [25.2.2](https://github.com/dhis2/analytics/compare/v25.2.1...v25.2.2) (2023-06-20) ### Reverts * Revert "feat: toolbar UI update with hoverable menu (#1478)" ([c55346c](https://github.com/dhis2/analytics/commit/c55346c3e91dee98337d129056545afc1e4e712f)), closes [#1478](https://github.com/dhis2/analytics/issues/1478) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9729807a..2f27076a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.2.2](https://github.com/dhis2/analytics/compare/v25.2.1...v25.2.2) (2023-06-20) + + +### Reverts + +* Revert "feat: toolbar UI update with hoverable menu (#1478)" ([c55346c](https://github.com/dhis2/analytics/commit/c55346c3e91dee98337d129056545afc1e4e712f)), closes [#1478](https://github.com/dhis2/analytics/issues/1478) + ## [25.2.1](https://github.com/dhis2/analytics/compare/v25.2.0...v25.2.1) (2023-06-20) diff --git a/package.json b/package.json index 8581733f9..c9b9d0e54 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.2.1", + "version": "25.2.2", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 5296bd79104dbd914daa8242deda69ce150b355e Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Tue, 20 Jun 2023 11:11:12 +0200 Subject: [PATCH 102/285] fix: avoid DV plugin crash if no ou levels are returned (#1442) This happened on test.e2e.dhis2.org/analytics-dev, I'm not sure it's possible for an instance to not have any ou levels, but this fix prevents the plugin from crashing and the app from showing a blank visualization. Co-authored-by: Martin --- src/modules/ouLevelUtils/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/ouLevelUtils/index.js b/src/modules/ouLevelUtils/index.js index 6eaac0028..dc38b1070 100644 --- a/src/modules/ouLevelUtils/index.js +++ b/src/modules/ouLevelUtils/index.js @@ -15,9 +15,11 @@ const replaceNumericOuLevelWithUid = (ouLevels) => (item) => { } const ouIntId = parseInt(ouIdHelper.removePrefix(item.id), 10) - const ouUid = ouLevels.find((l) => parseInt(l.level, 10) === ouIntId).id + const ouUid = ouLevels.find((l) => parseInt(l.level, 10) === ouIntId)?.id - return Object.assign({}, item, { id: ouIdHelper.addLevelPrefix(ouUid) }) + return ouUid + ? Object.assign({}, item, { id: ouIdHelper.addLevelPrefix(ouUid) }) + : item } export const convertOuLevelsToUids = (ouLevels, layout) => { From a2a7117ef8a4d4dc6a5d3f16308957cb73ea4b27 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 20 Jun 2023 09:15:36 +0000 Subject: [PATCH 103/285] chore(release): cut 25.2.3 [skip ci] ## [25.2.3](https://github.com/dhis2/analytics/compare/v25.2.2...v25.2.3) (2023-06-20) ### Bug Fixes * avoid DV plugin crash if no ou levels are returned ([#1442](https://github.com/dhis2/analytics/issues/1442)) ([5296bd7](https://github.com/dhis2/analytics/commit/5296bd79104dbd914daa8242deda69ce150b355e)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f27076a1..2bae44c82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [25.2.3](https://github.com/dhis2/analytics/compare/v25.2.2...v25.2.3) (2023-06-20) + + +### Bug Fixes + +* avoid DV plugin crash if no ou levels are returned ([#1442](https://github.com/dhis2/analytics/issues/1442)) ([5296bd7](https://github.com/dhis2/analytics/commit/5296bd79104dbd914daa8242deda69ce150b355e)) + ## [25.2.2](https://github.com/dhis2/analytics/compare/v25.2.1...v25.2.2) (2023-06-20) diff --git a/package.json b/package.json index c9b9d0e54..0e106d1d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.2.2", + "version": "25.2.3", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 0a5115327c0c0dbe9da22a092f2fc6002f798290 Mon Sep 17 00:00:00 2001 From: Hendrik de Graaf Date: Tue, 20 Jun 2023 11:28:08 +0200 Subject: [PATCH 104/285] feat: toolbar UI update with hoverable menu (#1478) (#1509) BREAKING CHANGE: The `FileMenu` is now using the new `HoverMenuBar` components which makes this version of the `FileMenu` incompatible with the previous version. Apps will be need to update their toolbar and file menu before using this version of analytics. --- .github/workflows/node-publish.yml | 2 +- .github/workflows/node-test.yml | 2 +- config/setupTestingLibrary.js | 5 + i18n/en.pot | 7 +- jest.config.js | 5 +- package.json | 2 + src/__demo__/FileMenu.stories.js | 29 +- src/__demo__/Toolbar.stories.js | 78 +++ src/components/FileMenu/FileMenu.js | 326 +++++------- .../FileMenu/__tests__/FileMenu.spec.js | 503 +++++++++--------- .../Options/VisualizationOptions.js | 10 +- .../Toolbar/HoverMenuBar/HoverMenuBar.js | 118 ++++ .../Toolbar/HoverMenuBar/HoverMenuDropdown.js | 49 ++ .../Toolbar/HoverMenuBar/HoverMenuList.js | 97 ++++ .../Toolbar/HoverMenuBar/HoverMenuListItem.js | 95 ++++ .../HoverMenuBar/HoverMenuListItem.styles.js | 91 ++++ .../__tests__/HoverMenuBar.spec.js | 256 +++++++++ .../__tests__/HoverMenuDropdown.spec.js | 20 + .../__tests__/HoverMenuList.spec.js | 56 ++ .../__tests__/HoverMenuListItem.spec.js | 39 ++ src/components/Toolbar/HoverMenuBar/index.js | 4 + .../InterpretationsAndDetailsToggler.js | 34 ++ src/components/Toolbar/MenuButton.styles.js | 37 ++ src/components/Toolbar/Toolbar.js | 29 + src/components/Toolbar/ToolbarSidebar.js | 31 ++ src/components/Toolbar/UpdateButton.js | 39 ++ .../InterpretationsAndDetailsToggler.spec.js | 50 ++ .../Toolbar/__tests__/Toolbar.spec.js | 18 + .../Toolbar/__tests__/ToolbarSidebar.spec.js | 23 + .../Toolbar/__tests__/UpdateButton.spec.js | 34 ++ src/components/Toolbar/index.js | 5 + src/index.js | 2 + yarn.lock | 202 ++++++- 33 files changed, 1841 insertions(+), 457 deletions(-) create mode 100644 config/setupTestingLibrary.js create mode 100644 src/__demo__/Toolbar.stories.js create mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuBar.js create mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js create mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuList.js create mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js create mode 100644 src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js create mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js create mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js create mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js create mode 100644 src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js create mode 100644 src/components/Toolbar/HoverMenuBar/index.js create mode 100644 src/components/Toolbar/InterpretationsAndDetailsToggler.js create mode 100644 src/components/Toolbar/MenuButton.styles.js create mode 100644 src/components/Toolbar/Toolbar.js create mode 100644 src/components/Toolbar/ToolbarSidebar.js create mode 100644 src/components/Toolbar/UpdateButton.js create mode 100644 src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js create mode 100644 src/components/Toolbar/__tests__/Toolbar.spec.js create mode 100644 src/components/Toolbar/__tests__/ToolbarSidebar.spec.js create mode 100644 src/components/Toolbar/__tests__/UpdateButton.spec.js create mode 100644 src/components/Toolbar/index.js diff --git a/.github/workflows/node-publish.yml b/.github/workflows/node-publish.yml index 5e04dd7ec..76aaddfaf 100644 --- a/.github/workflows/node-publish.yml +++ b/.github/workflows/node-publish.yml @@ -32,7 +32,7 @@ jobs: token: ${{env.GH_TOKEN}} - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 16.x - name: Install run: yarn install --frozen-lockfile diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 27930a8f7..efa5999b7 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 16.x - name: Install run: yarn install --frozen-lockfile diff --git a/config/setupTestingLibrary.js b/config/setupTestingLibrary.js new file mode 100644 index 000000000..446c378d5 --- /dev/null +++ b/config/setupTestingLibrary.js @@ -0,0 +1,5 @@ +import { configure } from '@testing-library/dom' + +configure({ + testIdAttribute: 'data-test', +}) diff --git a/i18n/en.pot b/i18n/en.pot index 549b42735..2ceaf2ec9 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: 2023-04-18T08:41:27.838Z\n" -"PO-Revision-Date: 2023-04-18T08:41:27.838Z\n" +"POT-Creation-Date: 2023-05-24T12:55:52.925Z\n" +"PO-Revision-Date: 2023-05-24T12:55:52.925Z\n" msgid "view only" msgstr "view only" @@ -856,6 +856,9 @@ msgstr "Financial Years" msgid "Years" msgstr "Years" +msgid "Interpretations and details" +msgstr "Interpretations and details" + msgid "Translating to" msgstr "Translating to" diff --git a/jest.config.js b/jest.config.js index 83db82633..59289738e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,7 @@ module.exports = { testPathIgnorePatterns: ['/node_modules/', '/build/'], - setupFilesAfterEnv: ['/config/setupEnzyme.js'], + setupFilesAfterEnv: [ + '/config/setupEnzyme.js', + '/config/setupTestingLibrary.js', + ], } diff --git a/package.json b/package.json index 0e106d1d1..0bb5f0ea1 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "@storybook/addons": "^6.5.9", "@storybook/preset-create-react-app": "^3.1.7", "@storybook/react": "^6.1.14", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^12.1.5", "enzyme": "^3.9.0", "enzyme-adapter-react-16": "^1.15.6", "fs-extra": "^10.1.0", diff --git a/src/__demo__/FileMenu.stories.js b/src/__demo__/FileMenu.stories.js index f8b821002..98092feac 100644 --- a/src/__demo__/FileMenu.stories.js +++ b/src/__demo__/FileMenu.stories.js @@ -2,6 +2,7 @@ import { Provider } from '@dhis2/app-runtime' import { storiesOf } from '@storybook/react' import React from 'react' import { FileMenu } from '../components/FileMenu/FileMenu.js' +import { HoverMenuBar } from '../components/Toolbar/index.js' const configMock = { baseUrl: 'http://localhost:8080', @@ -61,24 +62,30 @@ const visReadonlyObject = { storiesOf('FileMenu', module) .add('Simple', () => ( - + + + )) .add('With AO', () => ( - + + + )) .add('With readonly AO', () => ( - + + + )) diff --git a/src/__demo__/Toolbar.stories.js b/src/__demo__/Toolbar.stories.js new file mode 100644 index 000000000..14d50fb34 --- /dev/null +++ b/src/__demo__/Toolbar.stories.js @@ -0,0 +1,78 @@ +import { storiesOf } from '@storybook/react' +import React, { useState } from 'react' +import { + HoverMenuBar, + HoverMenuDropdown, + HoverMenuList, + HoverMenuListItem, + InterpretationsAndDetailsToggler, + Toolbar, + ToolbarSidebar, + UpdateButton, +} from '../components/Toolbar/index.js' + +function ToolbarWithState() { + const [isHidden, setIsHidden] = useState(false) + const [isSidebarShowing, setIsSidebarShowing] = useState(false) + return ( + + + Toolbar side bar + setIsHidden(true)} + > + click to hide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setIsSidebarShowing((current) => !current)} + /> + + ) +} + +storiesOf('Toolbar', module).add('default', () => { + return +}) diff --git a/src/components/FileMenu/FileMenu.js b/src/components/FileMenu/FileMenu.js index 588639a2d..bc0c3766a 100644 --- a/src/components/FileMenu/FileMenu.js +++ b/src/components/FileMenu/FileMenu.js @@ -9,19 +9,19 @@ import { IconDelete24, SharingDialog, colors, - FlyoutMenu, - Layer, - MenuItem, MenuDivider, - Popper, } from '@dhis2/ui' import PropTypes from 'prop-types' -import React, { createRef, useState } from 'react' +import React, { useState } from 'react' import i18n from '../../locales/index.js' import { OpenFileDialog } from '../OpenFileDialog/OpenFileDialog.js' +import { + HoverMenuListItem, + HoverMenuList, + HoverMenuDropdown, +} from '../Toolbar/index.js' import { TranslationDialog } from '../TranslationDialog/index.js' import { DeleteDialog } from './DeleteDialog.js' -import { fileMenuStyles } from './FileMenu.styles.js' import { GetLinkDialog } from './GetLinkDialog.js' import { RenameDialog } from './RenameDialog.js' import { SaveAsDialog } from './SaveAsDialog.js' @@ -43,21 +43,11 @@ export const FileMenu = ({ onError, onTranslate, }) => { - const [menuIsOpen, setMenuIsOpen] = useState(false) const [currentDialog, setCurrentDialog] = useState(null) - - // Escape key press closes the menu - const onKeyDown = (e) => { - if (e?.keyCode === 27) { - setMenuIsOpen(false) - } - } const onMenuItemClick = (dialogToOpen) => () => { - setMenuIsOpen(false) setCurrentDialog(dialogToOpen) } const onDialogClose = () => setCurrentDialog(null) - const toggleMenu = () => setMenuIsOpen(!menuIsOpen) const onDeleteConfirm = () => { // The dialog must be closed before calling the callback // otherwise the fileObject is changed to null before the @@ -67,8 +57,6 @@ export const FileMenu = ({ onDelete() } - const buttonRef = createRef() - const renderDialog = () => { switch (currentDialog) { case 'rename': @@ -138,17 +126,7 @@ export const FileMenu = ({ const iconInactiveColor = colors.grey500 return ( -
    - -
    - -
    + <> - {menuIsOpen && ( - - - - } - onClick={() => { - toggleMenu() - onNew() - }} - dataTest="file-menu-new" - /> - - } - onClick={onMenuItemClick('open')} - dataTest="file-menu-open" - /> - - } - disabled={ + + + } + onClick={onNew} + dataTest="file-menu-new" + /> + + } + onClick={onMenuItemClick('open')} + dataTest="file-menu-open" + /> + { - toggleMenu() - onSave() - } - : onMenuItemClick('saveas') - } - dataTest="file-menu-save" /> - + } + disabled={ + !onSave || + !(!fileObject?.id || fileObject?.access?.update) + } + onClick={ + fileObject?.id ? onSave : onMenuItemClick('saveas') + } + dataTest="file-menu-save" + /> + - - } - disabled={ - !( - fileObject?.id && - fileObject?.access?.update - ) + } + disabled={!(onSaveAs && fileObject?.id)} + onClick={onMenuItemClick('saveas')} + dataTest="file-menu-saveas" + /> + - - } - disabled={ - !( - fileObject?.id && - fileObject?.access?.update - ) + } + disabled={ + !(fileObject?.id && fileObject?.access?.update) + } + onClick={onMenuItemClick('rename')} + dataTest="file-menu-rename" + /> + - - - } - disabled={ - !( - fileObject?.id && - fileObject?.access?.manage - ) + } + disabled={ + !(fileObject?.id && fileObject?.access?.update) + } + onClick={onMenuItemClick('translate')} + dataTest="file-menu-translate" + /> + + - + } + disabled={ + !(fileObject?.id && fileObject?.access?.manage) + } + onClick={onMenuItemClick('sharing')} + dataTest="file-menu-sharing" + /> + - - - } - disabled={ - !( - fileObject?.id && - fileObject?.access?.delete - ) + } + disabled={!fileObject?.id} + onClick={onMenuItemClick('getlink')} + dataTest="file-menu-getlink" + /> + + - - - - )} + } + disabled={ + !(fileObject?.id && fileObject?.access?.delete) + } + onClick={onMenuItemClick('delete')} + dataTest="file-menu-delete" + /> + + {renderDialog()} -
    + ) } diff --git a/src/components/FileMenu/__tests__/FileMenu.spec.js b/src/components/FileMenu/__tests__/FileMenu.spec.js index 3e56fdacc..fefe5b5a3 100644 --- a/src/components/FileMenu/__tests__/FileMenu.spec.js +++ b/src/components/FileMenu/__tests__/FileMenu.spec.js @@ -1,18 +1,24 @@ -import { SharingDialog } from '@dhis2/ui' -import { shallow } from 'enzyme' +import { CustomDataProvider } from '@dhis2/app-runtime' +import { render, fireEvent, screen, getByText } from '@testing-library/react' +import '@testing-library/jest-dom' import React from 'react' -import { OpenFileDialog } from '../../OpenFileDialog/OpenFileDialog.js' -import { TranslationDialog } from '../../TranslationDialog/index.js' -import { DeleteDialog } from '../DeleteDialog.js' +import { HoverMenuBar } from '../../Toolbar/index.js' import { FileMenu } from '../FileMenu.js' -import { GetLinkDialog } from '../GetLinkDialog.js' -import { RenameDialog } from '../RenameDialog.js' -import { SaveAsDialog } from '../SaveAsDialog.js' -describe('The FileMenu component ', () => { - let shallowFileMenu - let props +jest.mock( + '../../TranslationDialog/TranslationModal/useTranslationsResults.js', + () => ({ + /* This will keep the translation dialog in + * a loading state, which prevents it from + * throwing other errors */ + useTranslationsResults: () => ({ + translationsData: undefined, + fetching: true, + }), + }) +) +describe('The FileMenu component ', () => { const onDelete = jest.fn() const onError = jest.fn() const onNew = jest.fn() @@ -23,308 +29,317 @@ describe('The FileMenu component ', () => { const onShare = jest.fn() const onTranslate = jest.fn() - const getFileMenuComponent = (props) => { - if (!shallowFileMenu) { - shallowFileMenu = shallow() - } - return shallowFileMenu + const baseProps = { + currentUser: { id: 'u1', displayName: 'Test user' }, + fileType: 'visualization', + fileObject: undefined, + onDelete, + onError, + onNew, + onOpen, + onRename, + onSave, + onSaveAs, + onShare, + onTranslate, } - beforeEach(() => { - shallowFileMenu = undefined - props = { - currentUser: { id: 'u1', displayName: 'Test user' }, - fileType: 'visualization', - fileObject: undefined, - onDelete, - onError, - onNew, - onOpen, - onRename, - onSave, - onSaveAs, - onShare, - onTranslate, - } - }) - - it('renders a button', () => { - expect(getFileMenuComponent(props).find('button')).toHaveLength(1) - }) - - it('renders some enabled buttons regardless of the access settings', () => { - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - const buttonLabels = ['New', 'Open…'] - - buttonLabels.forEach((buttonLabel) => - expect( - fileMenuComponent - .findWhere((n) => n.prop('label') === buttonLabel) - .prop('disabled') - ).toBe(undefined) - ) - }) - - it('renders some disabled buttons when no fileObject is present', () => { - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - const buttonLabels = [ - 'Save as…', - 'Rename…', - 'Translate…', - 'Share…', - 'Get link…', - 'Delete', - ] - - buttonLabels.forEach((buttonLabel) => - expect( - fileMenuComponent - .findWhere((n) => n.prop('label') === buttonLabel) - .prop('disabled') - ).toBe(true) - ) - }) - - it('renders some enabled buttons when update access is granted', () => { - props.fileObject = { + const fullAccessProps = { + fileObject: { id: 'test', access: { - delete: false, - manage: false, + delete: true, + manage: true, update: true, }, + href: 'http://dhis2.org', + }, + } + + const renderFileMenu = (customProps = {}) => { + const props = { ...baseProps, ...customProps } + const providerData = { + translations: { + translations: {}, + }, + sharing: { + meta: { + allowPublicAccess: true, + }, + object: { + userAccesses: [], + userGroupAccesses: [], + }, + }, } - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + return render( + + + + + + ) + } - const buttonLabels = ['Save', 'Rename…', 'Translate…'] + const openDropdown = async () => { + fireEvent.click(screen.getByTestId('dhis2-analytics-hovermenudropdown')) + expect(await screen.findByTestId('file-menu-container')).toBeVisible() + } - buttonLabels.forEach((buttonLabel) => - expect( - fileMenuComponent - .findWhere((n) => n.prop('label') === buttonLabel) - .prop('disabled') - ).toBe(false) - ) - }) + const MENU_ITEMS = { + NEW: { testId: 'file-menu-new', text: 'New' }, + OPEN: { testId: 'file-menu-open', text: 'Open…' }, + SAVE: { testId: 'file-menu-save', text: 'Save' }, + SAVE_AS: { testId: 'file-menu-saveas', text: 'Save as…' }, + RENAME: { testId: 'file-menu-rename', text: 'Rename…' }, + TRANSLATE: { testId: 'file-menu-translate', text: 'Translate…' }, + SHARE: { testId: 'file-menu-sharing', text: 'Share…' }, + GET_LINK: { testId: 'file-menu-getlink', text: 'Get link…' }, + DELETE: { testId: 'file-menu-delete', text: 'Delete' }, + } - it('renders enabled Delete button when delete access is granted', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: false, - update: false, - }, - } + const assertMenuItemsDisabledState = (menuItems) => { + for (const menuTitem of menuItems) { + const li = screen.getByTestId(menuTitem.testId) + expect(getByText(li, menuTitem.text)).toBeVisible() - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + if (menuTitem.disabled) { + expect(li).toHaveClass('disabled') + } else { + expect(li).not.toHaveClass('disabled') + } + } + } + it('renders a button', () => { + renderFileMenu() expect( - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Delete') - .prop('disabled') - ).toBe(false) - }) + screen.getAllByTestId('dhis2-analytics-hovermenudropdown') + ).toHaveLength(1) - it('renders enabled Share button when manage access is granted', () => { - props.fileObject = { - id: 'test', - access: { - delete: false, - manage: true, - update: false, - }, - } + const button = screen.getByTestId('dhis2-analytics-hovermenudropdown') + expect(button).toBeVisible() + expect(button).toHaveTextContent('File') + }) - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + it('opens when clicking the button', async () => { + renderFileMenu() expect( - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Share…') - .prop('disabled') - ).toBe(false) + screen.queryByTestId('file-menu-container') + ).not.toBeInTheDocument() + + await openDropdown() + + expect(await screen.findByTestId('file-menu-container')).toBeVisible() }) - it('renders the OpenFileDialog component when the Open button is clicked', () => { - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + it('renders some enabled buttons regardless of the access settings', async () => { + renderFileMenu() + await openDropdown() - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Open…') - .simulate('click') + assertMenuItemsDisabledState([ + { ...MENU_ITEMS.NEW, disabled: false }, + { ...MENU_ITEMS.OPEN, disabled: false }, + ]) + }) - expect(fileMenuComponent.find(OpenFileDialog)).toHaveLength(1) + it('renders some disabled buttons when no fileObject is present', async () => { + renderFileMenu() + await openDropdown() + + assertMenuItemsDisabledState([ + { ...MENU_ITEMS.SAVE_AS, disabled: true }, + { ...MENU_ITEMS.RENAME, disabled: true }, + { ...MENU_ITEMS.TRANSLATE, disabled: true }, + { ...MENU_ITEMS.SHARE, disabled: true }, + { ...MENU_ITEMS.GET_LINK, disabled: true }, + { ...MENU_ITEMS.DELETE, disabled: true }, + ]) }) - it('renders the RenameDialog when the Rename button is clicked', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, + it('renders some enabled buttons when update access is granted', async () => { + const customProps = { + fileObject: { + id: 'test', + access: { + delete: false, + manage: false, + update: true, + }, }, } - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + renderFileMenu(customProps) + await openDropdown() - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Rename…') - .simulate('click') - - expect(fileMenuComponent.find(RenameDialog)).toHaveLength(1) + assertMenuItemsDisabledState([ + { ...MENU_ITEMS.SAVE, disabled: false }, + { ...MENU_ITEMS.RENAME, disabled: false }, + { ...MENU_ITEMS.TRANSLATE, disabled: false }, + ]) }) - it('renders the TranslationDialog when the Translate button is clicked', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, + it('renders enabled Delete button when delete access is granted', async () => { + const customProps = { + fileObject: { + id: 'test', + access: { + delete: true, + manage: false, + update: false, + }, }, } - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Translate…') - .simulate('click') + renderFileMenu(customProps) + await openDropdown() - expect(fileMenuComponent.find(TranslationDialog)).toHaveLength(1) + assertMenuItemsDisabledState([ + { ...MENU_ITEMS.DELETE, disabled: false }, + ]) }) - it('renders the SharingDialog when the Share button is clicked', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, + it('renders enabled Share button when manage access is granted', async () => { + const customProps = { + fileObject: { + id: 'test', + access: { + delete: false, + manage: true, + update: false, + }, }, } - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + renderFileMenu(customProps) + await openDropdown() + + assertMenuItemsDisabledState([{ ...MENU_ITEMS.SHARE, disabled: false }]) + }) - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Share…') - .simulate('click') + it('renders the OpenFileDialog component when the Open button is clicked', async () => { + renderFileMenu() + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.OPEN.testId)) - expect(fileMenuComponent.find(SharingDialog)).toHaveLength(1) + expect( + await screen.findByText('Open a visualization', { selector: 'h1' }) + ).toBeVisible() }) - it('renders the GetLinkDialog when the Get link button is clicked', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, - }, - } + it('renders the RenameDialog when the Rename button is clicked', async () => { + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.RENAME.testId)) - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + expect( + await screen.findByText('Rename visualization', { selector: 'h1' }) + ).toBeVisible() + }) - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Get link…') - .simulate('click') + it('renders the TranslationDialog when the Translate button is clicked', async () => { + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.TRANSLATE.testId)) - expect(fileMenuComponent.find(GetLinkDialog)).toHaveLength(1) + expect( + await screen.findByText('Translate', { + exact: false, + selector: 'h1', + }) + ).toBeVisible() }) - it('renders the DeleteDialog when the Delete button is clicked', () => { - props.fileObject = { - id: 'delete-test', - access: { - delete: true, - manage: true, - update: true, - }, - } + it('renders the SharingDialog when the Share button is clicked', async () => { + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.SHARE.testId)) - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + expect( + await screen.findByText('Sharing and access', { selector: 'h1' }) + ).toBeVisible() + }) - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Delete') - .simulate('click') + it('renders the GetLinkDialog when the Get link button is clicked', async () => { + const url = 'http://localhost/dhis-web-data-visualizer/#/test' - expect(fileMenuComponent.find(DeleteDialog)).toHaveLength(1) + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.GET_LINK.testId)) + + expect(await screen.findByTestId('dhis2-uicore-modal')).toBeVisible() + expect(screen.getByRole('link', { name: url })).toHaveAttribute( + 'href', + url + ) }) - it('renders the SaveAsDialog when the Save as… button is clicked', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, + it('renders the DeleteDialog when the Delete button is clicked', async () => { + const customProps = { + fileObject: { + id: 'delete-test', + access: { + delete: true, + manage: true, + update: true, + }, }, } - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Save as…') - .simulate('click') + renderFileMenu(customProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.DELETE.testId)) - expect(fileMenuComponent.find(SaveAsDialog)).toHaveLength(1) + expect( + await screen.findByText('Delete visualization', { selector: 'h1' }) + ).toBeVisible() }) - it('renders the SaveAsDialog when the Save… button is clicked but no fileObject is present', () => { - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Save…') - .simulate('click') + it('renders the SaveAsDialog when the Save as… button is clicked', async () => { + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE_AS.testId)) - expect(fileMenuComponent.find(SaveAsDialog)).toHaveLength(1) + expect( + await screen.findByText('Save visualization as', { selector: 'h1' }) + ).toBeVisible() }) - it('calls the onSave callback when the Save button is clicked and a fileObject is present', () => { - props.fileObject = { - id: 'test', - access: { - delete: true, - manage: true, - update: true, + it('renders the SaveAsDialog when the Save… button is clicked but no fileObject is present', async () => { + const customProps = { + fileObject: { + // NOTE: no `id` field + access: { + update: true, + }, }, } + renderFileMenu(customProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE.testId)) - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') + expect( + await screen.findByText('Save visualization as', { selector: 'h1' }) + ).toBeVisible() + }) - fileMenuComponent - .findWhere((n) => n.prop('label') === 'Save') - .simulate('click') + it('calls the onSave callback when the Save button is clicked and a fileObject is present', async () => { + renderFileMenu(fullAccessProps) + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.SAVE.testId)) - expect(fileMenuComponent.find(OpenFileDialog).prop('open')).toBe(false) - expect(onSave).toHaveBeenCalled() + expect(screen.queryByText('Open a visualization')).not.toBeVisible() + expect(onSave).toHaveBeenCalledTimes(1) }) - it('calls the onNew callback when the New button is clicked', () => { - const fileMenuComponent = getFileMenuComponent(props) - fileMenuComponent.find('button').simulate('click') - - fileMenuComponent - .findWhere((n) => n.prop('label') === 'New') - .simulate('click') + it('calls the onNew callback when the New button is clicked', async () => { + renderFileMenu() + await openDropdown() + fireEvent.click(screen.getByTestId(MENU_ITEMS.NEW.testId)) - expect(fileMenuComponent.find(OpenFileDialog).prop('open')).toBe(false) - expect(onNew).toHaveBeenCalled() + expect(screen.queryByText('Open a visualization')).not.toBeVisible() + expect(onNew).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/Options/VisualizationOptions.js b/src/components/Options/VisualizationOptions.js index 7edc2c2f1..3bb3e3174 100644 --- a/src/components/Options/VisualizationOptions.js +++ b/src/components/Options/VisualizationOptions.js @@ -30,8 +30,13 @@ import { tabSectionOptionIcon, } from './styles/VisualizationOptions.style.js' -const VisualizationOptions = ({ optionsConfig, onClose, onUpdate }) => { - const [activeTabKey, setActiveTabKey] = useState() +const VisualizationOptions = ({ + initiallyActiveTabKey, + optionsConfig, + onClose, + onUpdate, +}) => { + const [activeTabKey, setActiveTabKey] = useState(initiallyActiveTabKey) const generateTabContent = (sections) => sections.map(({ key, label, content, helpText }) => ( @@ -144,6 +149,7 @@ const VisualizationOptions = ({ optionsConfig, onClose, onUpdate }) => { VisualizationOptions.propTypes = { optionsConfig: PropTypes.array.isRequired, + initiallyActiveTabKey: PropTypes.string, onClose: PropTypes.func, onUpdate: PropTypes.func, } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuBar.js b/src/components/Toolbar/HoverMenuBar/HoverMenuBar.js new file mode 100644 index 000000000..055694e78 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuBar.js @@ -0,0 +1,118 @@ +import PropTypes from 'prop-types' +import React, { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from 'react' + +const throwErrorIfNotInitialized = () => { + throw new Error('`HoverMenubarContext` has not been initialised') +} + +const HoverMenubarContext = createContext({ + closeMenu: throwErrorIfNotInitialized, + onDropDownButtonClick: throwErrorIfNotInitialized, + onDropDownButtonMouseOver: throwErrorIfNotInitialized, + setLastHoveredSubMenuEl: throwErrorIfNotInitialized, + openedDropdownEl: null, +}) + +const useHoverMenubarContext = () => useContext(HoverMenubarContext) + +const HoverMenuBar = ({ children, dataTest }) => { + const [openedDropdownEl, setOpenedDropdownEl] = useState(null) + const [lastHoveredSubMenuEl, setLastHoveredSubMenuEl] = useState(null) + const [isInHoverMode, setIsInHoverMode] = useState(false) + + const closeMenu = useCallback(() => { + setIsInHoverMode(false) + setOpenedDropdownEl(null) + }, []) + + const onDocumentClick = useCallback( + (event) => { + const isClickOnOpenedSubMenuAnchor = + lastHoveredSubMenuEl && + (lastHoveredSubMenuEl === event.target || + lastHoveredSubMenuEl.contains(event.target)) + + if (!isClickOnOpenedSubMenuAnchor) { + closeMenu() + } + }, + [closeMenu, lastHoveredSubMenuEl] + ) + + const onDropDownButtonClick = useCallback( + (event) => { + if (!isInHoverMode) { + setIsInHoverMode(true) + setOpenedDropdownEl(event.currentTarget) + } else { + closeMenu() + } + }, + [closeMenu, isInHoverMode] + ) + + const onDropDownButtonMouseOver = useCallback( + (event) => { + if (isInHoverMode) { + setOpenedDropdownEl(event.currentTarget) + } + }, + [isInHoverMode] + ) + + const closeMenuWithEsc = useCallback( + (event) => { + if (event.keyCode === 27) { + closeMenu() + } + }, + [closeMenu] + ) + + useEffect(() => { + if (isInHoverMode) { + document.addEventListener('click', onDocumentClick, { + once: true, + }) + } + + return () => { + document.removeEventListener('click', onDocumentClick) + } + }, [onDocumentClick, isInHoverMode]) + + return ( + +
    + {children} + +
    +
    + ) +} + +HoverMenuBar.defaultProps = { + dataTest: 'dhis2-analytics-hovermenubar', +} + +HoverMenuBar.propTypes = { + children: PropTypes.node.isRequired, + dataTest: PropTypes.string, +} +export { HoverMenuBar, useHoverMenubarContext } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js new file mode 100644 index 000000000..1664d1473 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js @@ -0,0 +1,49 @@ +import { Popper } from '@dhis2-ui/popper' +import { Portal } from '@dhis2-ui/portal' +import PropTypes from 'prop-types' +import React, { useRef } from 'react' +import menuButtonStyles from '../MenuButton.styles.js' +import { useHoverMenubarContext } from './HoverMenuBar.js' + +export const HoverMenuDropdown = ({ children, label, dataTest, disabled }) => { + const buttonRef = useRef() + const { + onDropDownButtonClick, + onDropDownButtonMouseOver, + openedDropdownEl, + } = useHoverMenubarContext() + const isOpen = openedDropdownEl === buttonRef.current + + return ( + <> + + {isOpen && ( + + + {children} + + + )} + + ) +} + +HoverMenuDropdown.defaultProps = { + dataTest: 'dhis2-analytics-hovermenudropdown', +} + +HoverMenuDropdown.propTypes = { + children: PropTypes.node.isRequired, + label: PropTypes.node.isRequired, + dataTest: PropTypes.string, + disabled: PropTypes.bool, +} diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuList.js b/src/components/Toolbar/HoverMenuBar/HoverMenuList.js new file mode 100644 index 000000000..a9ae02b84 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuList.js @@ -0,0 +1,97 @@ +import { colors, elevations, spacers } from '@dhis2/ui-constants' +import PropTypes from 'prop-types' +import React, { createContext, useCallback, useContext, useState } from 'react' +import { useHoverMenubarContext } from './HoverMenuBar.js' + +const throwErrorIfNotInitialized = () => { + throw new Error('`HoverMenuListContext` has not been initialised') +} + +const HoverMenuListContext = createContext({ + onSubmenuAnchorMouseEnter: throwErrorIfNotInitialized, + onMenuItemMouseEnter: throwErrorIfNotInitialized, + openedSubMenuEl: null, + dense: false, +}) + +const useHoverMenuListContext = () => useContext(HoverMenuListContext) + +const HoverMenuList = ({ + children, + className, + dataTest, + dense, + maxHeight, + maxWidth, +}) => { + const { setLastHoveredSubMenuEl } = useHoverMenubarContext() + const [openedSubMenuEl, setOpenedSubMenuEl] = useState(null) + + const onSubmenuAnchorMouseEnter = useCallback( + (event) => { + if (openedSubMenuEl !== event.currentTarget) { + setOpenedSubMenuEl(event.currentTarget) + setLastHoveredSubMenuEl(event.currentTarget) + } + }, + [openedSubMenuEl, setLastHoveredSubMenuEl] + ) + + const onMenuItemMouseEnter = useCallback(() => { + setOpenedSubMenuEl(null) + setLastHoveredSubMenuEl(null) + }, [setLastHoveredSubMenuEl]) + + return ( + +
      + {children} + +
    +
    + ) +} + +HoverMenuList.defaultProps = { + dataTest: 'dhis2-analytics-hovermenulist', + maxWidth: '380px', + maxHeight: 'auto', +} + +HoverMenuList.propTypes = { + /** Typically `MenuItem`, `MenuDivider`, and `MenuSectionHeader` */ + children: PropTypes.node, + className: PropTypes.string, + dataTest: PropTypes.string, + /** Gives all HoverMenuListItem children a dense style */ + dense: PropTypes.bool, + maxHeight: PropTypes.string, + maxWidth: PropTypes.string, +} + +export { HoverMenuList, useHoverMenuListContext } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js new file mode 100644 index 000000000..a99bd4cc5 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js @@ -0,0 +1,95 @@ +import { Popper } from '@dhis2-ui/popper' +import { Portal } from '@dhis2-ui/portal' +import { IconChevronRight24 } from '@dhis2/ui-icons' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React, { useRef } from 'react' +import { HoverMenuList, useHoverMenuListContext } from './HoverMenuList.js' +import styles from './HoverMenuListItem.styles.js' + +const HoverMenuListItem = ({ + onClick, + children, + icon, + className, + destructive, + disabled, + dataTest, + label, +}) => { + const ref = useRef() + const { + onSubmenuAnchorMouseEnter, + onMenuItemMouseEnter, + openedSubMenuEl, + dense, + } = useHoverMenuListContext() + + const isSubMenuOpen = openedSubMenuEl === ref.current + + return ( + <> +
  • + {icon && {icon}} + + {label} + + {!!children && ( + + + + )} + + +
  • + {children && isSubMenuOpen && ( + + + {children} + + + )} + + ) +} + +HoverMenuListItem.defaultProps = { + dataTest: 'dhis2-uicore-hovermenulistitem', +} + +HoverMenuListItem.propTypes = { + // Nested menu items become submenus + children: PropTypes.node, + className: PropTypes.string, + dataTest: PropTypes.string, + destructive: PropTypes.bool, + disabled: PropTypes.bool, + /** An icon for the left side of the menu item */ + icon: PropTypes.node, + /** Text in the menu item */ + label: PropTypes.node, + /** Click handler */ + onClick: PropTypes.func, +} + +export { HoverMenuListItem } diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js new file mode 100644 index 000000000..31748d2a5 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js @@ -0,0 +1,91 @@ +import { colors, spacers } from '@dhis2/ui-constants' +import css from 'styled-jsx/css' + +export default css` + li { + display: flex; + align-items: center; + padding: 0px ${spacers.dp24}; + cursor: pointer; + list-style: none; + background-color: ${colors.white}; + color: ${colors.grey900}; + fill: ${colors.grey900}; + font-size: 14px; + line-height: 16px; + user-select: none; + } + + li:hover { + background-color: ${colors.grey200}; + } + + li:active, + li.active { + background-color: ${colors.grey300}; + } + + li.destructive { + color: ${colors.red700}; + fill: ${colors.red600}; + } + + li.destructive:hover { + background-color: ${colors.red050}; + } + + li.destructive:active, + li.destructive.active { + background-color: ${colors.red100}; + } + + li.disabled { + cursor: not-allowed; + color: ${colors.grey500}; + fill: ${colors.grey500}; + } + + li.disabled:hover { + background-color: ${colors.white}; + } + + .label { + flex-grow: 1; + padding: ${spacers.dp12} 0; + } + + li.dense .label { + padding: ${spacers.dp8} 0; + } + + .icon { + flex-grow: 0; + margin-right: ${spacers.dp12}; + width: 24px; + height: 24px; + } + + .chevron { + display: flex; + align-items: center; + flex-grow: 0; + margin-left: ${spacers.dp24}; + } + + li.dense .icon { + margin-right: ${spacers.dp8}; + width: 16px; + height: 16px; + } + + li .icon > :global(svg) { + width: 24px; + height: 24px; + } + + li.dense .icon > :global(svg), + li .chevron > :global(svg) { + width: 16px; + height: 16px; + } +` diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js new file mode 100644 index 000000000..9ae361ca4 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuBar.spec.js @@ -0,0 +1,256 @@ +import '@testing-library/jest-dom' +import { render, fireEvent, screen } from '@testing-library/react' +import { shallow } from 'enzyme' +import React from 'react' +import { + HoverMenuBar, + HoverMenuDropdown, + HoverMenuList, + HoverMenuListItem, +} from '../index.js' + +describe('', () => { + it('renders children', () => { + const childNode = 'text node' + const wrapper = shallow({childNode}) + + expect(wrapper.containsMatchingElement(childNode)).toBe(true) + }) + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow( + children + ) + + expect(wrapper.find('div').prop('data-test')).toBe(dataTest) + }) + + describe('mouse interactions', () => { + it('does not open on hover before a dropdown anchor is clicked', async () => { + createFullMenuBarWrapper() + fireEvent.mouseOver(screen.getByText('Menu A')) + + await expectMenuItemsInDocument([ + ['Menu item A.1', false], + ['Menu item A.2', false], + ['Menu item A.3', false], + ]) + }) + it('does not open when a disabled dropdown anchor is clicked', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu C')) + + await expectMenuItemsInDocument([ + ['Menu item A.1', false], + ['Menu item A.2', false], + ['Menu item A.3', false], + ]) + }) + it('opens menu list when clicked', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu A')) + + await expectMenuItemsInDocument([ + ['Menu item A.1', true], + ['Menu item B.1', false], + ['Menu item C.1', false], + ]) + }) + it('responds to hover once open', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu A')) + fireEvent.mouseOver(screen.getByText('Menu B')) + + await expectMenuItemsInDocument([ + ['Menu item A.1', false], + ['Menu item B.1', true], + ['Menu item C.1', false], + ]) + }) + it('does not open disabled dropdown on hover in hover mode', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu B')) + fireEvent.mouseOver(screen.getByText('Menu C')) + + await expectMenuItemsInDocument([ + ['Menu item B.1', true], + ['Menu item C.1', false], + ]) + }) + it('opens submenus when in hover mode', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu B')) + fireEvent.mouseOver(screen.getByText('Menu item B.1')) + + await expectMenuItemsInDocument([ + ['Menu item B.1.1', true], + ['Menu item B.1.2', true], + ['Menu item B.1.3', true], + ['Menu item B.2.1', false], + ['Menu item B.2.2', false], + ['Menu item B.2.3', false], + ]) + + fireEvent.mouseOver(screen.getByText('Menu item B.2')) + + await expectMenuItemsInDocument([ + ['Menu item B.1.1', false], + ['Menu item B.1.2', false], + ['Menu item B.1.3', false], + ['Menu item B.2.1', true], + ['Menu item B.2.2', true], + ['Menu item B.2.3', true], + ]) + }) + it('does not open disabled submenus when in hover mode', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu B')) + fireEvent.mouseOver(screen.getByText('Menu item B.2')) + + await expectMenuItemsInDocument([ + ['Menu item B.2.1', true], + ['Menu item B.2.2', true], + ['Menu item B.2.3', true], + ['Menu item B.3.1', false], + ['Menu item B.3.2', false], + ['Menu item B.3.3', false], + ]) + + fireEvent.mouseOver(screen.getByText('Menu item B.3')) + + await expectMenuItemsInDocument([ + ['Menu item B.2.1', true], + ['Menu item B.2.2', true], + ['Menu item B.2.3', true], + ['Menu item B.3.1', false], + ['Menu item B.3.2', false], + ['Menu item B.3.3', false], + ]) + }) + it('closes when clicking on then document', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu A')) + + await expectMenuItemsInDocument([['Menu item A.1', true]]) + + fireEvent.click(document) + + await expectMenuItemsInDocument([['Menu item A.1', false]]) + }) + it('stays open when clicking a open submenu anchor', async () => { + createFullMenuBarWrapper() + fireEvent.click(screen.getByText('Menu B')) + + await expectMenuItemsInDocument([['Menu item B.1', true]]) + + fireEvent.mouseOver(screen.getByText('Menu item B.1')) + + await expectMenuItemsInDocument([ + ['Menu item B.1', true], + ['Menu item B.1.1', true], + ['Menu item B.1.2', true], + ['Menu item B.1.3', true], + ]) + + fireEvent.click(screen.getByText('Menu item B.1')) + + await expectMenuItemsInDocument([ + ['Menu item B.1', true], + ['Menu item B.1.1', true], + ['Menu item B.1.2', true], + ['Menu item B.1.3', true], + ]) + }) + it('calls the onClick of the menu item and closes when clicking a menu item', async () => { + const menuItemOnClickSpy = jest.fn() + createFullMenuBarWrapper({ menuItemOnClickSpy }) + fireEvent.click(screen.getByText('Menu A')) + + await expectMenuItemsInDocument([['Menu item A.1', true]]) + + fireEvent.click(screen.getByText('Menu item A.1')) + + expect(menuItemOnClickSpy).toHaveBeenCalledTimes(1) + await expectMenuItemsInDocument([['Menu item A.1', false]]) + }) + + it('calls the onClick of the menu item and closes when clicking a submenu item', async () => { + const subMenuItemOnClickSpy = jest.fn() + createFullMenuBarWrapper({ subMenuItemOnClickSpy }) + + fireEvent.click(screen.getByText('Menu B')) + await expectMenuItemsInDocument([['Menu item B.1', true]]) + + fireEvent.mouseOver(screen.getByText('Menu item B.1')) + await expectMenuItemsInDocument([['Menu item B.1.1', true]]) + + fireEvent.click(screen.getByText('Menu item B.1.1')) + + expect(subMenuItemOnClickSpy).toHaveBeenCalledTimes(1) + await expectMenuItemsInDocument([ + ['Menu item B.1', false], + ['Menu item B.1.1', false], + ['Menu item B.1.1', false], + ]) + }) + }) +}) + +function createFullMenuBarWrapper({ + menuItemOnClickSpy, + subMenuItemOnClickSpy, +} = {}) { + return render( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +async function expectMenuItemsInDocument(items) { + for (const [text, inDocument] of items) { + if (inDocument) { + expect(await screen.findByText(text)).toBeInTheDocument() + } else { + expect(screen.queryByText(text)).not.toBeInTheDocument() + } + } +} diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js new file mode 100644 index 000000000..fa050d1f4 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuDropdown.spec.js @@ -0,0 +1,20 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { HoverMenuDropdown } from '../index.js' + +describe('', () => { + /* Most of the props for this component are included + * in the mouse interaction tests for the HoverMenuBar. + * Only the `dataTest` prop needs to be verified here. */ + + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow( + + children + + ) + + expect(wrapper.find('button').prop('data-test')).toBe(dataTest) + }) +}) diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js new file mode 100644 index 000000000..9b79d9222 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuList.spec.js @@ -0,0 +1,56 @@ +import { shallow, mount } from 'enzyme' +import React from 'react' +import { HoverMenuList, HoverMenuListItem } from '../index.js' + +describe('', () => { + const dataTest = 'test' + const childNode = 'children' + + it('renders children', () => { + const wrapper = shallow({childNode}) + expect(wrapper.containsMatchingElement(childNode)).toBe(true) + }) + it('accept a `className` prop', () => { + const className = 'className' + const wrapper = shallow( + {childNode} + ) + expect(wrapper.find('ul')).toHaveClassName(className) + }) + + it('accepts a `dataTest` prop', () => { + const wrapper = shallow( + {childNode} + ) + + expect(wrapper.find('ul').prop('data-test')).toBe(dataTest) + }) + + it('accept a `dense` prop', () => { + const wrapper = mount( + + + + + ) + + expect(wrapper.find('li').first()).toHaveClassName('dense') + expect(wrapper.find('li').last()).toHaveClassName('dense') + }) + it('accept a `maxHeight` prop', () => { + const maxHeight = '100000px' + const wrapper = shallow( + {childNode} + ) + expect(wrapper.find('style').text()).toContain( + `max-height: ${maxHeight}` + ) + }) + it('accept a `maxWidth` prop', () => { + const maxWidth = '100000px' + const wrapper = shallow( + {childNode} + ) + expect(wrapper.find('style').text()).toContain(`max-width: ${maxWidth}`) + }) +}) diff --git a/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js new file mode 100644 index 000000000..4c2503619 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/__tests__/HoverMenuListItem.spec.js @@ -0,0 +1,39 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { HoverMenuListItem } from '../index.js' + +describe('', () => { + /* Some of the props for this component are included + * in the mouse interaction tests for the HoverMenuBar. + * Only the `className`, `dataTest`, `destructive` and + * `icon` prop need to be verified here. */ + + it('accepts a `className` prop', () => { + const className = 'className' + const wrapper = shallow() + + expect(wrapper.find('li')).toHaveClassName(className) + }) + + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow() + + expect(wrapper.find('li').prop('data-test')).toBe(dataTest) + }) + + it('accepts a `destructive` prop', () => { + const wrapper = shallow() + + expect(wrapper.find('li')).toHaveClassName('destructive') + }) + it('accepts an `icon` prop', () => { + const iconText = 'I am an icon' + const icon = {iconText} + const wrapper = shallow() + + expect(wrapper.find('span.icon')).toExist() + expect(wrapper.find('span#testicon')).toExist() + expect(wrapper.find('span#testicon').text()).toBe(iconText) + }) +}) diff --git a/src/components/Toolbar/HoverMenuBar/index.js b/src/components/Toolbar/HoverMenuBar/index.js new file mode 100644 index 000000000..8fb29dba3 --- /dev/null +++ b/src/components/Toolbar/HoverMenuBar/index.js @@ -0,0 +1,4 @@ +export { HoverMenuBar } from './HoverMenuBar.js' +export { HoverMenuDropdown } from './HoverMenuDropdown.js' +export { HoverMenuList } from './HoverMenuList.js' +export { HoverMenuListItem } from './HoverMenuListItem.js' diff --git a/src/components/Toolbar/InterpretationsAndDetailsToggler.js b/src/components/Toolbar/InterpretationsAndDetailsToggler.js new file mode 100644 index 000000000..8bb8abeb3 --- /dev/null +++ b/src/components/Toolbar/InterpretationsAndDetailsToggler.js @@ -0,0 +1,34 @@ +import i18n from '@dhis2/d2-i18n' +import { IconChevronRight24, IconChevronLeft24 } from '@dhis2/ui' +import PropTypes from 'prop-types' +import React from 'react' +import menuButtonStyles from './MenuButton.styles.js' + +export const InterpretationsAndDetailsToggler = ({ + onClick, + dataTest, + disabled, + isShowing, +}) => ( + +) + +InterpretationsAndDetailsToggler.defaultProps = { + dataTest: 'dhis2-analytics-interpretationsanddetailstoggler', +} + +InterpretationsAndDetailsToggler.propTypes = { + onClick: PropTypes.func.isRequired, + dataTest: PropTypes.string, + disabled: PropTypes.bool, + isShowing: PropTypes.bool, +} diff --git a/src/components/Toolbar/MenuButton.styles.js b/src/components/Toolbar/MenuButton.styles.js new file mode 100644 index 000000000..7522ca1d7 --- /dev/null +++ b/src/components/Toolbar/MenuButton.styles.js @@ -0,0 +1,37 @@ +import { colors, spacers, theme } from '@dhis2/ui-constants' +import css from 'styled-jsx/css' + +export default css` + button { + all: unset; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 14px; + line-height: 14px; + padding: 0 ${spacers.dp12}; + color: ${colors.grey900}; + transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + cursor: pointer; + } + + button:hover:enabled, + button:active { + background-color: ${colors.grey200}; + } + + button:focus { + outline: 3px solid ${theme.focus}; + outline-offset: -3px; + } + + /* Prevent focus styles when mouse clicking */ + button:focus:not(:focus-visible) { + outline: none; + } + + button:disabled { + color: ${colors.grey500}; + cursor: not-allowed; + } +` diff --git a/src/components/Toolbar/Toolbar.js b/src/components/Toolbar/Toolbar.js new file mode 100644 index 000000000..f6dcce5e7 --- /dev/null +++ b/src/components/Toolbar/Toolbar.js @@ -0,0 +1,29 @@ +import { colors } from '@dhis2/ui-constants' +import PropTypes from 'prop-types' +import React from 'react' + +export const Toolbar = ({ children, dataTest }) => ( +
    + {children} + +
    +) + +Toolbar.defaultProps = { + dataTest: 'dhis2-analytics-toolbar', +} + +Toolbar.propTypes = { + children: PropTypes.node, + dataTest: PropTypes.string, +} diff --git a/src/components/Toolbar/ToolbarSidebar.js b/src/components/Toolbar/ToolbarSidebar.js new file mode 100644 index 000000000..948a9fbed --- /dev/null +++ b/src/components/Toolbar/ToolbarSidebar.js @@ -0,0 +1,31 @@ +import { colors } from '@dhis2/ui-constants' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React from 'react' + +export const ToolbarSidebar = ({ children, dataTest, isHidden }) => ( +
    + {children} + +
    +) + +ToolbarSidebar.defaultProps = { + dataTest: 'dhis2-analytics-toolbarsidebar', +} + +ToolbarSidebar.propTypes = { + children: PropTypes.node, + dataTest: PropTypes.string, + isHidden: PropTypes.bool, +} diff --git a/src/components/Toolbar/UpdateButton.js b/src/components/Toolbar/UpdateButton.js new file mode 100644 index 000000000..fdfaa06e7 --- /dev/null +++ b/src/components/Toolbar/UpdateButton.js @@ -0,0 +1,39 @@ +import { CircularLoader } from '@dhis2-ui/loader' +import i18n from '@dhis2/d2-i18n' +import { colors } from '@dhis2/ui-constants' +import { IconSync16 } from '@dhis2/ui-icons' +import PropTypes from 'prop-types' +import React from 'react' +import menuButtonStyles from './MenuButton.styles.js' + +export const UpdateButton = ({ onClick, disabled, loading, dataTest }) => ( + +) + +UpdateButton.defaultProps = { + dataTest: 'dhis2-analytics-updatebutton', +} + +UpdateButton.propTypes = { + onClick: PropTypes.func.isRequired, + dataTest: PropTypes.string, + disabled: PropTypes.bool, + loading: PropTypes.bool, +} diff --git a/src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js b/src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js new file mode 100644 index 000000000..4477e19d8 --- /dev/null +++ b/src/components/Toolbar/__tests__/InterpretationsAndDetailsToggler.spec.js @@ -0,0 +1,50 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { InterpretationsAndDetailsToggler } from '../index.js' + +describe('', () => { + const noop = () => {} + + it('accepts an `onClick` prop', () => { + const onClick = jest.fn() + const wrapper = shallow( + + ) + + wrapper.simulate('click') + + expect(onClick).toHaveBeenCalledTimes(1) + }) + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow( + + ) + + expect(wrapper.prop('data-test')).toBe(dataTest) + }) + it('accepts a `disabled` prop', () => { + const wrapper = shallow( + + ) + + expect(wrapper.find('button').prop('disabled')).toEqual(true) + }) + it('accepts an `isShowing` prop', () => { + const wrapper = shallow( + + ) + const wrapperWithIsShowing = shallow( + + ) + + expect(wrapper.find('SvgChevronRight24')).toHaveLength(0) + expect(wrapper.find('SvgChevronLeft24')).toHaveLength(1) + + expect(wrapperWithIsShowing.find('SvgChevronRight24')).toHaveLength(1) + expect(wrapperWithIsShowing.find('SvgChevronLeft24')).toHaveLength(0) + }) +}) diff --git a/src/components/Toolbar/__tests__/Toolbar.spec.js b/src/components/Toolbar/__tests__/Toolbar.spec.js new file mode 100644 index 000000000..bfd65785a --- /dev/null +++ b/src/components/Toolbar/__tests__/Toolbar.spec.js @@ -0,0 +1,18 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { Toolbar } from '../index.js' + +describe('', () => { + it('renders children', () => { + const childNode = 'text node' + const wrapper = shallow({childNode}) + + expect(wrapper.containsMatchingElement(childNode)).toBe(true) + }) + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow() + + expect(wrapper.prop('data-test')).toBe(dataTest) + }) +}) diff --git a/src/components/Toolbar/__tests__/ToolbarSidebar.spec.js b/src/components/Toolbar/__tests__/ToolbarSidebar.spec.js new file mode 100644 index 000000000..7801ec550 --- /dev/null +++ b/src/components/Toolbar/__tests__/ToolbarSidebar.spec.js @@ -0,0 +1,23 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { ToolbarSidebar } from '../index.js' + +describe('', () => { + it('renders children', () => { + const childNode = 'text node' + const wrapper = shallow({childNode}) + + expect(wrapper.containsMatchingElement(childNode)).toBe(true) + }) + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow() + + expect(wrapper.prop('data-test')).toBe(dataTest) + }) + it('accepts a `isHidden` prop', () => { + const wrapper = shallow() + + expect(wrapper.find('div').hasClass('isHidden')).toEqual(true) + }) +}) diff --git a/src/components/Toolbar/__tests__/UpdateButton.spec.js b/src/components/Toolbar/__tests__/UpdateButton.spec.js new file mode 100644 index 000000000..3be73c6b4 --- /dev/null +++ b/src/components/Toolbar/__tests__/UpdateButton.spec.js @@ -0,0 +1,34 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { UpdateButton } from '../index.js' + +describe('', () => { + const noop = () => {} + + it('accepts an `onClick` prop', () => { + const onClick = jest.fn() + const wrapper = shallow() + + wrapper.simulate('click') + + expect(onClick).toHaveBeenCalledTimes(1) + }) + it('accepts a `dataTest` prop', () => { + const dataTest = 'test' + const wrapper = shallow( + + ) + + expect(wrapper.prop('data-test')).toBe(dataTest) + }) + it('accepts a `disabled` prop', () => { + const wrapper = shallow() + + expect(wrapper.find('button').prop('disabled')).toEqual(true) + }) + it('accepts an `loading` prop', () => { + const wrapper = shallow() + + expect(wrapper.find('CircularLoader')).toHaveLength(1) + }) +}) diff --git a/src/components/Toolbar/index.js b/src/components/Toolbar/index.js new file mode 100644 index 000000000..86d6df30a --- /dev/null +++ b/src/components/Toolbar/index.js @@ -0,0 +1,5 @@ +export { InterpretationsAndDetailsToggler } from './InterpretationsAndDetailsToggler.js' +export { Toolbar } from './Toolbar.js' +export { ToolbarSidebar } from './ToolbarSidebar.js' +export { UpdateButton } from './UpdateButton.js' +export * from './HoverMenuBar/index.js' diff --git a/src/index.js b/src/index.js index e21a38df7..069656b77 100644 --- a/src/index.js +++ b/src/index.js @@ -32,6 +32,8 @@ export { default as AboutAOUnit } from './components/AboutAOUnit/AboutAOUnit.js' export { InterpretationsUnit } from './components/Interpretations/InterpretationsUnit/InterpretationsUnit.js' export { InterpretationModal } from './components/Interpretations/InterpretationModal/InterpretationModal.js' +export * from './components/Toolbar/index.js' + export { TranslationDialog } from './components/TranslationDialog/index.js' export { OfflineTooltip } from './components/OfflineTooltip.js' diff --git a/yarn.lock b/yarn.lock index 77e2352c9..496096711 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.0.1": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.2.0.tgz#e1a84fca468f4b337816fcb7f0964beb620ba855" + integrity sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA== + "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -2658,6 +2663,13 @@ "@types/node" "*" jest-mock "^27.5.1" +"@jest/expect-utils@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" + integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== + dependencies: + jest-get-type "^29.4.3" + "@jest/fake-timers@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" @@ -2772,6 +2784,13 @@ terminal-link "^2.0.0" v8-to-istanbul "^8.1.0" +"@jest/schemas@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" + integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== + dependencies: + "@sinclair/typebox" "^0.25.16" + "@jest/source-map@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" @@ -2944,6 +2963,18 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jest/types@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" + integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== + dependencies: + "@jest/schemas" "^29.4.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -3157,6 +3188,11 @@ "@storybook/react" "^5.3.3" uuid "^3.1.0" +"@sinclair/typebox@^0.25.16": + version "0.25.24" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" + integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -4291,6 +4327,21 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/jest-dom@^5.16.5": + version "5.16.5" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" + integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== + dependencies: + "@adobe/css-tools" "^4.0.1" + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + "@testing-library/react@^12.1.2": version "12.1.2" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.2.tgz#f1bc9a45943461fa2a598bb4597df1ae044cfc76" @@ -4299,6 +4350,15 @@ "@babel/runtime" "^7.12.5" "@testing-library/dom" "^8.0.0" +"@testing-library/react@^12.1.5": + version "12.1.5" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" + integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^8.0.0" + "@types/react-dom" "<18.0.0" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -4460,6 +4520,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@*": + version "29.5.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.1.tgz#83c818aa9a87da27d6da85d3378e5a34d2f31a47" + integrity sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -4568,6 +4636,13 @@ "@types/react" "*" "@types/reactcss" "*" +"@types/react-dom@<18.0.0": + version "17.0.20" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.20.tgz#e0c8901469d732b36d8473b40b679ad899da1b53" + integrity sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA== + dependencies: + "@types/react" "^17" + "@types/react-syntax-highlighter@11.0.4": version "11.0.4" resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz#d86d17697db62f98046874f62fdb3e53a0bbc4cd" @@ -4590,6 +4665,15 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/react@^17": + version "17.0.60" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.60.tgz#a4a97dcdbebad76612c188fc06440e4995fd8ad2" + integrity sha512-pCH7bqWIfzHs3D+PDs3O/COCQJka+Kcw3RnO9rFA2zalqoXg7cNjJDh6mZ7oRtY1wmY4LVwDdAbA1F7Z8tv3BQ== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/reactcss@*": version "1.2.3" resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834" @@ -4611,6 +4695,11 @@ dependencies: "@types/node" "*" +"@types/scheduler@*": + version "0.16.3" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" + integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -4631,6 +4720,13 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA== +"@types/testing-library__jest-dom@^5.9.1": + version "5.14.6" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.6.tgz#4887f6e1af11215428ab02777873bcede98a53b0" + integrity sha512-FkHXCb+ikSoUP4Y4rOslzTdX5sqYwMxfefKh1GmZ8ce1GOkEHntSp6b5cGadmNfp5e4BMEWOMx+WSKd5/MqlDA== + dependencies: + "@types/jest" "*" + "@types/trusted-types@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" @@ -4700,6 +4796,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.8": + version "17.0.24" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" + integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@^4.5.0": version "4.13.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.13.0.tgz#5f580ea520fa46442deb82c038460c3dd3524bb6" @@ -7863,6 +7966,11 @@ css-what@^3.2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + css@^2.0.0: version "2.2.4" resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" @@ -7992,6 +8100,11 @@ csstype@^2.2.0, csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== +csstype@^3.0.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -8284,6 +8397,11 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== +diff-sequences@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" + integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== + diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -8367,6 +8485,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.5.6: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + dom-accessibility-api@^0.5.9: version "0.5.11" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.11.tgz#79d5846c4f90eba3e617d9031e921de9324f84ed" @@ -9347,6 +9470,17 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" +expect@^29.0.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" + integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== + dependencies: + "@jest/expect-utils" "^29.5.0" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-util "^29.5.0" + express@^4.17.0, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -11943,6 +12077,16 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-diff@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" + integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" + jest-docblock@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" @@ -12067,6 +12211,11 @@ jest-get-type@^27.5.1: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== +jest-get-type@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" + integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== + jest-haste-map@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" @@ -12210,6 +12359,16 @@ jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-matcher-utils@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" + integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== + dependencies: + chalk "^4.0.0" + jest-diff "^29.5.0" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" + jest-message-util@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" @@ -12254,6 +12413,21 @@ jest-message-util@^27.5.1: slash "^3.0.0" stack-utils "^2.0.3" +jest-message-util@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" + integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.5.0" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.5.0" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-mock@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" @@ -12586,6 +12760,18 @@ jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-util@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" + integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== + dependencies: + "@jest/types" "^29.5.0" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" @@ -15722,6 +15908,15 @@ pretty-format@^27.0.2, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^29.0.0, pretty-format@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" + integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== + dependencies: + "@jest/schemas" "^29.4.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -16279,6 +16474,11 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -19362,10 +19562,8 @@ watchpack@^1.7.4: resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: - chokidar "^3.4.1" graceful-fs "^4.1.2" neo-async "^2.5.0" - watchpack-chokidar2 "^2.0.1" optionalDependencies: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" From 6fc8cdaeb418eacfc87b97e7e2a71ad66cef247b Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 20 Jun 2023 09:33:00 +0000 Subject: [PATCH 105/285] chore(release): cut 26.0.0 [skip ci] # [26.0.0](https://github.com/dhis2/analytics/compare/v25.2.3...v26.0.0) (2023-06-20) ### Features * toolbar UI update with hoverable menu ([#1478](https://github.com/dhis2/analytics/issues/1478)) ([#1509](https://github.com/dhis2/analytics/issues/1509)) ([0a51153](https://github.com/dhis2/analytics/commit/0a5115327c0c0dbe9da22a092f2fc6002f798290)) ### BREAKING CHANGES * The `FileMenu` is now using the new `HoverMenuBar` components which makes this version of the `FileMenu` incompatible with the previous version. Apps will be need to update their toolbar and file menu before using this version of analytics. --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bae44c82..06b0d1996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# [26.0.0](https://github.com/dhis2/analytics/compare/v25.2.3...v26.0.0) (2023-06-20) + + +### Features + +* toolbar UI update with hoverable menu ([#1478](https://github.com/dhis2/analytics/issues/1478)) ([#1509](https://github.com/dhis2/analytics/issues/1509)) ([0a51153](https://github.com/dhis2/analytics/commit/0a5115327c0c0dbe9da22a092f2fc6002f798290)) + + +### BREAKING CHANGES + +* The `FileMenu` is now using the new `HoverMenuBar` components which makes this version of the `FileMenu` incompatible with the previous version. Apps will be need to update their toolbar and file menu before using this version of analytics. + ## [25.2.3](https://github.com/dhis2/analytics/compare/v25.2.2...v25.2.3) (2023-06-20) diff --git a/package.json b/package.json index 0bb5f0ea1..e27410eca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "25.2.3", + "version": "26.0.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 3bc8f34af26684653efc90dd2349a5c2358776a6 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 21 Jun 2023 03:46:25 +0200 Subject: [PATCH 106/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/es.po | 9 ++++++--- i18n/zh.po | 59 ++++++++++++++++++++++++++++-------------------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/i18n/es.po b/i18n/es.po index 57df0b0ff..44b4897da 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" +"POT-Creation-Date: 2023-05-24T12:55:52.925Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" "Last-Translator: Janeth Cruz, 2023\n" "Language-Team: Spanish (https://app.transifex.com/hisp-uio/teams/100509/es/)\n" @@ -881,17 +881,20 @@ msgstr "Años fiscales" msgid "Years" msgstr "Años" +msgid "Interpretations and details" +msgstr "Interpretaciones y detalles" + msgid "Translating to" msgstr "Traducir a" msgid "Choose a locale" -msgstr "" +msgstr "Elegir un idioma" msgid "Base locale reference" msgstr "" msgid "Choose a locale to translate from the menu above" -msgstr "" +msgstr "Elegir un idioma para traducir desde el menú de arriba" msgid "Translate: {{objectName}}" msgstr "Traducir: {{objectName}}" diff --git a/i18n/zh.po b/i18n/zh.po index a21e5fe34..31ab090ae 100644 --- a/i18n/zh.po +++ b/i18n/zh.po @@ -2,15 +2,15 @@ # Translators: # Viktor Varland , 2021 # phil_dhis2, 2022 -# easylin , 2022 # 晓东 林 <13981924470@126.com>, 2023 +# easylin , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" +"POT-Creation-Date: 2023-05-24T12:55:52.925Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: 晓东 林 <13981924470@126.com>, 2023\n" +"Last-Translator: easylin , 2023\n" "Language-Team: Chinese (https://app.transifex.com/hisp-uio/teams/100509/zh/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -83,45 +83,45 @@ msgid "Network error" msgstr "网络错误" msgid "Data / Edit calculation" -msgstr "" +msgstr "数据/编辑计算" msgid "Data / New calculation" -msgstr "" +msgstr "数据/新计算" msgid "Remove item" -msgstr "" +msgstr "除去项目" msgid "Check formula" -msgstr "" +msgstr "检查公式" msgid "Calculation name" -msgstr "" +msgstr "计算名称" msgid "Shown in table headers and chart axes/legends" -msgstr "" +msgstr "显示在表格标题和图表轴/图例中" msgid "Delete calculation" -msgstr "" +msgstr "删除计算" msgid "Cancel" msgstr "取消" msgid "The calculation can only be saved with a valid formula" -msgstr "" +msgstr "计算只能用有效的公式保存" msgid "Add a name to save this calculation" -msgstr "" +msgstr "添加名称以保存此计算" msgid "Save calculation" -msgstr "" +msgstr "保存计算" msgid "" "Are you sure you want to delete this calculation? It may be used by other " "visualizations." -msgstr "" +msgstr "您确定要删除此计算吗?它可以被其他可视化使用。" msgid "Yes, delete" -msgstr "" +msgstr "是的,删除" msgid "Totals only" msgstr "总计" @@ -136,7 +136,7 @@ msgid "Data elements" msgstr "数据元" msgid "Search by data element name" -msgstr "" +msgstr "按数据元素名称搜索" msgid "No data elements found for \"{{- searchTerm}}\"" msgstr "找不到“ {{-searchTerm}}”的数据元素" @@ -147,10 +147,10 @@ msgstr "找不到数据元素" msgid "" "Drag items here, or double click in the list, to start building a " "calculation formula" -msgstr "" +msgstr "将项目拖到此处,或在列表中双击,开始构建计算公式" msgid "Math operators" -msgstr "" +msgstr "数学运算符" msgid "Data Type" msgstr "数据类型" @@ -201,7 +201,7 @@ msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "找不到“ {{-searchTerm}}”" msgid "Calculation" -msgstr "" +msgstr "计算" msgid "Metric type" msgstr "尺度类型" @@ -321,7 +321,7 @@ msgid "Rename" msgstr "改名" msgid "{{- objectName}} (copy)" -msgstr "" +msgstr "{{- objectName}}(复制)" msgid "Save {{fileType}} as" msgstr "将{{fileType}}另存为" @@ -839,6 +839,9 @@ msgstr "Financial Years" msgid "Years" msgstr "年" +msgid "Interpretations and details" +msgstr "解释和细节" + msgid "Translating to" msgstr "翻译成" @@ -969,31 +972,31 @@ msgid "Program indicator" msgstr "项目指标" msgid "Calculations" -msgstr "" +msgstr "计算" msgid "Number" msgstr "数据" msgid "Formula is empty. Add items to the formula from the lists on the left." -msgstr "" +msgstr "公式为空。将左侧列表中的项目添加到公式中。" msgid "Consecutive math operators" -msgstr "" +msgstr "连续的数学运算符" msgid "Consecutive data elements" -msgstr "" +msgstr "连续的数据元素" msgid "Starts or ends with a math operator" -msgstr "" +msgstr "以数学运算符开始或结束" msgid "Empty parentheses" -msgstr "" +msgstr "空括号" msgid "Missing right parenthesis )" -msgstr "" +msgstr "缺少右括号 )" msgid "Missing left parenthesis (" -msgstr "" +msgstr "缺少左括号 (" msgid "Extra Small" msgstr "特小" From c33cd6e6933909e212ae5cc5e921ab3215b37133 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 21 Jun 2023 01:51:19 +0000 Subject: [PATCH 107/285] chore(release): cut 26.0.1 [skip ci] ## [26.0.1](https://github.com/dhis2/analytics/compare/v26.0.0...v26.0.1) (2023-06-21) ### Bug Fixes * **translations:** sync translations from transifex (master) ([3bc8f34](https://github.com/dhis2/analytics/commit/3bc8f34af26684653efc90dd2349a5c2358776a6)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b0d1996..c46d8e412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.0.1](https://github.com/dhis2/analytics/compare/v26.0.0...v26.0.1) (2023-06-21) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([3bc8f34](https://github.com/dhis2/analytics/commit/3bc8f34af26684653efc90dd2349a5c2358776a6)) + # [26.0.0](https://github.com/dhis2/analytics/compare/v25.2.3...v26.0.0) (2023-06-20) diff --git a/package.json b/package.json index e27410eca..f45017f74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.0.0", + "version": "26.0.1", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 28f299634a93fb422329720d6caf78e3a52955da Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 22 Jun 2023 03:48:37 +0200 Subject: [PATCH 108/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/nl.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/nl.po b/i18n/nl.po index ddad2f6b7..907898e52 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -444,7 +444,7 @@ msgid "Created by" msgstr "Gecreëerd door" msgid "Anyone" -msgstr "" +msgstr "Iedereen" msgid "Only you" msgstr "" @@ -493,7 +493,7 @@ msgid "" msgstr "" msgid "Create new" -msgstr "" +msgstr "Maak nieuw" msgid "Open a visualization" msgstr "" From 8aeaa2f8b261ff87067c0de109ce96bd02704af7 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 22 Jun 2023 01:54:38 +0000 Subject: [PATCH 109/285] chore(release): cut 26.0.2 [skip ci] ## [26.0.2](https://github.com/dhis2/analytics/compare/v26.0.1...v26.0.2) (2023-06-22) ### Bug Fixes * **translations:** sync translations from transifex (master) ([28f2996](https://github.com/dhis2/analytics/commit/28f299634a93fb422329720d6caf78e3a52955da)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c46d8e412..de4286671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.0.2](https://github.com/dhis2/analytics/compare/v26.0.1...v26.0.2) (2023-06-22) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([28f2996](https://github.com/dhis2/analytics/commit/28f299634a93fb422329720d6caf78e3a52955da)) + ## [26.0.1](https://github.com/dhis2/analytics/compare/v26.0.0...v26.0.1) (2023-06-21) diff --git a/package.json b/package.json index f45017f74..670020739 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.0.1", + "version": "26.0.2", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From d8f3864261cadef8f0a9609257eece49a8e9874b Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 23 Jun 2023 03:49:18 +0200 Subject: [PATCH 110/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/nl.po | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/i18n/nl.po b/i18n/nl.po index 907898e52..366dbb468 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -598,13 +598,13 @@ msgid "Nothing selected" msgstr "" msgid "User organisation unit" -msgstr "" +msgstr "Gebruikersorganisatie-eenheid" msgid "User sub-units" -msgstr "" +msgstr "Subeenheden van gebruikers" msgid "User sub-x2-units" -msgstr "" +msgstr "Gebruiker sub-x2-eenheden" msgid "Select a level" msgstr "Selecteer een niveau" @@ -883,10 +883,10 @@ msgid "Filter" msgstr "Filter" msgid "Columns" -msgstr "" +msgstr "Kolommen" msgid "Rows" -msgstr "" +msgstr "Rijen" msgid "Points" msgstr "" @@ -1006,13 +1006,13 @@ msgid "Extra Small" msgstr "" msgid "Small" -msgstr "" +msgstr "Klein" msgid "Regular" msgstr "" msgid "Large" -msgstr "" +msgstr "Groot" msgid "Extra Large" msgstr "" From 09f2c478ff41382c0734ba8b70819a8a51de4a69 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 23 Jun 2023 01:54:00 +0000 Subject: [PATCH 111/285] chore(release): cut 26.0.3 [skip ci] ## [26.0.3](https://github.com/dhis2/analytics/compare/v26.0.2...v26.0.3) (2023-06-23) ### Bug Fixes * **translations:** sync translations from transifex (master) ([d8f3864](https://github.com/dhis2/analytics/commit/d8f3864261cadef8f0a9609257eece49a8e9874b)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de4286671..0cd44ea41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.0.3](https://github.com/dhis2/analytics/compare/v26.0.2...v26.0.3) (2023-06-23) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([d8f3864](https://github.com/dhis2/analytics/commit/d8f3864261cadef8f0a9609257eece49a8e9874b)) + ## [26.0.2](https://github.com/dhis2/analytics/compare/v26.0.1...v26.0.2) (2023-06-22) diff --git a/package.json b/package.json index 670020739..d9b9c4797 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.0.2", + "version": "26.0.3", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 7d12e06767ba234bb3a6203ca08e2ef4cef6ebff Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sat, 24 Jun 2023 03:49:22 +0200 Subject: [PATCH 112/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/nl.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/nl.po b/i18n/nl.po index 366dbb468..56dbe4df1 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -943,7 +943,7 @@ msgid "Loading data element groups" msgstr "" msgid "Data sets" -msgstr "" +msgstr "Gegevensreeksen" msgid "Data set" msgstr "Gegevensreeks" From 76a96ded5d507d6bdf680f5d3fa30f6154bafbc7 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sat, 24 Jun 2023 01:55:15 +0000 Subject: [PATCH 113/285] chore(release): cut 26.0.4 [skip ci] ## [26.0.4](https://github.com/dhis2/analytics/compare/v26.0.3...v26.0.4) (2023-06-24) ### Bug Fixes * **translations:** sync translations from transifex (master) ([7d12e06](https://github.com/dhis2/analytics/commit/7d12e06767ba234bb3a6203ca08e2ef4cef6ebff)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cd44ea41..430abcdd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.0.4](https://github.com/dhis2/analytics/compare/v26.0.3...v26.0.4) (2023-06-24) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([7d12e06](https://github.com/dhis2/analytics/commit/7d12e06767ba234bb3a6203ca08e2ef4cef6ebff)) + ## [26.0.3](https://github.com/dhis2/analytics/compare/v26.0.2...v26.0.3) (2023-06-23) diff --git a/package.json b/package.json index d9b9c4797..1b2e103a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.0.3", + "version": "26.0.4", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 17853e02b6357e4d409ce2c124bc9640fa9bcf7b Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 27 Jun 2023 03:50:31 +0200 Subject: [PATCH 114/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/nl.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/nl.po b/i18n/nl.po index 56dbe4df1..16c31c038 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -468,7 +468,7 @@ msgid "Type" msgstr "Soort" msgid "Clear filters" -msgstr "" +msgstr "Wis filters" msgid "{{firstItemIndex}}-{{lastItemIndex}} of {{totalNumberOfItems}}" msgstr "" @@ -820,7 +820,7 @@ msgid "Days" msgstr "Dagen" msgid "Weeks" -msgstr "" +msgstr "Weken" msgid "Bi-weeks" msgstr "" From 47da97a7e9bf7d2e594adf4d1f329c6fd67db0eb Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 27 Jun 2023 01:55:35 +0000 Subject: [PATCH 115/285] chore(release): cut 26.0.5 [skip ci] ## [26.0.5](https://github.com/dhis2/analytics/compare/v26.0.4...v26.0.5) (2023-06-27) ### Bug Fixes * **translations:** sync translations from transifex (master) ([17853e0](https://github.com/dhis2/analytics/commit/17853e02b6357e4d409ce2c124bc9640fa9bcf7b)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 430abcdd2..791daf023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.0.5](https://github.com/dhis2/analytics/compare/v26.0.4...v26.0.5) (2023-06-27) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([17853e0](https://github.com/dhis2/analytics/commit/17853e02b6357e4d409ce2c124bc9640fa9bcf7b)) + ## [26.0.4](https://github.com/dhis2/analytics/compare/v26.0.3...v26.0.4) (2023-06-24) diff --git a/package.json b/package.json index 1b2e103a7..a7505fb30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.0.4", + "version": "26.0.5", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From d4a96e943e688718f391e2bae9c6240bbe198eaf Mon Sep 17 00:00:00 2001 From: Hendrik de Graaf Date: Tue, 27 Jun 2023 15:14:11 +0200 Subject: [PATCH 116/285] fix: various issues related to the new toolbar (#1522) * fix: remove background color transition from menu button styles * fix: prevent disabled menu button from getting grey highlight on click * fix: decrease hovermenu list items size and align file menu * fix: prevent text highlighting by user selection in menu button * fix: keep grey highlight on open menu dropdown button * fix: ensure menu list item background highlight colors are consistent * chore: fix props error in toolbar demo story * fix: prevent menu button from having focus outline when closing with ESC --- src/__demo__/Toolbar.stories.js | 2 +- src/components/FileMenu/FileMenu.js | 6 +++--- src/components/Toolbar/HoverMenuBar/HoverMenuBar.js | 9 +++++++++ src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js | 2 ++ src/components/Toolbar/HoverMenuBar/HoverMenuList.js | 1 + .../Toolbar/HoverMenuBar/HoverMenuListItem.styles.js | 7 ++----- src/components/Toolbar/MenuButton.styles.js | 5 +++-- 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/__demo__/Toolbar.stories.js b/src/__demo__/Toolbar.stories.js index 14d50fb34..f0795632e 100644 --- a/src/__demo__/Toolbar.stories.js +++ b/src/__demo__/Toolbar.stories.js @@ -29,7 +29,7 @@ function ToolbarWithState() { click to hide - + {}} /> diff --git a/src/components/FileMenu/FileMenu.js b/src/components/FileMenu/FileMenu.js index bc0c3766a..f81a3145c 100644 --- a/src/components/FileMenu/FileMenu.js +++ b/src/components/FileMenu/FileMenu.js @@ -148,7 +148,7 @@ export const FileMenu = ({ onClick={onNew} dataTest="file-menu-new" /> - + } @@ -230,7 +230,7 @@ export const FileMenu = ({ onClick={onMenuItemClick('translate')} dataTest="file-menu-translate" /> - + - + { const closeMenuWithEsc = useCallback( (event) => { if (event.keyCode === 27) { + /* Blurring the active element is needed here to prevent + * the menu button which was clicked to open the hovermenu + * from getting the blue outline style. This looks a bit off + * in all cases, but especially when the menu item which was + * clicked to open the hover menu isn't the currently opened + * dropdown menu. This doesn't have to be the case since + * dropdown menues open on hover once the first one has been + * clicked. */ + document.activeElement.blur() closeMenu() } }, diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js index 1664d1473..259131be2 100644 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js @@ -1,5 +1,6 @@ import { Popper } from '@dhis2-ui/popper' import { Portal } from '@dhis2-ui/portal' +import cx from 'classnames' import PropTypes from 'prop-types' import React, { useRef } from 'react' import menuButtonStyles from '../MenuButton.styles.js' @@ -17,6 +18,7 @@ export const HoverMenuDropdown = ({ children, label, dataTest, disabled }) => { return ( <>
    ) : ( - + Date: Thu, 6 Jul 2023 09:51:41 +0200 Subject: [PATCH 167/285] fix: interpretations modal height (DHIS2-15558) --- .../InterpretationModal/InterpretationModal.js | 8 +++++--- .../InterpretationModal/InterpretationThread.js | 13 ++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/Interpretations/InterpretationModal/InterpretationModal.js b/src/components/Interpretations/InterpretationModal/InterpretationModal.js index 4f02eaefa..6853ed4ed 100644 --- a/src/components/Interpretations/InterpretationModal/InterpretationModal.js +++ b/src/components/Interpretations/InterpretationModal/InterpretationModal.js @@ -24,14 +24,14 @@ const modalCSS = css.resolve` max-width: calc(100vw - 128px) !important; max-height: calc(100vh - 128px) !important; width: auto !important; - height: auto !important; + height: calc(100vw - 128px) !important; overflow-y: hidden; } aside.hidden { display: none; } aside > :global(div) > :global(div) { - max-height: none; + height: 100%; } ` @@ -39,6 +39,7 @@ function getModalContentCSS(width) { return css.resolve` div { width: ${width}px; + overflow-y: visible; } ` } @@ -216,12 +217,14 @@ const InterpretationModal = ({ .container { display: flex; flex-direction: column; + height: 100%; } .row { display: flex; flex-direction: row; gap: 16px; + height: 100%; } .visualisation-wrap { @@ -233,7 +236,6 @@ const InterpretationModal = ({ padding-right: ${spacers.dp4}; flex-basis: 300px; flex-shrink: 0; - overflow-y: auto; } `} diff --git a/src/components/Interpretations/InterpretationModal/InterpretationThread.js b/src/components/Interpretations/InterpretationModal/InterpretationThread.js index f1d5779e2..ec4a8b624 100644 --- a/src/components/Interpretations/InterpretationModal/InterpretationThread.js +++ b/src/components/Interpretations/InterpretationModal/InterpretationThread.js @@ -80,9 +80,17 @@ const InterpretationThread = ({ )}
    + onThreadUpdated(true)} + focusRef={focusRef} + /> {i18n.t('Rename {{fileType}}', { @@ -67,6 +80,7 @@ export const RenameDialog = ({ type, object, onClose, onRename, onError }) => { required value={name} onChange={({ value }) => setName(value)} + dataTest="file-menu-rename-modal-name" /> { value={description} rows={3} onChange={({ value }) => setDescription(value)} + dataTest="file-menu-rename-modal-description" />
    - - From 4a8a54ac61d0af1ea3ea5923c6487eb4c1061b18 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 7 Nov 2023 09:09:57 +0000 Subject: [PATCH 177/285] chore(release): cut 26.1.6 [skip ci] ## [26.1.6](https://github.com/dhis2/analytics/compare/v26.1.5...v26.1.6) (2023-11-07) ### Bug Fixes * use json+patch for PATCH requests ([#1593](https://github.com/dhis2/analytics/issues/1593)) ([bdb0b9c](https://github.com/dhis2/analytics/commit/bdb0b9ceaa5ef102ef9768038c763b70a3d5e7d1)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff21a7bd6..7a6c9d268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.1.6](https://github.com/dhis2/analytics/compare/v26.1.5...v26.1.6) (2023-11-07) + + +### Bug Fixes + +* use json+patch for PATCH requests ([#1593](https://github.com/dhis2/analytics/issues/1593)) ([bdb0b9c](https://github.com/dhis2/analytics/commit/bdb0b9ceaa5ef102ef9768038c763b70a3d5e7d1)) + ## [26.1.5](https://github.com/dhis2/analytics/compare/v26.1.4...v26.1.5) (2023-11-03) diff --git a/package.json b/package.json index d8370ddad..350328668 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.1.5", + "version": "26.1.6", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From cb4a052a19ebdced3ea7b6ffb7d1c40064abac38 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 19 Nov 2023 02:37:51 +0100 Subject: [PATCH 178/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/uz_UZ_Cyrl.po | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/i18n/uz_UZ_Cyrl.po b/i18n/uz_UZ_Cyrl.po index 8a67b872d..888e2a210 100644 --- a/i18n/uz_UZ_Cyrl.po +++ b/i18n/uz_UZ_Cyrl.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-07-06T08:30:33.216Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" "Last-Translator: Ibatov , 2023\n" "Language-Team: Uzbek (Cyrillic) (https://app.transifex.com/hisp-uio/teams/100509/uz@Cyrl/)\n" @@ -237,7 +237,7 @@ msgid "Dimension recommended with selected data" msgstr "Танланган маълумотлар учун созламалар тавсия этилади" msgid "All items" -msgstr "" +msgstr "Барча элементлар" msgid "Automatically include all items" msgstr "" @@ -333,7 +333,7 @@ msgid "map" msgstr "харита" msgid "visualization" -msgstr "" +msgstr "Визуализация" msgid "Edit" msgstr "Таҳрирлаш" @@ -373,6 +373,10 @@ msgstr "" msgid "Write an interpretation" msgstr "Талқин ёзиш" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "" @@ -595,10 +599,10 @@ msgid_plural "{{count}} groups" msgstr[0] "" msgid "Selected: {{commaSeparatedListOfOrganisationUnits}}" -msgstr "" +msgstr "Танланган: {{commaSeparatedListOfOrganisationUnits}}" msgid "Nothing selected" -msgstr "" +msgstr "Ҳеч нима танланмаган" msgid "User organisation unit" msgstr "Ташкилий бўлим фойдаланувчиси" From f8678880f10843e5c3504cb12bf86c5acb208112 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 19 Nov 2023 01:41:34 +0000 Subject: [PATCH 179/285] chore(release): cut 26.1.7 [skip ci] ## [26.1.7](https://github.com/dhis2/analytics/compare/v26.1.6...v26.1.7) (2023-11-19) ### Bug Fixes * **translations:** sync translations from transifex (master) ([cb4a052](https://github.com/dhis2/analytics/commit/cb4a052a19ebdced3ea7b6ffb7d1c40064abac38)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a6c9d268..9664c79a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.1.7](https://github.com/dhis2/analytics/compare/v26.1.6...v26.1.7) (2023-11-19) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([cb4a052](https://github.com/dhis2/analytics/commit/cb4a052a19ebdced3ea7b6ffb7d1c40064abac38)) + ## [26.1.6](https://github.com/dhis2/analytics/compare/v26.1.5...v26.1.6) (2023-11-07) diff --git a/package.json b/package.json index 350328668..6c50f8338 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.1.6", + "version": "26.1.7", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 43a607ba0310b19edd07d866f15337e736567b2f Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 26 Nov 2023 02:37:17 +0100 Subject: [PATCH 180/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/uz_UZ_Cyrl.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/uz_UZ_Cyrl.po b/i18n/uz_UZ_Cyrl.po index 888e2a210..9b9dc5769 100644 --- a/i18n/uz_UZ_Cyrl.po +++ b/i18n/uz_UZ_Cyrl.po @@ -77,7 +77,7 @@ msgid "This app could not retrieve required data." msgstr "" msgid "Network error" -msgstr "" +msgstr "Тармоқда хатолик" msgid "Data / Edit calculation" msgstr "" @@ -926,7 +926,7 @@ msgid "Indicator group" msgstr "Индикатор гуруҳи" msgid "All groups" -msgstr "" +msgstr "Барча гуруҳлар" msgid "Indicator" msgstr "Индикатор" From 07f2c7d66ff536486a44d7bbb684f52aa797e8f1 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 26 Nov 2023 01:41:11 +0000 Subject: [PATCH 181/285] chore(release): cut 26.1.8 [skip ci] ## [26.1.8](https://github.com/dhis2/analytics/compare/v26.1.7...v26.1.8) (2023-11-26) ### Bug Fixes * **translations:** sync translations from transifex (master) ([43a607b](https://github.com/dhis2/analytics/commit/43a607ba0310b19edd07d866f15337e736567b2f)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9664c79a4..2aa4eb993 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.1.8](https://github.com/dhis2/analytics/compare/v26.1.7...v26.1.8) (2023-11-26) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([43a607b](https://github.com/dhis2/analytics/commit/43a607ba0310b19edd07d866f15337e736567b2f)) + ## [26.1.7](https://github.com/dhis2/analytics/compare/v26.1.6...v26.1.7) (2023-11-19) diff --git a/package.json b/package.json index 6c50f8338..1142355fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.1.7", + "version": "26.1.8", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 2b404b423cfb49347bdf57125870bb7b55338322 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Thu, 14 Dec 2023 15:07:41 +0100 Subject: [PATCH 182/285] feat: implement cumulative values in PT engine (DHIS2-5497) (#1567) * feat: implement cumulative values DHIS2-5497 * fix: render NUMBER type cells for cumulative values This solves formatting issues, like right alignment, caused by the default type being TEXT. * fix: do not accumulate TEXT values * fix: pass valueType also for empty cells This is needed for the cumulative values feature, so empty cells can be set to 0 or to the accumulated value. * fix: show 0 for empty cells with no accumulated value * fix: pass dxDimension object also for empty cells Needed for the cumulative values feature. * fix: store rendered values in accumulators * fix: override cell size when using cumulative values This is to accommodate the accumulated value which might need a wider cell. The cell size is computed based on the rendered value. * fix: add styles for title in disabled sections * fix: fix sorting with cumulative values DHIS2-16156 Refactored and moved some code around to allow getRaw() to return cumulative values, thus fixing the sorting which uses getRaw() to sort the rows/columns. --- src/__demo__/PivotTable.stories.js | 40 ++++ .../Options/VisualizationOptions.js | 2 + .../styles/VisualizationOptions.style.js | 6 + src/modules/pivotTable/PivotTableEngine.js | 177 +++++++++++++----- 4 files changed, 173 insertions(+), 52 deletions(-) diff --git a/src/__demo__/PivotTable.stories.js b/src/__demo__/PivotTable.stories.js index e06782146..93ca49ae9 100644 --- a/src/__demo__/PivotTable.stories.js +++ b/src/__demo__/PivotTable.stories.js @@ -784,6 +784,26 @@ storiesOf('PivotTable', module).add( } ) +storiesOf('PivotTable', module).add( + 'cumulative + empty columns (weekly) - shown', + (_, { pivotTableOptions }) => { + const visualization = { + ...weeklyColumnsVisualization, + ...pivotTableOptions, + hideEmptyColumns: false, + cumulativeValues: true, + } + return ( +
    + +
    + ) + } +) + storiesOf('PivotTable', module).add( 'empty columns (weekly) - hidden', (_, { pivotTableOptions }) => { @@ -803,6 +823,26 @@ storiesOf('PivotTable', module).add( } ) +storiesOf('PivotTable', module).add( + 'cumulative + empty columns (weekly) - hidden', + (_, { pivotTableOptions }) => { + const visualization = { + ...weeklyColumnsVisualization, + ...pivotTableOptions, + hideEmptyColumns: true, + cumulativeValues: true, + } + return ( +
    + +
    + ) + } +) + storiesOf('PivotTable', module).add( 'empty columns + assigned cats (shown)', (_, { pivotTableOptions }) => { diff --git a/src/components/Options/VisualizationOptions.js b/src/components/Options/VisualizationOptions.js index 3bb3e3174..c8eb10e72 100644 --- a/src/components/Options/VisualizationOptions.js +++ b/src/components/Options/VisualizationOptions.js @@ -18,6 +18,7 @@ import { modalContent, tabSection, tabSectionTitle, + tabSectionTitleDisabled, tabSectionTitleMargin, tabSectionOption, tabSectionOptionItem, @@ -95,6 +96,7 @@ const VisualizationOptions = ({ {tabContent.styles} {tabSection.styles} {tabSectionTitle.styles} + {tabSectionTitleDisabled.styles} {tabSectionTitleMargin.styles} {tabSectionOption.styles} {tabSectionOptionItem.styles} diff --git a/src/components/Options/styles/VisualizationOptions.style.js b/src/components/Options/styles/VisualizationOptions.style.js index bcd74f517..61c87803f 100644 --- a/src/components/Options/styles/VisualizationOptions.style.js +++ b/src/components/Options/styles/VisualizationOptions.style.js @@ -51,6 +51,12 @@ export const tabSectionTitle = css.resolve` } ` +export const tabSectionTitleDisabled = css.resolve` + span { + color: ${colors.grey600}; + } +` + export const tabSectionTitleMargin = css.resolve` span { margin-top: ${spacers.dp8}; diff --git a/src/modules/pivotTable/PivotTableEngine.js b/src/modules/pivotTable/PivotTableEngine.js index d59559d2b..b5788fee1 100644 --- a/src/modules/pivotTable/PivotTableEngine.js +++ b/src/modules/pivotTable/PivotTableEngine.js @@ -55,6 +55,7 @@ const defaultOptions = { showColumnSubtotals: false, fixColumnHeaders: false, fixRowHeaders: false, + cumulativeValues: false, } const defaultVisualizationProps = { @@ -268,6 +269,7 @@ export class PivotTableEngine { data = [] rowMap = [] columnMap = [] + accumulators = { rows: {} } constructor(visualization, data, legendSets) { this.visualization = Object.assign( @@ -306,6 +308,7 @@ export class PivotTableEngine { fixRowHeaders: this.dimensionLookup.rows.length ? visualization.fixRowHeaders : false, + cumulativeValues: visualization.cumulativeValues, } this.adaptiveClippingController = new AdaptiveClippingController(this) @@ -333,6 +336,7 @@ export class PivotTableEngine { getRaw({ row, column }) { const cellType = this.getRawCellType({ row, column }) const dxDimension = this.getRawCellDxDimension({ row, column }) + const valueType = dxDimension?.valueType || VALUE_TYPE_TEXT const headers = [ ...this.getRawRowHeader(row), @@ -346,55 +350,79 @@ export class PivotTableEngine { header?.dimensionItemType === DIMENSION_TYPE_ORGANISATION_UNIT )?.uid + const rawCell = { + cellType, + valueType, + ouId, + peId, + } + if (!this.data[row] || !this.data[row][column]) { - return { - cellType, - empty: true, - ouId, - peId, + rawCell.empty = true + } else { + const dataRow = this.data[row][column] + + let rawValue = + cellType === CELL_TYPE_VALUE + ? dataRow[this.dimensionLookup.dataHeaders.value] + : dataRow.value + let renderedValue = rawValue + + if (valueType === VALUE_TYPE_NUMBER) { + rawValue = parseValue(rawValue) + switch (this.visualization.numberType) { + case NUMBER_TYPE_ROW_PERCENTAGE: + renderedValue = + rawValue / this.percentageTotals[row].value + break + case NUMBER_TYPE_COLUMN_PERCENTAGE: + renderedValue = + rawValue / this.percentageTotals[column].value + break + default: + break + } } - } - const dataRow = this.data[row][column] + renderedValue = renderValue( + renderedValue, + valueType, + this.visualization + ) - let rawValue = - cellType === CELL_TYPE_VALUE - ? dataRow[this.dimensionLookup.dataHeaders.value] - : dataRow.value - let renderedValue = rawValue - const valueType = dxDimension?.valueType || VALUE_TYPE_TEXT + rawCell.dxDimension = dxDimension + rawCell.empty = false + rawCell.rawValue = rawValue + rawCell.renderedValue = renderedValue + } - if (valueType === VALUE_TYPE_NUMBER) { - rawValue = parseValue(rawValue) - switch (this.visualization.numberType) { - case NUMBER_TYPE_ROW_PERCENTAGE: - renderedValue = rawValue / this.percentageTotals[row].value - break - case NUMBER_TYPE_COLUMN_PERCENTAGE: - renderedValue = - rawValue / this.percentageTotals[column].value - break - default: - break + if (this.options.cumulativeValues) { + const cumulativeValue = this.getCumulative({ + row, + column, + }) + + if (cumulativeValue !== undefined && cumulativeValue !== null) { + // force to NUMBER for accumulated values + rawCell.valueType = + valueType === undefined || valueType === null + ? VALUE_TYPE_NUMBER + : valueType + rawCell.empty = false + rawCell.rawValue = cumulativeValue + rawCell.renderedValue = renderValue( + cumulativeValue, + valueType, + this.visualization + ) } } - renderedValue = renderValue( - renderedValue, - valueType, - this.visualization - ) + return rawCell + } - return { - cellType, - empty: false, - valueType, - rawValue, - renderedValue, - dxDimension, - ouId, - peId, - } + getCumulative({ row, column }) { + return this.accumulators.rows[row][column] } get({ row, column }) { @@ -488,10 +516,8 @@ export class PivotTableEngine { return undefined } const cellValue = this.data[row][column] - if (!cellValue) { - return undefined - } - if (!Array.isArray(cellValue)) { + + if (cellValue && !Array.isArray(cellValue)) { // This is a total cell return { valueType: cellValue.valueType, @@ -527,6 +553,11 @@ export class PivotTableEngine { } } + // Empty cell + // The cell still needs to get the valueType to render correctly 0 and cumulative values + // + // OR + // // Data is in Filter // TODO : This assumes the server ignores text types, we should confirm this is the case return { @@ -539,7 +570,7 @@ export class PivotTableEngine { return !this.data[row] || this.data[row].length === 0 } columnIsEmpty(column) { - return !this.adaptiveClippingController.columns.sizes[column] + return !this.rowMap.some((row) => this.data[row][column]) } getRawColumnHeader(column) { @@ -967,6 +998,45 @@ export class PivotTableEngine { : times(this.dataWidth, (n) => n) } + resetAccumulators() { + if (this.options.cumulativeValues) { + this.rowMap.forEach((row) => { + this.accumulators.rows[row] = {} + this.columnMap.reduce((acc, column) => { + const cellType = this.getRawCellType({ row, column }) + const dxDimension = this.getRawCellDxDimension({ + row, + column, + }) + const valueType = dxDimension?.valueType || VALUE_TYPE_TEXT + + // only accumulate numeric values + // accumulating text values does not make sense + if (valueType === VALUE_TYPE_NUMBER) { + if (this.data[row] && this.data[row][column]) { + const dataRow = this.data[row][column] + + const rawValue = + cellType === CELL_TYPE_VALUE + ? dataRow[ + this.dimensionLookup.dataHeaders.value + ] + : dataRow.value + + acc += parseValue(rawValue) + } + + this.accumulators.rows[row][column] = acc + } + + return acc + }, 0) + }) + } else { + this.accumulators = { rows: {} } + } + } + get cellPadding() { switch (this.visualization.displayDensity) { case DISPLAY_DENSITY_OPTION_COMPACT: @@ -1059,19 +1129,22 @@ export class PivotTableEngine { this.finalizeTotals() - this.rawData.rows.forEach((dataRow) => { - const pos = lookup(dataRow, this.dimensionLookup, this) - if (pos) { + this.resetRowMap() + this.resetColumnMap() + + this.resetAccumulators() + + this.rowMap.forEach((row) => { + this.columnMap.forEach((column) => { + const pos = { row, column } + this.adaptiveClippingController.add( pos, this.getRaw(pos).renderedValue ) - } + }) }) - this.resetRowMap() - this.resetColumnMap() - this.height = this.rowMap.length this.width = this.columnMap.length From 913eb4084261c035d6027d7a8acf9b3b4e43633a Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Thu, 14 Dec 2023 14:11:20 +0000 Subject: [PATCH 183/285] chore(release): cut 26.2.0 [skip ci] # [26.2.0](https://github.com/dhis2/analytics/compare/v26.1.8...v26.2.0) (2023-12-14) ### Features * implement cumulative values in PT engine (DHIS2-5497) ([#1567](https://github.com/dhis2/analytics/issues/1567)) ([2b404b4](https://github.com/dhis2/analytics/commit/2b404b423cfb49347bdf57125870bb7b55338322)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aa4eb993..f366e4f6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [26.2.0](https://github.com/dhis2/analytics/compare/v26.1.8...v26.2.0) (2023-12-14) + + +### Features + +* implement cumulative values in PT engine (DHIS2-5497) ([#1567](https://github.com/dhis2/analytics/issues/1567)) ([2b404b4](https://github.com/dhis2/analytics/commit/2b404b423cfb49347bdf57125870bb7b55338322)) + ## [26.1.8](https://github.com/dhis2/analytics/compare/v26.1.7...v26.1.8) (2023-11-26) diff --git a/package.json b/package.json index 1142355fa..e9402ffa4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.1.8", + "version": "26.2.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From bac4cc94572b2e8367fb3d1f061eb7ed122f372f Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 24 Dec 2023 02:36:43 +0100 Subject: [PATCH 184/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/lo.po | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/i18n/lo.po b/i18n/lo.po index 31f689fc5..b41993e0e 100644 --- a/i18n/lo.po +++ b/i18n/lo.po @@ -2,16 +2,16 @@ # Translators: # Viktor Varland , 2022 # Somkhit Bouavong , 2022 -# Saysamone Sibounma, 2022 -# phil_dhis2, 2023 +# Philip Larsen Donnelly, 2023 # Phouthasinh PHEUAYSITHIPHONE, 2023 +# Saysamone Sibounma, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Phouthasinh PHEUAYSITHIPHONE, 2023\n" +"Last-Translator: Saysamone Sibounma, 2023\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" @@ -81,7 +81,7 @@ msgid "This app could not retrieve required data." msgstr "" msgid "Network error" -msgstr "" +msgstr "ຂໍ້ຜິດພາດທາງເຄື່ອຂ່າຍ" msgid "Data / Edit calculation" msgstr "" @@ -90,7 +90,7 @@ msgid "Data / New calculation" msgstr "" msgid "Remove item" -msgstr "" +msgstr "ລົບລາຍການ" msgid "Check formula" msgstr "" @@ -348,6 +348,9 @@ msgstr "" msgid "Post reply" msgstr "" +msgid "Delete failed" +msgstr "" + msgid "Could not update comment" msgstr "" @@ -374,21 +377,32 @@ msgstr "" msgid "Write an interpretation" msgstr "" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "" msgid "Interpretations" msgstr "ການແປຂໍ້ມູນ" +msgid "Reply" +msgstr "ຕອບກັບ" + +msgid "{{count}} replies" +msgid_plural "{{count}} replies" +msgstr[0] "" + +msgid "View replies" +msgstr "" + msgid "Unlike" msgstr "ບໍ່ມັກ" msgid "Like" msgstr "ມັກ" -msgid "Reply" -msgstr "ຕອບກັບ" - msgid "Share" msgstr "ເຜີຍແຜ່" @@ -840,6 +854,9 @@ msgstr "" msgid "Years" msgstr "ປີ" +msgid "Interpretations and details" +msgstr "" + msgid "Translating to" msgstr "" From b2751a3bdfc57309f2a18cc90128fb4a0300f669 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 24 Dec 2023 01:40:20 +0000 Subject: [PATCH 185/285] chore(release): cut 26.2.1 [skip ci] ## [26.2.1](https://github.com/dhis2/analytics/compare/v26.2.0...v26.2.1) (2023-12-24) ### Bug Fixes * **translations:** sync translations from transifex (master) ([bac4cc9](https://github.com/dhis2/analytics/commit/bac4cc94572b2e8367fb3d1f061eb7ed122f372f)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f366e4f6e..8f208755b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.2.1](https://github.com/dhis2/analytics/compare/v26.2.0...v26.2.1) (2023-12-24) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([bac4cc9](https://github.com/dhis2/analytics/commit/bac4cc94572b2e8367fb3d1f061eb7ed122f372f)) + # [26.2.0](https://github.com/dhis2/analytics/compare/v26.1.8...v26.2.0) (2023-12-14) diff --git a/package.json b/package.json index e9402ffa4..e79653acd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.2.0", + "version": "26.2.1", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 52ae98d3cadd975c2079055b00adba58f0982ef4 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 2 Jan 2024 11:26:04 +0100 Subject: [PATCH 186/285] fix: add white-space css to preserve paragraphs in interpretations (#1599) Fixes DHIS2-16365 --- src/components/Interpretations/common/Message/Message.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Interpretations/common/Message/Message.js b/src/components/Interpretations/common/Message/Message.js index 50613ab2b..016e8b9d0 100644 --- a/src/components/Interpretations/common/Message/Message.js +++ b/src/components/Interpretations/common/Message/Message.js @@ -49,6 +49,7 @@ const Message = ({ children, text, created, username }) => { line-height: 19px; color: ${colors.grey900}; word-break: break-word; + white-space: pre-line; } .content :global(p:first-child) { From e8334947155d0d6b84d4ea90907059d44e60b398 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 2 Jan 2024 10:29:40 +0000 Subject: [PATCH 187/285] chore(release): cut 26.2.2 [skip ci] ## [26.2.2](https://github.com/dhis2/analytics/compare/v26.2.1...v26.2.2) (2024-01-02) ### Bug Fixes * add white-space css to preserve paragraphs in interpretations ([#1599](https://github.com/dhis2/analytics/issues/1599)) ([52ae98d](https://github.com/dhis2/analytics/commit/52ae98d3cadd975c2079055b00adba58f0982ef4)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f208755b..f4e829421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.2.2](https://github.com/dhis2/analytics/compare/v26.2.1...v26.2.2) (2024-01-02) + + +### Bug Fixes + +* add white-space css to preserve paragraphs in interpretations ([#1599](https://github.com/dhis2/analytics/issues/1599)) ([52ae98d](https://github.com/dhis2/analytics/commit/52ae98d3cadd975c2079055b00adba58f0982ef4)) + ## [26.2.1](https://github.com/dhis2/analytics/compare/v26.2.0...v26.2.1) (2023-12-24) diff --git a/package.json b/package.json index e79653acd..aa5a1e077 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.2.1", + "version": "26.2.2", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 14fbfb5bbfbbaa430631d39b8ca77c68817f10a4 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Fri, 19 Jan 2024 13:32:04 +0100 Subject: [PATCH 188/285] fix: adjust width of interpretation reply input when in focus [DHIS2-16429] (#1607) Fixes: https://dhis2.atlassian.net/browse/DHIS2-16429 Reduce width by the width of the box shadow so it doesn't get cut off. --- .../common/RichTextEditor/styles/RichTextEditor.style.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Interpretations/common/RichTextEditor/styles/RichTextEditor.style.js b/src/components/Interpretations/common/RichTextEditor/styles/RichTextEditor.style.js index 865e484aa..53b9a5457 100644 --- a/src/components/Interpretations/common/RichTextEditor/styles/RichTextEditor.style.js +++ b/src/components/Interpretations/common/RichTextEditor/styles/RichTextEditor.style.js @@ -36,6 +36,7 @@ export const mainClasses = css` .textarea:focus { outline: none; box-shadow: 0 0 0 3px ${theme.focus}; + width: calc(100% - 3px); } .textarea:disabled { From b259e0390286003713232177686da929477f2511 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Fri, 19 Jan 2024 12:35:42 +0000 Subject: [PATCH 189/285] chore(release): cut 26.2.3 [skip ci] ## [26.2.3](https://github.com/dhis2/analytics/compare/v26.2.2...v26.2.3) (2024-01-19) ### Bug Fixes * adjust width of interpretation reply input when in focus [DHIS2-16429] ([#1607](https://github.com/dhis2/analytics/issues/1607)) ([14fbfb5](https://github.com/dhis2/analytics/commit/14fbfb5bbfbbaa430631d39b8ca77c68817f10a4)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4e829421..6a38826c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.2.3](https://github.com/dhis2/analytics/compare/v26.2.2...v26.2.3) (2024-01-19) + + +### Bug Fixes + +* adjust width of interpretation reply input when in focus [DHIS2-16429] ([#1607](https://github.com/dhis2/analytics/issues/1607)) ([14fbfb5](https://github.com/dhis2/analytics/commit/14fbfb5bbfbbaa430631d39b8ca77c68817f10a4)) + ## [26.2.2](https://github.com/dhis2/analytics/compare/v26.2.1...v26.2.2) (2024-01-02) diff --git a/package.json b/package.json index aa5a1e077..36200f9b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.2.2", + "version": "26.2.3", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 41aa57ff24e790b8db2e21d916b22fb03d6878b2 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 21 Jan 2024 02:39:32 +0100 Subject: [PATCH 190/285] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/sv.po | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/i18n/sv.po b/i18n/sv.po index afe88915f..4148939cc 100644 --- a/i18n/sv.po +++ b/i18n/sv.po @@ -1,14 +1,15 @@ # # Translators: # Viktor Varland , 2022 -# phil_dhis2, 2023 +# Philip Larsen Donnelly, 2023 +# Jason Pickering , 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2023\n" +"Last-Translator: Jason Pickering , 2024\n" "Language-Team: Swedish (https://app.transifex.com/hisp-uio/teams/100509/sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -346,6 +347,9 @@ msgstr "" msgid "Post reply" msgstr "" +msgid "Delete failed" +msgstr "" + msgid "Could not update comment" msgstr "" @@ -372,21 +376,33 @@ msgstr "" msgid "Write an interpretation" msgstr "" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "" msgid "Interpretations" msgstr "tolkningar" +msgid "Reply" +msgstr "Svar" + +msgid "{{count}} replies" +msgid_plural "{{count}} replies" +msgstr[0] "" +msgstr[1] "" + +msgid "View replies" +msgstr "" + msgid "Unlike" msgstr "" msgid "Like" msgstr "" -msgid "Reply" -msgstr "Svar" - msgid "Share" msgstr "Dela med sig" @@ -611,7 +627,7 @@ msgid "Select a group" msgstr "" msgid "Deselect all" -msgstr "" +msgstr "Avmarkera alla" msgid "Period type" msgstr "Periodtyp" @@ -841,6 +857,9 @@ msgstr "" msgid "Years" msgstr "År" +msgid "Interpretations and details" +msgstr "" + msgid "Translating to" msgstr "" From 9ad9f354c752f97c0ceddeb42f81f0e35c69b3b3 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Sun, 21 Jan 2024 01:43:18 +0000 Subject: [PATCH 191/285] chore(release): cut 26.2.4 [skip ci] ## [26.2.4](https://github.com/dhis2/analytics/compare/v26.2.3...v26.2.4) (2024-01-21) ### Bug Fixes * **translations:** sync translations from transifex (master) ([41aa57f](https://github.com/dhis2/analytics/commit/41aa57ff24e790b8db2e21d916b22fb03d6878b2)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a38826c4..934300ff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.2.4](https://github.com/dhis2/analytics/compare/v26.2.3...v26.2.4) (2024-01-21) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([41aa57f](https://github.com/dhis2/analytics/commit/41aa57ff24e790b8db2e21d916b22fb03d6878b2)) + ## [26.2.3](https://github.com/dhis2/analytics/compare/v26.2.2...v26.2.3) (2024-01-19) diff --git a/package.json b/package.json index 36200f9b8..3eae9bf1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.2.3", + "version": "26.2.4", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 57eb703a4227e6496d021fdb17d6d4fd0e935eb9 Mon Sep 17 00:00:00 2001 From: HendrikThePendric Date: Wed, 24 Jan 2024 11:00:50 +0100 Subject: [PATCH 192/285] feat: add classname prop to hover-menu-dropdown --- .../Toolbar/HoverMenuBar/HoverMenuDropdown.js | 11 +++++++++-- .../HoverMenuBar/__tests__/HoverMenuDropdown.spec.js | 11 +++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js index 259131be2..fbcdf146c 100644 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js @@ -6,7 +6,13 @@ import React, { useRef } from 'react' import menuButtonStyles from '../MenuButton.styles.js' import { useHoverMenubarContext } from './HoverMenuBar.js' -export const HoverMenuDropdown = ({ children, label, dataTest, disabled }) => { +export const HoverMenuDropdown = ({ + children, + className, + label, + dataTest, + disabled, +}) => { const buttonRef = useRef() const { onDropDownButtonClick, @@ -18,7 +24,7 @@ export const HoverMenuDropdown = ({ children, label, dataTest, disabled }) => { return ( <> +const OptionsButton = ({ onClick }) => ( + <> + + + ) OptionsButton.propTypes = { - style: PropTypes.object, onClick: PropTypes.func, } diff --git a/src/components/DimensionsPanel/List/RecommendedIcon.js b/src/components/DimensionsPanel/List/RecommendedIcon.js index 3a8b75dc9..12609d3a7 100644 --- a/src/components/DimensionsPanel/List/RecommendedIcon.js +++ b/src/components/DimensionsPanel/List/RecommendedIcon.js @@ -9,7 +9,6 @@ const RecommendedIcon = ({ isRecommended, dataTest }) =>
    diff --git a/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap b/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap index 1c33a04bf..f58b07faa 100644 --- a/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap +++ b/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap @@ -1,405 +1,283 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DimensionItem matches the snapshot 1`] = ` -
  • -
    + +
  • - -
    -
    - - Period - - + +
    +
    + + + Period + + + +
  • -
    - + +
    diff --git a/src/components/Interpretations/common/RichTextEditor/index.js b/src/components/Interpretations/common/RichTextEditor/index.js deleted file mode 100644 index 31c0113ca..000000000 --- a/src/components/Interpretations/common/RichTextEditor/index.js +++ /dev/null @@ -1 +0,0 @@ -export { RichTextEditor } from './RichTextEditor.js' diff --git a/src/components/Interpretations/common/index.js b/src/components/Interpretations/common/index.js index 562614fb1..d3473298f 100644 --- a/src/components/Interpretations/common/index.js +++ b/src/components/Interpretations/common/index.js @@ -1,4 +1,3 @@ export * from './Interpretation/index.js' export * from './Message/index.js' -export * from './RichTextEditor/index.js' export * from './getInterpretationAccess.js' diff --git a/src/components/Interpretations/common/RichTextEditor/RichTextEditor.js b/src/components/RichText/Editor/Editor.js similarity index 78% rename from src/components/Interpretations/common/RichTextEditor/RichTextEditor.js rename to src/components/RichText/Editor/Editor.js index e8ad9216d..6fdbf558e 100644 --- a/src/components/Interpretations/common/RichTextEditor/RichTextEditor.js +++ b/src/components/RichText/Editor/Editor.js @@ -1,10 +1,9 @@ import i18n from '@dhis2/d2-i18n' -import { Parser as RichTextParser } from '@dhis2/d2-ui-rich-text' import { Button, Popover, Tooltip, - Field, + Help, IconAt24, IconFaceAdd24, IconLink24, @@ -12,9 +11,11 @@ import { IconTextItalic24, colors, } from '@dhis2/ui' +import cx from 'classnames' import PropTypes from 'prop-types' import React, { forwardRef, useRef, useEffect, useState } from 'react' -import { UserMentionWrapper } from '../UserMention/UserMentionWrapper.js' +import { UserMentionWrapper } from '../../UserMention/UserMentionWrapper.js' +import { Parser } from '../Parser/Parser.js' import { convertCtrlKey, insertMarkdown, @@ -33,22 +34,22 @@ import { toolbarClasses, tooltipAnchorClasses, emojisPopoverClasses, -} from './styles/RichTextEditor.style.js' +} from './styles/Editor.style.js' const EmojisPopover = ({ onInsertMarkdown, onClose, reference }) => (
    • onInsertMarkdown(EMOJI_SMILEY_FACE)}> - {emojis[EMOJI_SMILEY_FACE]} + {emojis[EMOJI_SMILEY_FACE]}
    • onInsertMarkdown(EMOJI_SAD_FACE)}> - {emojis[EMOJI_SAD_FACE]} + {emojis[EMOJI_SAD_FACE]}
    • onInsertMarkdown(EMOJI_THUMBS_UP)}> - {emojis[EMOJI_THUMBS_UP]} + {emojis[EMOJI_THUMBS_UP]}
    • onInsertMarkdown(EMOJI_THUMBS_DOWN)}> - {emojis[EMOJI_THUMBS_DOWN]} + {emojis[EMOJI_THUMBS_DOWN]}
    @@ -190,29 +191,59 @@ Toolbar.propTypes = { disabled: PropTypes.bool, } -export const RichTextEditor = forwardRef( +export const Editor = forwardRef( ( - { value, disabled, inputPlaceholder, onChange, errorText, helpText }, + { + value, + disabled, + inputPlaceholder, + onChange, + errorText, + helpText, + initialFocus, + resizable, + }, externalRef ) => { const [previewMode, setPreviewMode] = useState(false) const internalRef = useRef() const textareaRef = externalRef || internalRef + const caretPosRef = useRef(undefined) - useEffect(() => textareaRef.current?.focus(), [textareaRef]) + const insertMarkdownCallback = (text, caretPos) => { + caretPosRef.current = caretPos + onChange(text) + textareaRef.current.focus() + } + + useEffect(() => { + if (initialFocus) { + textareaRef.current?.focus() + } + }, [initialFocus, textareaRef]) + + useEffect(() => { + if (caretPosRef.current) { + textareaRef.current?.setSelectionRange( + caretPosRef.current, + caretPosRef.current + ) + + caretPosRef.current = undefined + } + }, [value, textareaRef]) return ( -
    +
    { insertMarkdown( markdown, textareaRef.current, - (text, caretPos) => { - onChange(text) - textareaRef.current.focus() - textareaRef.current.selectionEnd = caretPos - } + insertMarkdownCallback ) if (markdown === MENTION) { @@ -231,20 +262,18 @@ export const RichTextEditor = forwardRef( /> {previewMode ? (
    - {value} + {value}
    ) : ( - +