├── .eslintrc.js ├── .github ├── dependabot.yml ├── pull_request_template.md ├── stale.yml └── workflows │ ├── check-tasklist.yml │ ├── dhis2-preview-pr.yml │ ├── dhis2-verify-commits.yml │ ├── e2e-dev.yml │ ├── generate-and-upload-bom.yml │ ├── nightly.yml │ ├── publish-d2-ci.yml │ ├── release.yml │ └── verify-pr.yml ├── .gitignore ├── .hooks ├── .gitignore ├── commit-msg ├── pre-commit └── pre-push ├── .prettierignore ├── .prettierrc.js ├── .releaserc ├── .stylelintrc.js ├── .tx └── config ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config ├── testSetup.js └── testsContext.js ├── cypress.config.js ├── cypress ├── elements │ ├── calculationsModal.js │ ├── chart.js │ ├── common.js │ ├── confirmLeaveModal.js │ ├── dimensionModal │ │ ├── dataDimension.js │ │ ├── dynamicDimension.js │ │ ├── index.js │ │ ├── orgUnitDimension.js │ │ └── periodDimension.js │ ├── dimensionsPanel.js │ ├── drillDownMenu.js │ ├── fileMenu │ │ ├── index.js │ │ ├── open.js │ │ └── save.js │ ├── layout.js │ ├── menuBar.js │ ├── optionsModal │ │ ├── axes.js │ │ ├── fontStyles.js │ │ ├── index.js │ │ ├── legend.js │ │ ├── limitValues.js │ │ ├── lines.js │ │ ├── outliers.js │ │ ├── series.js │ │ ├── subtitle.js │ │ └── totals.js │ ├── pivotTable.js │ ├── route.js │ ├── startScreen.js │ ├── visualizationErrorInfo.js │ ├── visualizationTypeSelector.js │ └── window.js ├── integration │ ├── confirmLeave.cy.js │ ├── customErrors │ │ └── visualizationError.cy.js │ ├── dimensions │ │ ├── calculations.cy.js │ │ ├── data.cy.js │ │ ├── dynamic.cy.js │ │ ├── orgUnit.cy.js │ │ └── period.cy.js │ ├── dimensionsPanel.cy.js │ ├── drillDown.cy.js │ ├── interpretations.cy.js │ ├── layout.cy.js │ ├── new.cy.js │ ├── open.cy.js │ ├── options │ │ ├── axes.cy.js │ │ ├── cumulativeValues.cy.js │ │ ├── fontStyles.cy.js │ │ ├── icon.cy.js │ │ ├── legend.cy.js │ │ ├── limitValues.cy.js │ │ ├── lines.cy.js │ │ └── totals.cy.js │ ├── pushAnalytics.cy.js │ ├── saveAndRename.cy.js │ ├── start.cy.js │ └── visTypes │ │ ├── outlierTable.cy.js │ │ └── scatter.cy.js ├── plugins │ └── excludeByVersionTags.js ├── support │ ├── commands.js │ ├── e2e.js │ ├── generateTestMatrix.js │ ├── index.js │ └── utils.js └── utils │ ├── config.js │ ├── data.js │ ├── random.js │ ├── store.js │ └── window.js ├── d2.config.js ├── docs ├── data-visualizer.md └── resources │ └── images │ ├── data-visualizer-assigned-categories.png │ ├── data-visualizer-cc-calculation-modal.png │ ├── data-visualizer-cc-data-modal-info.png │ ├── data-visualizer-cc-data-modal-options-mode.png │ ├── data-visualizer-cc-data-modal-with-option-set.png │ ├── data-visualizer-cc-data-modal.png │ ├── data-visualizer-cc-data-type.jpg │ ├── data-visualizer-chart-options.png │ ├── data-visualizer-chip-hover.png │ ├── data-visualizer-column-drill.png │ ├── data-visualizer-create-interpretation.png │ ├── data-visualizer-data-dimensions.png │ ├── data-visualizer-delete-dialog.png │ ├── data-visualizer-dimension-add-to-series.png │ ├── data-visualizer-dimension-modal.png │ ├── data-visualizer-dimensions.png │ ├── data-visualizer-dynamic-dimension-modal.png │ ├── data-visualizer-dynamic-dimension.png │ ├── data-visualizer-filter-dimensions.png │ ├── data-visualizer-fixed-period-dimension.png │ ├── data-visualizer-interpretation-actions.png │ ├── data-visualizer-interpretation-detail.png │ ├── data-visualizer-layout-area.png │ ├── data-visualizer-multi-type-chart.png │ ├── data-visualizer-new.png │ ├── data-visualizer-open-ao.png │ ├── data-visualizer-open-as-map.png │ ├── data-visualizer-open-dialog.png │ ├── data-visualizer-open-interpretations.png │ ├── data-visualizer-options-axes-legend.png │ ├── data-visualizer-options-style.png │ ├── data-visualizer-org-unit-dimension.png │ ├── data-visualizer-organisation-unit-dimension-modal.png │ ├── data-visualizer-overview.png │ ├── data-visualizer-period-dimension-modal.png │ ├── data-visualizer-period-dimension.png │ ├── data-visualizer-pt-drill.png │ ├── data-visualizer-rename-dialog.png │ ├── data-visualizer-save-dialog.png │ ├── data-visualizer-series-tab-multi-axis-multi-type.png │ ├── data-visualizer-series-tab-multi-axis.png │ ├── data-visualizer-share-dialog.png │ ├── data-visualizer-text-styling-tool.png │ ├── data-visualizer-two-category.png │ ├── data-visualizer-view-interpretation.png │ └── data-visualizer-visualization-type.png ├── i18n ├── ar.po ├── ar_IQ.po ├── ckb.po ├── cs.po ├── da.po ├── en.pot ├── es.po ├── es_419.po ├── fr.po ├── hi_IN.po ├── id.po ├── km.po ├── lo.po ├── my.po ├── nb.po ├── nl.po ├── or.po ├── prs.po ├── ps.po ├── pt.po ├── pt_BR.po ├── ro.po ├── ru.po ├── si.po ├── sv.po ├── tet.po ├── tg.po ├── uk.po ├── ur.po ├── uz_Cyrl.po ├── uz_Latn.po ├── uz_UZ_Cyrl.po ├── uz_UZ_Latn.po ├── vi.po ├── zh.po └── zh_CN.po ├── jest.config.js ├── package.json ├── public ├── dhis2-app-icon.png ├── favicon.ico ├── fonts │ ├── NotoSans-Bold.ttf │ ├── NotoSans-BoldItalic.ttf │ ├── NotoSans-Italic.ttf │ ├── NotoSans-Regular.ttf │ ├── NotoSansArabic-Bold.ttf │ ├── NotoSansArabic-Regular.ttf │ ├── NotoSansBengali-Bold.ttf │ ├── NotoSansBengali-Regular.ttf │ ├── NotoSansEthiopic-Bold.ttf │ ├── NotoSansEthiopic-Regular.ttf │ ├── NotoSansHebrew-Bold.ttf │ ├── NotoSansHebrew-Regular.ttf │ ├── NotoSansJP-Bold.ttf │ ├── NotoSansJP-Regular.ttf │ ├── NotoSansKR-Bold.ttf │ ├── NotoSansKR-Regular.ttf │ ├── NotoSansKhmer-Bold.ttf │ ├── NotoSansKhmer-Regular.ttf │ ├── NotoSansLao-Bold.ttf │ ├── NotoSansLao-Regular.ttf │ ├── NotoSansMyanmar-Bold.ttf │ ├── NotoSansMyanmar-Regular.ttf │ ├── NotoSansOriya-Bold.ttf │ ├── NotoSansOriya-Regular.ttf │ ├── NotoSansSC-Bold.ttf │ ├── NotoSansSC-Regular.ttf │ ├── NotoSansSinhala-Bold.ttf │ ├── NotoSansSinhala-Regular.ttf │ ├── NotoSansThai-Bold.ttf │ └── NotoSansThai-Regular.ttf ├── push-analytics.json └── vendor │ ├── jspdf.js │ └── svg2pdf.js ├── src ├── AppWrapper.js ├── PluginWrapper.js ├── __tests__ │ └── cypressGetExcludedTags.spec.js ├── actions │ ├── __mocks__ │ │ └── DataEngine.js │ ├── __tests__ │ │ ├── index.rename.spec.js │ │ ├── index.save.spec.js │ │ └── index.spec.js │ ├── current.js │ ├── dimensions.js │ ├── index.js │ ├── loader.js │ ├── metadata.js │ ├── recommendedIds.js │ ├── settings.js │ ├── snackbar.js │ ├── ui.js │ └── visualization.js ├── api │ ├── analytics.js │ ├── dataStatistics.js │ ├── index.js │ ├── legendSets.js │ ├── mostViewedVisualizations.js │ └── visualization.js ├── assets │ ├── AreaIcon.js │ ├── ArrowDown.js │ ├── ArrowDownwardIcon.js │ ├── ArrowUpwardIcon.js │ ├── AxisIcons.js │ ├── BarIcon.js │ ├── BoldIcon.js │ ├── BubbleIcon.js │ ├── ColumnIcon.js │ ├── DataIcon.js │ ├── DynamicDimensionIcon.js │ ├── ErrorIcons.js │ ├── FontColorIcon.js │ ├── GaugeIcon.js │ ├── GlobeIcon.js │ ├── HorizontalIcon.js │ ├── ItalicIcon.js │ ├── LineIcon.js │ ├── OrgUnitIcon.js │ ├── OutlierTableIcon.js │ ├── PeriodIcon.js │ ├── PieIcon.js │ ├── PivotTableIcon.js │ ├── RadarIcon.js │ ├── ScatterIcon.js │ ├── SingleValueIcon.js │ ├── StackedAreaIcon.js │ ├── StackedBarIcon.js │ ├── StackedColumnIcon.js │ ├── VerticalIcon.js │ ├── YearOverYearColumnIcon.js │ ├── YearOverYearLineIcon.js │ ├── chart-error-graphic.png │ └── chart-error-graphic.svg ├── components │ ├── App.css │ ├── App.js │ ├── AxesTabs │ │ ├── AxesTabs.js │ │ └── styles │ │ │ └── AxesTabs.module.css │ ├── ChartProvider.js │ ├── DetailsPanel │ │ ├── DetailsPanel.js │ │ └── styles │ │ │ └── DetailsPanel.module.css │ ├── DimensionsPanel │ │ ├── Dialogs │ │ │ ├── AddToLayoutButton │ │ │ │ └── AddToLayoutButton.js │ │ │ ├── DialogManager.js │ │ │ ├── __tests__ │ │ │ │ ├── DialogManager.spec.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── DialogManager.spec.js.snap │ │ │ └── styles │ │ │ │ └── DialogManager.module.css │ │ ├── DimensionsPanel.js │ │ ├── DndDimensionItem.js │ │ ├── DndDimensionList.js │ │ ├── DndDimensionsPanel.js │ │ ├── __tests__ │ │ │ └── DimensionsPanel.spec.js │ │ └── styles │ │ │ ├── DimensionsPanel.style.js │ │ │ ├── DndDimensionItem.module.css │ │ │ ├── DndDimensionList.module.css │ │ │ └── DndDimensionsPanel.module.css │ ├── DndContext.js │ ├── DownloadMenu │ │ ├── AdvancedSubMenu.js │ │ ├── DownloadMenu.js │ │ ├── GraphicsMenu.js │ │ ├── ModalDownloadDropdown.js │ │ ├── ModalDownloadDropdown.module.css │ │ ├── PlainDataSourceSubMenu.js │ │ ├── TableMenu.js │ │ ├── ToolbarDownloadDropdown.js │ │ ├── computeChartOptionsForExport.js │ │ ├── constants.js │ │ ├── index.js │ │ └── useDownload.js │ ├── HideButton │ │ └── HideButton.js │ ├── IconButton │ │ ├── IconButton.js │ │ └── styles │ │ │ └── IconButton.module.css │ ├── InterpretationModal │ │ ├── InterpretationModal.js │ │ ├── index.js │ │ └── interpretationIdQueryParam.js │ ├── Layout │ │ ├── Chip.js │ │ ├── ChipMenu.js │ │ ├── DefaultLayout │ │ │ ├── DefaultAxis.js │ │ │ ├── DefaultLayout.js │ │ │ └── styles │ │ │ │ ├── DefaultAxis.module.css │ │ │ │ ├── DefaultAxis.style.js │ │ │ │ └── DefaultLayout.style.js │ │ ├── Layout.js │ │ ├── OutlierTable │ │ │ ├── OutlierTableLayout.js │ │ │ └── styles │ │ │ │ └── OutlierTableLayout.style.js │ │ ├── PieLayout │ │ │ ├── PieLayout.js │ │ │ └── styles │ │ │ │ └── PieLayout.style.js │ │ ├── PivotTableLayout │ │ │ ├── PivotTableLayout.js │ │ │ └── styles │ │ │ │ └── PivotTableLayout.style.js │ │ ├── ScatterLayout │ │ │ ├── ScatterAxis.js │ │ │ ├── ScatterLayout.js │ │ │ └── styles │ │ │ │ ├── ScatterAxis.style.js │ │ │ │ └── ScatterLayout.style.js │ │ ├── TooltipContent.js │ │ ├── YearOverYearLayout │ │ │ ├── YearOverYearAxis.js │ │ │ ├── YearOverYearLayout.js │ │ │ ├── YearOverYearSelect.js │ │ │ └── styles │ │ │ │ ├── YearOverYearAxis.style.js │ │ │ │ └── YearOverYearLayout.style.js │ │ ├── __tests__ │ │ │ └── TooltipContent.spec.js │ │ └── styles │ │ │ ├── Chip.module.css │ │ │ ├── Tooltip.style.js │ │ │ └── style.js │ ├── MenuBar │ │ ├── InterpretationsButton.js │ │ └── MenuBar.js │ ├── Snackbar │ │ ├── Snackbar.js │ │ └── styles │ │ │ └── Snackbar.module.css │ ├── TitleBar │ │ ├── TitleBar.js │ │ ├── __tests__ │ │ │ └── TitleBar.spec.js │ │ └── styles │ │ │ └── TitleBar.style.js │ ├── UpdateButton │ │ ├── UpdateButton.js │ │ ├── UpdateVisualizationContainer.js │ │ └── styles │ │ │ └── UpdateButton.style.js │ ├── VerticalTabBar │ │ ├── VerticalTab.js │ │ ├── VerticalTabBar.js │ │ └── styles │ │ │ ├── VerticalTab.module.css │ │ │ └── VerticalTabBar.module.css │ ├── Visualization │ │ ├── StartScreen.js │ │ ├── Visualization.js │ │ ├── __tests__ │ │ │ ├── Visualization.spec.js │ │ │ └── utils.spec.js │ │ ├── styles │ │ │ ├── StartScreen.module.css │ │ │ └── Visualization.style.js │ │ └── utils.js │ ├── VisualizationErrorInfo │ │ ├── VisualizationErrorInfo.js │ │ └── styles │ │ │ └── VisualizationErrorInfo.module.css │ ├── VisualizationOptions │ │ ├── InfoText.js │ │ ├── NoticeBox.js │ │ ├── Options │ │ │ ├── AggregationType.js │ │ │ ├── ApprovalLevel.js │ │ │ ├── AxisDecimals.js │ │ │ ├── AxisLabels.js │ │ │ ├── AxisMaxValue.js │ │ │ ├── AxisMinValue.js │ │ │ ├── AxisRange.js │ │ │ ├── AxisSteps.js │ │ │ ├── AxisTitle.js │ │ │ ├── BaseLine.js │ │ │ ├── CheckboxBaseOption.js │ │ │ ├── ColSubTotals.js │ │ │ ├── ColTotals.js │ │ │ ├── ColorSet.js │ │ │ ├── CompletedOnly.js │ │ │ ├── Cumulative.js │ │ │ ├── CumulativeValues.js │ │ │ ├── DataIcon.js │ │ │ ├── DigitGroupSeparator.js │ │ │ ├── DisplayDensity.js │ │ │ ├── ExtremeLines.js │ │ │ ├── FixColumnHeaders.js │ │ │ ├── FixRowHeaders.js │ │ │ ├── FontSize.js │ │ │ ├── HideEmptyColumns.js │ │ │ ├── HideEmptyRowItems.js │ │ │ ├── HideEmptyRows.js │ │ │ ├── HideSubtitle.js │ │ │ ├── HideTitle.js │ │ │ ├── Legend.js │ │ │ ├── LegendDisplayStrategy.js │ │ │ ├── LegendDisplayStyle.js │ │ │ ├── LegendSet.js │ │ │ ├── MeasureCriteria.js │ │ │ ├── NoSpaceBetweenColumns.js │ │ │ ├── NumberBaseType.js │ │ │ ├── NumberType.js │ │ │ ├── OutlierDetectionMethod.js │ │ │ ├── Outliers.js │ │ │ ├── OutliersForOutlierTable.js │ │ │ ├── OutliersMaxResults.js │ │ │ ├── ParamOrganisationUnit.js │ │ │ ├── ParamParentOrganisationUnit.js │ │ │ ├── ParamReportingPeriod.js │ │ │ ├── PercentStackedValues.js │ │ │ ├── PositiveNumberBaseType.js │ │ │ ├── RadioBaseOption.js │ │ │ ├── Regression.js │ │ │ ├── RegressionLine.js │ │ │ ├── RegressionLineTitle.js │ │ │ ├── RegressionLineValue.js │ │ │ ├── RegressionType.js │ │ │ ├── RowSubTotals.js │ │ │ ├── RowTotals.js │ │ │ ├── SelectBaseOption.js │ │ │ ├── SeriesTable.js │ │ │ ├── ShowData.js │ │ │ ├── ShowDimensionLabels.js │ │ │ ├── ShowHierarchy.js │ │ │ ├── ShowLegendKey.js │ │ │ ├── ShowSeriesKey.js │ │ │ ├── SkipRounding.js │ │ │ ├── SortOrder.js │ │ │ ├── Subtitle.js │ │ │ ├── TargetLine.js │ │ │ ├── TextBaseOption.js │ │ │ ├── TextStyle.js │ │ │ ├── Title.js │ │ │ └── TopLimit.js │ │ ├── VisualizationOptionsManager.js │ │ ├── __tests__ │ │ │ ├── CheckboxBaseOption.spec.js │ │ │ ├── SelectBaseOption.spec.js │ │ │ └── TextBaseOption.spec.js │ │ └── styles │ │ │ ├── AxisRange.module.css │ │ │ ├── Outliers.module.css │ │ │ ├── SeriesTable.module.css │ │ │ ├── TextStyle.module.css │ │ │ └── VisualizationOptions.module.css │ ├── VisualizationPlugin │ │ ├── ChartPlugin.js │ │ ├── ContextualMenu.js │ │ ├── OutlierTablePlugin.js │ │ ├── PivotPlugin.js │ │ ├── VisualizationPlugin.js │ │ ├── VisualizationPluginWrapper.js │ │ ├── __tests__ │ │ │ ├── ChartPlugin.spec.js │ │ │ ├── VisualizationPlugin.spec.js │ │ │ ├── yoy-daily-edge-cases.spec.js │ │ │ └── yoy-weekly-edge-cases.spec.js │ │ └── styles │ │ │ ├── ChartPlugin.module.css │ │ │ ├── OutlierTablePlugin.module.css │ │ │ ├── PivotPlugin.style.js │ │ │ └── VisualizationPlugin.module.css │ ├── VisualizationTypeSelector │ │ ├── ListItemIcon.js │ │ ├── VisualizationTypeListItem.js │ │ ├── VisualizationTypeSelector.js │ │ ├── __tests__ │ │ │ └── VisualizationTypeListItem.spec.js │ │ └── styles │ │ │ └── VisualizationTypeSelector.module.css │ ├── __tests__ │ │ └── App.spec.js │ └── scrollbar.css ├── configureStore.js ├── middleware │ └── metadata.js ├── modules │ ├── __tests__ │ │ ├── current.spec.js │ │ ├── currentAnalyticalObject.spec.js │ │ ├── error.spec.js │ │ ├── layout.spec.js │ │ ├── orgUnit.spec.js │ │ ├── ui.spec.js │ │ ├── visualization.test.js │ │ └── yearOverYear.spec.js │ ├── analytics.js │ ├── current.js │ ├── currentAnalyticalObject.js │ ├── dataSets.js │ ├── disabledOptions.js │ ├── dnd.js │ ├── error.js │ ├── fetchData.js │ ├── fields │ │ ├── __tests__ │ │ │ └── baseFields.spec.js │ │ ├── baseFields.js │ │ ├── index.js │ │ └── nestedFields.js │ ├── getNotoPdfFontForLocale │ │ ├── index.js │ │ └── notoFontLookup.js │ ├── getPWAInstallationStatus.js │ ├── getRequestOptions.js │ ├── history.js │ ├── layout.js │ ├── layoutValidation.js │ ├── metadata.js │ ├── options.js │ ├── options │ │ ├── config.js │ │ ├── defaultConfig.js │ │ ├── gaugeConfig.js │ │ ├── outlierTableConfig.js │ │ ├── pieConfig.js │ │ ├── pivotTableConfig.js │ │ ├── scatterConfig.js │ │ ├── sections │ │ │ ├── advanced.js │ │ │ ├── chartStyle.js │ │ │ ├── colorSet.js │ │ │ ├── display.js │ │ │ ├── domainAxis.js │ │ │ ├── lines.js │ │ │ ├── rangeAxis.js │ │ │ ├── templates │ │ │ │ ├── advanced.js │ │ │ │ ├── chartStyle.js │ │ │ │ ├── display.js │ │ │ │ ├── emptyData.js │ │ │ │ ├── horizontalAxis.js │ │ │ │ ├── lines.js │ │ │ │ ├── totals.js │ │ │ │ └── verticalAxis.js │ │ │ └── titles.js │ │ ├── singleValueConfig.js │ │ └── tabs │ │ │ ├── axes.js │ │ │ ├── data.js │ │ │ ├── legend.js │ │ │ ├── limitValues.js │ │ │ ├── outliers.js │ │ │ ├── series.js │ │ │ └── style.js │ ├── orgUnit.js │ ├── systemSettings.js │ ├── ui.js │ ├── userSettings.js │ ├── visualization.js │ └── yearOverYear.js ├── reducers │ ├── __tests__ │ │ ├── current.spec.js │ │ ├── dimensions.spec.js │ │ ├── loader.spec.js │ │ ├── metadata.spec.js │ │ ├── recommendedIds.spec.js │ │ ├── settings.spec.js │ │ ├── snackbar.spec.js │ │ ├── ui.spec.js │ │ └── visualization.spec.js │ ├── current.js │ ├── dimensions.js │ ├── index.js │ ├── loader.js │ ├── metadata.js │ ├── recommendedIds.js │ ├── settings.js │ ├── snackbar.js │ ├── ui.js │ └── visualization.js └── widgets │ ├── LoadingMask.js │ └── styles │ └── LoadingMask.module.css ├── yarn-error.log └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { config } = require('@dhis2/cli-style') 2 | 3 | module.exports = { 4 | extends: [config.eslintReact, 'plugin:cypress/recommended'], 5 | } 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 5 8 | versioning-strategy: increase 9 | groups: 10 | security: 11 | applies-to: security-updates 12 | update-types: 13 | - minor 14 | - patch 15 | dependencies: 16 | applies-to: version-updates 17 | update-types: 18 | - minor 19 | - patch 20 | exclude-patterns: 21 | - '*@dhis2*' 22 | - '*i18next*' 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Implements [DHIS2-XXXX](https://dhis2.atlassian.net/browse/DHIS2-XXXX) 2 | 3 | ### Description 4 | 5 | _text_ 6 | 7 | --- 8 | 9 | ### Quality checklist 10 | 11 | Add _N/A_ to items that are not applicable. 12 | 13 | - [ ] Dashboard tested 14 | - [ ] Cypress and/or Jest tests added/updated 15 | - [ ] Docs added 16 | - [ ] d2-ci dependency replaced (requires https://github.com/dhis2/analytics/pull/XXX) 17 | - [ ] Tester approved (name) 18 | 19 | --- 20 | 21 | ### ToDos 22 | 23 | - [ ] _todo_ 24 | 25 | --- 26 | 27 | ### Known issues 28 | 29 | - [ ] _issue_ 30 | 31 | --- 32 | 33 | ### Screenshots 34 | 35 | _supporting images_ 36 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | -------------------------------------------------------------------------------- /.github/workflows/check-tasklist.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Task List Checker 2 | on: 3 | pull_request: 4 | types: [opened, edited, synchronize, reopened] 5 | 6 | jobs: 7 | task-list-checker: 8 | if: ${{ github.actor != 'dependabot[bot]' }} 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check for incomplete task list items 12 | uses: Shopify/task-list-checker@main 13 | with: 14 | github-token: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/generate-and-upload-bom.yml: -------------------------------------------------------------------------------- 1 | name: 'This workflow creates bill of material and uploads it to Dependency-Track each night' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.head_ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | create-bom: 13 | uses: dhis2/workflows-platform/.github/workflows/generate-and-upload-bom.yml@v1 14 | with: 15 | node_version: 20 16 | project_id: '20ef5819-4a79-4d74-bb32-7c0d02af89bf' 17 | secrets: inherit 18 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: 'dhis2: nightly' 2 | 3 | # This workflow runs the e2e tests on the default branch against dev at 6:50am M-F 4 | 5 | on: 6 | # schedule: 7 | # - cron: '50 4 * * 1-5' 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow}}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | defaults: 15 | run: 16 | shell: bash 17 | 18 | jobs: 19 | call-workflow-e2e-dev: 20 | uses: dhis2/workflows/.github/workflows/analytics-e2e-tests-dev.yml@master 21 | secrets: 22 | username: ${{ secrets.CYPRESS_DHIS2_USERNAME }} 23 | password: ${{ secrets.CYPRESS_DHIS2_PASSWORD }} 24 | recordkey: ${{ secrets.CYPRESS_RECORD_KEY }} 25 | 26 | send-slack-message: 27 | runs-on: ubuntu-latest 28 | needs: call-workflow-e2e-dev 29 | if: | 30 | failure() && 31 | !cancelled() 32 | 33 | steps: 34 | - name: Send failure message to analytics-internal-kfmt slack channel 35 | id: slack 36 | uses: slackapi/slack-github-action@v1.27.0 37 | with: 38 | channel-id: ${{ secrets.SLACK_CHANNEL_ID }} 39 | slack-message: ':data-visualizer-app: Data-visualizer-app e2e nightly run ' 40 | env: 41 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # DHIS2 platform 2 | node_modules 3 | .d2 4 | src/locales 5 | build 6 | coverage 7 | cypress.env.json 8 | cypress/screenshots 9 | cypress/videos 10 | cypress/downloads 11 | 12 | # Custom 13 | .vscode 14 | .gitignore 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /.hooks/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.hooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn d2-style check commit "$1" 5 | -------------------------------------------------------------------------------- /.hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn d2-app-scripts i18n extract && \ 5 | git add i18n && \ 6 | yarn d2-style check --staged 7 | -------------------------------------------------------------------------------- /.hooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn validate-push 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | .d2 4 | src/locales 5 | build 6 | cypress.env.json 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const { config } = require('@dhis2/cli-style') 2 | 3 | module.exports = { 4 | ...require(config.prettier), 5 | overrides: [ 6 | { 7 | files: 'pull_request_template.md', 8 | options: { 9 | tabWidth: 2, 10 | }, 11 | }, 12 | ], 13 | } 14 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "master" 4 | ], 5 | "plugins": [ 6 | "@semantic-release/commit-analyzer", 7 | "@semantic-release/release-notes-generator", 8 | "@semantic-release/changelog", 9 | "@semantic-release/npm", 10 | [ 11 | "@semantic-release/exec", 12 | { 13 | "prepareCmd": "yarn build" 14 | } 15 | ], 16 | [ 17 | "@semantic-release/git", 18 | { 19 | "assets": [ 20 | "CHANGELOG.md", 21 | "package.json" 22 | ], 23 | "message": "chore(release): cut ${nextRelease.version} [skip release]\n\n${nextRelease.notes}" 24 | } 25 | ], 26 | [ 27 | "@semantic-release/github", 28 | { 29 | "assets": [ 30 | { 31 | "path": "build/bundle/*.zip", 32 | "label": "DHIS2 app bundle" 33 | } 34 | ] 35 | } 36 | ] 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | const { config } = require('@dhis2/cli-style') 2 | 3 | module.exports = { 4 | extends: [config.stylelint], 5 | rules: { 6 | 'csstools/use-logical': [ 7 | true, 8 | { 9 | severity: 'error', 10 | }, 11 | ], 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | lang_map = fa_AF: prs, uz@Cyrl: uz_UZ_Cyrl, uz@Latn: uz_UZ_Latn 4 | 5 | [o:hisp-uio:p:app-data-visualizer:r:en-pot] 6 | file_filter = i18n/.po 7 | source_file = i18n/en.pot 8 | source_lang = en 9 | type = PO 10 | minimum_perc = 0 11 | -------------------------------------------------------------------------------- /config/testSetup.js: -------------------------------------------------------------------------------- 1 | import 'jest-enzyme' 2 | import 'jest-webgl-canvas-mock' 3 | import Enzyme from 'enzyme' 4 | import Adapter from 'enzyme-adapter-react-16' 5 | 6 | Enzyme.configure({ adapter: new Adapter() }) 7 | -------------------------------------------------------------------------------- /config/testsContext.js: -------------------------------------------------------------------------------- 1 | export function getStubContext() { 2 | return { 3 | i18n: { 4 | t: () => {}, 5 | }, 6 | store: { 7 | dispatch: () => {}, 8 | }, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /cypress/elements/confirmLeaveModal.js: -------------------------------------------------------------------------------- 1 | const confirmLeaveModalEl = 'confirm-leave-modal' 2 | const optionCancel = 'confirm-leave-modal-option-cancel' 3 | const optionConfirm = 'confirm-leave-modal-option-confirm' 4 | 5 | export const confirmLeave = (shouldLeave) => 6 | cy.getBySel(shouldLeave ? optionConfirm : optionCancel).click() 7 | 8 | export const expectConfirmLeaveModalToBeVisible = () => 9 | cy.getBySel(confirmLeaveModalEl).should('have.length', 1).and('be.visible') 10 | -------------------------------------------------------------------------------- /cypress/elements/dimensionModal/dynamicDimension.js: -------------------------------------------------------------------------------- 1 | const selectionTypeManualEl = 'dynamic-dimension-selection-type-manual' 2 | const selectionTypeAutomaticEl = 'dynamic-dimension-selection-type-automatic' 3 | const modalContentEl = 'dialog-manager-modal-content' 4 | 5 | export const expectManualSelectionToBeChecked = () => 6 | cy 7 | .getBySel(selectionTypeManualEl) 8 | .find('[type="radio"]') 9 | .should('be.checked') 10 | 11 | export const expectAutomaticSelectionToBeChecked = () => 12 | cy 13 | .getBySel(selectionTypeAutomaticEl) 14 | .find('[type="radio"]') 15 | .should('be.checked') 16 | 17 | export const changeSelectionToAutomatic = () => 18 | cy 19 | .getBySelLike(modalContentEl) 20 | .contains('Automatically include all items') 21 | .click() 22 | 23 | export const changeSelectionToManual = () => 24 | cy.getBySelLike(modalContentEl).contains('Manually select items...').click() 25 | -------------------------------------------------------------------------------- /cypress/elements/drillDownMenu.js: -------------------------------------------------------------------------------- 1 | const drillDownOrgUnitEl = 'visualization-drill-down-menu-org-unit' 2 | const drillDownOrgUnitDrillUpEl = 3 | 'visualization-drill-down-menu-org-unit-drill-up' 4 | const drillDownOrgUnitDrillDownEl = 5 | 'visualization-drill-down-menu-org-unit-drill-down' 6 | 7 | export const expectDrillDownMenuToBeVisible = () => 8 | cy.getBySel(drillDownOrgUnitEl).should('be.visible') 9 | 10 | export const expectDrillDownMenuToNotBeVisible = () => 11 | cy.getBySel(drillDownOrgUnitEl).should('not.exist') 12 | 13 | export const clickChangeOrgUnit = () => 14 | cy.getBySel(drillDownOrgUnitEl).containsExact('Change org unit').click() 15 | 16 | export const drillDown = (option) => 17 | cy.getBySel(drillDownOrgUnitDrillDownEl).containsExact(option).click() 18 | 19 | export const drillUp = (option) => 20 | cy.getBySel(drillDownOrgUnitDrillUpEl).containsExact(option).click() 21 | -------------------------------------------------------------------------------- /cypress/elements/fileMenu/save.js: -------------------------------------------------------------------------------- 1 | import { clearInput, typeInput } from '../common.js' 2 | import { clickMenuBarFileButton } from '../menuBar.js' 3 | import { 4 | clickFileMenuButton, 5 | FILE_MENU_BUTTON_SAVE_EXISTING, 6 | FILE_MENU_BUTTON_SAVE_NEW, 7 | FILE_MENU_BUTTON_SAVEAS, 8 | } from './index.js' 9 | 10 | const saveModalNameEl = 'file-menu-saveas-modal-name' 11 | const saveModalDescriptionEl = 'file-menu-saveas-modal-description' 12 | const saveModalSaveButtonEl = 'file-menu-saveas-modal-save' 13 | 14 | export const saveNewAO = (name, description) => { 15 | clickMenuBarFileButton() 16 | clickFileMenuButton(FILE_MENU_BUTTON_SAVE_NEW) 17 | clearInput(saveModalNameEl) 18 | typeInput(saveModalNameEl, name) 19 | if (description) { 20 | cy.getBySel(saveModalDescriptionEl).find('textarea').type(description) 21 | } 22 | cy.getBySel(saveModalSaveButtonEl).click() 23 | } 24 | 25 | export const saveExistingAO = () => { 26 | clickMenuBarFileButton() 27 | clickFileMenuButton(FILE_MENU_BUTTON_SAVE_EXISTING) 28 | } 29 | 30 | export const saveAOAs = (name, description) => { 31 | clickMenuBarFileButton() 32 | clickFileMenuButton(FILE_MENU_BUTTON_SAVEAS) 33 | if (name) { 34 | clearInput(saveModalNameEl) 35 | typeInput(saveModalNameEl, name) 36 | } 37 | if (description) { 38 | cy.getBySel(saveModalDescriptionEl) 39 | .find('textarea') 40 | .clear() 41 | .type(description) 42 | } 43 | cy.getBySel(saveModalSaveButtonEl).click() 44 | } 45 | -------------------------------------------------------------------------------- /cypress/elements/menuBar.js: -------------------------------------------------------------------------------- 1 | export const menubarEl = 'dhis2-analytics-hovermenubar' 2 | const updateButton = 'app-menubar-update-button' 3 | const optionsButton = 'app-menubar-options-button' 4 | 5 | export const clickMenuBarUpdateButton = () => cy.getBySel(updateButton).click() 6 | 7 | export const clickMenuBarFileButton = () => 8 | cy.getBySel(menubarEl).contains('File').click() 9 | 10 | export function clickMenuBarOptionsButton() { 11 | return cy.getBySel(optionsButton).click() 12 | } 13 | 14 | export const openOptionsModal = (section = 'Data') => { 15 | clickMenuBarOptionsButton() 16 | return cy.getBySel('options-menu-list').contains(section).click() 17 | } 18 | -------------------------------------------------------------------------------- /cypress/elements/optionsModal/limitValues.js: -------------------------------------------------------------------------------- 1 | import { typeInput } from '../common.js' 2 | 3 | const minValueInput = 'measure-critiera-min-value' 4 | const maxValueInput = 'measure-critiera-max-value' 5 | const minOperatorSelect = 'measure-critiera-min-operator' 6 | const minOperatorSelectOption = 'measure-critiera-min-operator-option' 7 | const maxOperatorSelect = 'measure-critiera-max-operator' 8 | const maxOperatorSelectOption = 'measure-critiera-max-operator-option' 9 | 10 | export const setMinValue = (text) => typeInput(minValueInput, text) 11 | 12 | export const setMaxValue = (text) => typeInput(maxValueInput, text) 13 | 14 | export const changeMinOperator = (optionName) => { 15 | cy.getBySel(minOperatorSelect).click() 16 | cy.getBySelLike(minOperatorSelectOption).contains(optionName).click() 17 | } 18 | 19 | export const changeMaxOperator = (optionName) => { 20 | cy.getBySel(maxOperatorSelect).click() 21 | cy.getBySelLike(maxOperatorSelectOption).contains(optionName).click() 22 | } 23 | 24 | export const expectMinValueToBeValue = (value) => 25 | cy.getBySel(minValueInput).find('input').should('have.value', value) 26 | 27 | export const expectMaxValueToBeValue = (value) => 28 | cy.getBySel(maxValueInput).find('input').should('have.value', value) 29 | 30 | export const expectMinOperatorToBeOption = (optionName) => 31 | cy.getBySel(minOperatorSelect).containsExact(optionName) 32 | 33 | export const expectMaxOperatorToBeOption = (optionName) => 34 | cy.getBySel(maxOperatorSelect).containsExact(optionName) 35 | -------------------------------------------------------------------------------- /cypress/elements/optionsModal/lines.js: -------------------------------------------------------------------------------- 1 | import { checkCheckbox, typeInput } from '../common.js' 2 | 3 | const trendLineCheckboxEl = 'option-trend-line-checkbox' 4 | const trendLineSelectEl = 'option-trend-line-select' 5 | const trendLineSelectOptionEl = 'option-trend-line-option' 6 | const targetLineCheckboxEl = 'option-target-line-checkbox' 7 | const targetLineValueInputEl = 'option-target-line-value-input' 8 | const targetLineLabelInputEl = 'option-target-line-label-input' 9 | const baseLineCheckboxEl = 'option-base-line-checkbox' 10 | const baseLineValueInputEl = 'option-base-line-value-input' 11 | const baseLineLabelInputEl = 'option-base-line-label-input' 12 | 13 | export const checkTrendLineCheckbox = () => checkCheckbox(trendLineCheckboxEl) 14 | 15 | export const selectTrendLineType = (optionName) => { 16 | cy.getBySel(trendLineSelectEl).findBySel('dhis2-uicore-select').click() 17 | cy.getBySel(trendLineSelectOptionEl).contains(optionName).click() 18 | } 19 | 20 | export const checkTargetLineCheckbox = () => checkCheckbox(targetLineCheckboxEl) 21 | 22 | export const setTargetLineValue = (text) => 23 | typeInput(targetLineValueInputEl, text) 24 | 25 | export const setTargetLineLabel = (text) => 26 | typeInput(targetLineLabelInputEl, text) 27 | 28 | export const checkBaseLineCheckbox = () => checkCheckbox(baseLineCheckboxEl) 29 | 30 | export const setBaseLineValue = (text) => typeInput(baseLineValueInputEl, text) 31 | 32 | export const setBaseLineLabel = (text) => typeInput(baseLineLabelInputEl, text) 33 | -------------------------------------------------------------------------------- /cypress/elements/optionsModal/outliers.js: -------------------------------------------------------------------------------- 1 | import { checkCheckbox } from '../common.js' 2 | 3 | const outliersCheckboxEl = 'option-outliers-enabled-checkbox' 4 | 5 | export const checkOutliersCheckbox = () => checkCheckbox(outliersCheckboxEl) 6 | -------------------------------------------------------------------------------- /cypress/elements/optionsModal/series.js: -------------------------------------------------------------------------------- 1 | export const setItemToAxis = (itemIndex, axis) => 2 | cy 3 | .getBySel('series-table-item') 4 | .eq(itemIndex) 5 | .findBySel(`item-axis-${axis}`) 6 | .click() 7 | 8 | export const setItemToType = (itemIndex, type) => 9 | cy 10 | .getBySel('series-table-item') 11 | .eq(itemIndex) 12 | .findBySel(`item-type-${type}`) 13 | .click() 14 | -------------------------------------------------------------------------------- /cypress/elements/optionsModal/subtitle.js: -------------------------------------------------------------------------------- 1 | import { typeInput } from '../common.js' 2 | 3 | const typeRadioEl = 'option-chart-subtitle-type-radios' 4 | const textEl = 'option-chart-subtitle-text-input' 5 | const customOption = 'Custom' 6 | 7 | export const setCustomSubtitle = (text) => { 8 | cy.getBySel(typeRadioEl).contains(customOption).click() 9 | typeInput(textEl, text) 10 | } 11 | -------------------------------------------------------------------------------- /cypress/elements/pivotTable.js: -------------------------------------------------------------------------------- 1 | const valueCellEl = 'visualization-value-cell' 2 | const headerCellEl = 'visualization-column-header' 3 | 4 | export const expectTableToBeVisible = () => 5 | cy.get('.pivot-table-container').should('have.length', 1).and('be.visible') 6 | 7 | export const clickTableValueCell = (index) => 8 | cy.getBySel(valueCellEl).eq(index).click() 9 | 10 | export const expectTableValueCellsToHaveLength = (length) => 11 | cy.getBySel(valueCellEl).should('have.length', length) 12 | 13 | export const expectTableValueCellToContainValue = (index, value) => 14 | cy.getBySel(valueCellEl).eq(index).contains(value) 15 | 16 | export const clickTableHeaderCell = (name) => 17 | cy.getBySel(headerCellEl).contains(name).click() 18 | -------------------------------------------------------------------------------- /cypress/elements/route.js: -------------------------------------------------------------------------------- 1 | const lengthOfIDs = 11 2 | 3 | const getRouteFromHash = (hash) => hash.slice(hash.lastIndexOf('/') + 1) 4 | 5 | export const expectRouteToBeAOId = () => 6 | cy 7 | .location() 8 | .should((loc) => 9 | expect(getRouteFromHash(loc.hash)).to.match( 10 | new RegExp(`[A-Za-z0-9]{${lengthOfIDs}}`, 'gm') 11 | ) 12 | ) 13 | 14 | export const expectRouteToBeEmpty = () => 15 | cy 16 | .location() 17 | .should((loc) => expect(getRouteFromHash(loc.hash)).to.have.length(0)) 18 | -------------------------------------------------------------------------------- /cypress/elements/startScreen.js: -------------------------------------------------------------------------------- 1 | import { EXTENDED_TIMEOUT } from '../support/utils.js' 2 | 3 | //const startScreen = '*[class^="StartScreen_outer"]' 4 | const primaryTitleText = 'Getting started' 5 | const primaryTitleEl = 'start-screen-primary-section-title' 6 | const secondaryTitleText = 'Your most viewed charts and tables' 7 | const secondaryTitleEl = 'start-screen-secondary-section-title' 8 | const mostViewedListItemAmount = 6 9 | const mostViewedListItemEl = 'start-screen-most-viewed-list-item' 10 | 11 | export const goToStartPage = () => { 12 | cy.visit('').log(Cypress.env('dhis2BaseUrl')) 13 | expectStartScreenToBeVisible() 14 | } 15 | 16 | export const expectStartScreenToBeVisible = () => 17 | cy 18 | .getBySel(primaryTitleEl, EXTENDED_TIMEOUT) 19 | .should('contain', primaryTitleText) 20 | 21 | export const expectMostViewedToBeVisible = () => { 22 | cy.getBySel(secondaryTitleEl).should('contain', secondaryTitleText) 23 | expectMostViewedToHaveItems() 24 | } 25 | 26 | export const expectMostViewedToHaveItems = () => 27 | cy 28 | .getBySel(mostViewedListItemEl) 29 | .should('have.length', mostViewedListItemAmount) 30 | -------------------------------------------------------------------------------- /cypress/elements/visualizationErrorInfo.js: -------------------------------------------------------------------------------- 1 | const errorContainerEl = 'visualization-error-container' 2 | 3 | export const expectErrorToContainTitle = (errorTitle) => 4 | cy.getBySel(errorContainerEl).should('contain', errorTitle) 5 | -------------------------------------------------------------------------------- /cypress/elements/visualizationTypeSelector.js: -------------------------------------------------------------------------------- 1 | const vstCardEl = 'visualization-type-selector-card' 2 | const vstButtonEl = 'visualization-type-selector-button' 3 | const vstButtonTextEl = 'visualization-type-selector-currently-selected-text' 4 | const defaultVisTypeName = 'Pivot table' 5 | 6 | export const clickVisTypeSelector = () => cy.getBySel(vstButtonEl).click() 7 | 8 | export const changeVisType = (visTypeName) => { 9 | clickVisTypeSelector() 10 | cy.getBySel(vstCardEl).contains(visTypeName).click() 11 | } 12 | 13 | export const expectVisTypeToBeValue = (value) => 14 | cy 15 | .getBySel(vstButtonTextEl) 16 | .contains(value) 17 | .should('have.length', 1) 18 | .and('be.visible') 19 | 20 | export const expectVisTypeToBeDefault = () => 21 | cy 22 | .getBySel(vstButtonTextEl) 23 | .contains(defaultVisTypeName) 24 | .should('have.length', 1) 25 | .and('be.visible') 26 | -------------------------------------------------------------------------------- /cypress/elements/window.js: -------------------------------------------------------------------------------- 1 | const windowTitleDefault = 'Data Visualizer | DHIS2' 2 | 3 | export const expectWindowTitleToBeDefault = () => 4 | cy.title().should('equal', windowTitleDefault) 5 | -------------------------------------------------------------------------------- /cypress/integration/interpretations.cy.js: -------------------------------------------------------------------------------- 1 | import { VIS_TYPE_BAR } from '@dhis2/analytics' 2 | import { 3 | expectAOTitleToBeValue, 4 | expectVisualizationToBeVisible, 5 | } from '../elements/chart.js' 6 | import { openAOByName } from '../elements/fileMenu/index.js' 7 | import { goToStartPage } from '../elements/startScreen.js' 8 | 9 | describe('Interpretations', () => { 10 | it('opens the interpretations modal on a saved AO', () => { 11 | const ao = { 12 | name: 'ANC: 1 and 3 coverage Yearly', 13 | type: VIS_TYPE_BAR, 14 | } 15 | 16 | // Open the saved AO 17 | goToStartPage() 18 | openAOByName(ao.name) 19 | expectAOTitleToBeValue(ao.name) 20 | expectVisualizationToBeVisible(ao.type) 21 | 22 | // Open the interpretations panel 23 | cy.getBySel('dhis2-analytics-interpretationsanddetailstoggler').click() 24 | 25 | cy.getBySel('interpretations-list') 26 | .find('button') 27 | .contains('See interpretation') 28 | .click() 29 | 30 | cy.getBySel('interpretation-modal').should('be.visible') 31 | 32 | cy.get('.highcharts-container').should('be.visible') 33 | 34 | cy.getBySel('interpretation-modal') 35 | .find('button') 36 | .contains('Hide interpretation') 37 | .click() 38 | 39 | cy.getBySel('interpretation-modal').should('not.exist') 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '@dhis2/cypress-commands' 2 | 3 | Cypress.Commands.add('getReduxState', (prop) => 4 | cy.window().its('store').invoke('getState').its(prop) 5 | ) 6 | 7 | Cypress.Commands.add('getBySel', (selector, ...args) => 8 | cy.get(`[data-test=${selector}]`, ...args) 9 | ) 10 | 11 | Cypress.Commands.add('getBySelLike', (selector, ...args) => 12 | cy.get(`[data-test*=${selector}]`, ...args) 13 | ) 14 | 15 | Cypress.Commands.add( 16 | 'findBySel', 17 | { 18 | prevSubject: true, 19 | }, 20 | (subject, selector, ...args) => 21 | cy.wrap(subject).find(`[data-test=${selector}]`, ...args) 22 | ) 23 | 24 | Cypress.Commands.add( 25 | 'findBySelLike', 26 | { 27 | prevSubject: true, 28 | }, 29 | (subject, selector, ...args) => 30 | cy.wrap(subject).find(`[data-test*=${selector}]`, ...args) 31 | ) 32 | 33 | Cypress.Commands.add( 34 | 'containsExact', 35 | { 36 | prevSubject: true, 37 | }, 38 | (subject, selector) => 39 | cy.wrap(subject).contains( 40 | new RegExp( 41 | `^${selector.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}$`, //eslint-disable-line no-useless-escape 42 | 'gm' 43 | ) 44 | ) 45 | ) 46 | 47 | Cypress.Commands.add( 48 | 'closePopper', 49 | { 50 | prevSubject: true, 51 | }, 52 | (subject) => 53 | cy 54 | .wrap(subject) 55 | .closest('[data-test=dhis2-uicore-layer]') 56 | .click('topLeft') 57 | ) 58 | -------------------------------------------------------------------------------- /cypress/support/generateTestMatrix.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const getAllFiles = (dirPath, arrayOfFiles = []) => { 5 | const files = fs.readdirSync(dirPath) 6 | 7 | files.forEach((file) => { 8 | if (fs.statSync(path.join(dirPath, file)).isDirectory()) { 9 | arrayOfFiles = getAllFiles(path.join(dirPath, file), arrayOfFiles) 10 | } else if (path.extname(file) === '.js') { 11 | arrayOfFiles.push(path.join(dirPath, file)) 12 | } 13 | }) 14 | 15 | return arrayOfFiles 16 | } 17 | 18 | const createGroups = (files, numberOfGroups = 5) => { 19 | const groups = [] 20 | for (let i = 0; i < numberOfGroups; i++) { 21 | groups.push([]) 22 | } 23 | 24 | files.forEach((file, index) => { 25 | groups[index % numberOfGroups].push(file) 26 | }) 27 | 28 | return groups.map((group, index) => ({ id: index + 1, tests: group })) 29 | } 30 | 31 | const cypressSpecsPath = './cypress/integration' 32 | const specs = getAllFiles(cypressSpecsPath) 33 | const groupedSpecs = createGroups(specs) 34 | 35 | console.log(JSON.stringify(groupedSpecs)) 36 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | import { enableAutoLogin } from '@dhis2/cypress-commands' 2 | 3 | import './commands.js' 4 | 5 | enableAutoLogin() 6 | 7 | const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/ 8 | Cypress.on('uncaught:exception', (err) => { 9 | // This prevents a benign error: 10 | // This error means that ResizeObserver was not able to deliver all 11 | // observations within a single animation frame. It is benign (your site 12 | // will not break). 13 | // 14 | // Source: https://stackoverflow.com/a/50387233/1319140 15 | if (resizeObserverLoopErrRe.test(err.message)) { 16 | // returning false here prevents Cypress from failing the test 17 | return false 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /cypress/support/utils.js: -------------------------------------------------------------------------------- 1 | export const EXTENDED_TIMEOUT = { timeout: 25000 } 2 | -------------------------------------------------------------------------------- /cypress/utils/random.js: -------------------------------------------------------------------------------- 1 | import { visTypes } from './data.js' 2 | 3 | export const generateRandomChar = () => 4 | Math.random() 5 | .toString(36) 6 | .replace(/[^a-z]+/g, '') 7 | .substr(0, 1) 8 | 9 | export const generateRandomNumber = (min, max) => 10 | Math.floor(Math.random() * (max - min + 1)) + min 11 | 12 | export const getRandomArrayItem = (array) => 13 | array[generateRandomNumber(0, array.length - 1)] 14 | 15 | export const getRandomVisType = () => getRandomArrayItem(visTypes) 16 | 17 | export const generateRandomBool = () => Math.random() < 0.5 18 | -------------------------------------------------------------------------------- /cypress/utils/store.js: -------------------------------------------------------------------------------- 1 | // TODO: Rename file to Store and move to /elements? 2 | 3 | export const expectStoreCurrentToBeEmpty = () => 4 | cy.getReduxState('current').should('be.null') 5 | 6 | export const expectStoreCurrentColumnsToHaveLength = (length) => 7 | cy.getReduxState('current').its('columns').should('have.length', length) 8 | 9 | // export const expectStoreCurrentFilterDimensionToHaveItemsLength = ( 10 | // filterDimension, 11 | // itemsLength 12 | // ) => cy.getReduxState('current').its('filters') 13 | // FIXME: Implement filters.find(filter => filter.dimension === dimensionId).items.length === indicators.length) 14 | -------------------------------------------------------------------------------- /d2.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | type: 'app', 3 | name: 'data-visualizer', 4 | id: '6f656971-c392-42d8-8363-eb37d9287f3d', 5 | title: 'Data Visualizer', 6 | coreApp: true, 7 | 8 | minDHIS2Version: '2.40', 9 | 10 | pluginType: 'DASHBOARD', 11 | 12 | direction: 'auto', 13 | 14 | pwa: { 15 | enabled: true, 16 | caching: { 17 | patternsToOmitFromAppShell: [/.*/], 18 | globsToOmitFromPrecache: [ 19 | 'fonts/**', 20 | 'vendor/jspdf.js', 21 | 'vendor/svg2pdf.js', 22 | ], 23 | }, 24 | }, 25 | 26 | entryPoints: { 27 | app: './src/AppWrapper.js', 28 | plugin: './src/PluginWrapper.js', 29 | }, 30 | } 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-assigned-categories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-assigned-categories.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-cc-calculation-modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-cc-calculation-modal.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-cc-data-modal-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-cc-data-modal-info.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-cc-data-modal-options-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-cc-data-modal-options-mode.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-cc-data-modal-with-option-set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-cc-data-modal-with-option-set.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-cc-data-modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-cc-data-modal.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-cc-data-type.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-cc-data-type.jpg -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-chart-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-chart-options.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-chip-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-chip-hover.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-column-drill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-column-drill.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-create-interpretation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-create-interpretation.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-data-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-data-dimensions.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-delete-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-delete-dialog.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-dimension-add-to-series.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-dimension-add-to-series.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-dimension-modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-dimension-modal.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-dimensions.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-dynamic-dimension-modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-dynamic-dimension-modal.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-dynamic-dimension.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-dynamic-dimension.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-filter-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-filter-dimensions.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-fixed-period-dimension.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-fixed-period-dimension.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-interpretation-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-interpretation-actions.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-interpretation-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-interpretation-detail.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-layout-area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-layout-area.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-multi-type-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-multi-type-chart.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-new.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-open-ao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-open-ao.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-open-as-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-open-as-map.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-open-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-open-dialog.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-open-interpretations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-open-interpretations.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-options-axes-legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-options-axes-legend.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-options-style.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-options-style.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-org-unit-dimension.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-org-unit-dimension.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-organisation-unit-dimension-modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-organisation-unit-dimension-modal.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-overview.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-period-dimension-modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-period-dimension-modal.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-period-dimension.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-period-dimension.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-pt-drill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-pt-drill.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-rename-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-rename-dialog.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-save-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-save-dialog.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-series-tab-multi-axis-multi-type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-series-tab-multi-axis-multi-type.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-series-tab-multi-axis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-series-tab-multi-axis.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-share-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-share-dialog.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-text-styling-tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-text-styling-tool.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-two-category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-two-category.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-view-interpretation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-view-interpretation.png -------------------------------------------------------------------------------- /docs/resources/images/data-visualizer-visualization-type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/docs/resources/images/data-visualizer-visualization-type.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transformIgnorePatterns: [ 3 | 'node_modules/(?!(lodash-es|@dhis2/d2-ui-[a-z-]+)/)', 4 | ], 5 | setupFilesAfterEnv: ['/config/testSetup.js'], 6 | testRunner: 'jest-circus/runner', 7 | reporters: ['default'], 8 | } 9 | -------------------------------------------------------------------------------- /public/dhis2-app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/dhis2-app-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/NotoSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSans-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSans-BoldItalic.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSans-Italic.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSans-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansArabic-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansArabic-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansArabic-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansArabic-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansBengali-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansBengali-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansBengali-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansBengali-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansEthiopic-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansEthiopic-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansEthiopic-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansEthiopic-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansHebrew-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansHebrew-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansHebrew-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansHebrew-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansJP-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansJP-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansJP-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansJP-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansKR-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansKR-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansKR-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansKR-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansKhmer-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansKhmer-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansKhmer-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansKhmer-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansLao-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansLao-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansLao-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansLao-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansMyanmar-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansMyanmar-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansMyanmar-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansMyanmar-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansOriya-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansOriya-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansOriya-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansOriya-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansSC-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansSC-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansSC-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansSC-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansSinhala-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansSinhala-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansSinhala-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansSinhala-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansThai-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansThai-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/NotoSansThai-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/public/fonts/NotoSansThai-Regular.ttf -------------------------------------------------------------------------------- /src/PluginWrapper.js: -------------------------------------------------------------------------------- 1 | import { DashboardPluginWrapper } from '@dhis2/analytics' 2 | import debounce from 'lodash-es/debounce' 3 | import React, { useLayoutEffect, useState } from 'react' 4 | import { VisualizationPluginWrapper } from './components/VisualizationPlugin/VisualizationPluginWrapper.js' 5 | import './locales/index.js' 6 | 7 | const PluginWrapper = (props) => { 8 | const [renderId, setRenderId] = useState(null) 9 | 10 | useLayoutEffect(() => { 11 | const updateRenderId = debounce( 12 | () => 13 | setRenderId((renderId) => 14 | typeof renderId === 'number' ? renderId + 1 : 1 15 | ), 16 | 300 17 | ) 18 | 19 | window.addEventListener('resize', updateRenderId) 20 | 21 | return () => window.removeEventListener('resize', updateRenderId) 22 | }, []) 23 | 24 | return ( 25 | 26 | {(props) => { 27 | return 28 | }} 29 | 30 | ) 31 | } 32 | 33 | export default PluginWrapper 34 | -------------------------------------------------------------------------------- /src/actions/__mocks__/DataEngine.js: -------------------------------------------------------------------------------- 1 | let dataEngineMock 2 | 3 | function mockInit() { 4 | dataEngineMock = { 5 | query: jest.fn(), 6 | mutate: jest.fn(), 7 | } 8 | } 9 | 10 | function values(object) { 11 | return Object.keys(object).map((key) => object[key]) 12 | } 13 | 14 | function mockClear() { 15 | values(dataEngineMock) 16 | .filter((property) => typeof property === 'function') 17 | .forEach((spyFn) => spyFn.mockClear()) 18 | } 19 | 20 | export default function DataEngine() { 21 | return dataEngineMock 22 | } 23 | 24 | DataEngine.mockReset = mockInit 25 | DataEngine.mockClear = mockClear 26 | 27 | mockInit() 28 | -------------------------------------------------------------------------------- /src/actions/current.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_CURRENT, 3 | SET_CURRENT_FROM_UI, 4 | CLEAR_CURRENT, 5 | } from '../reducers/current.js' 6 | import { sGetMetadata } from '../reducers/metadata.js' 7 | import { sGetUi } from '../reducers/ui.js' 8 | 9 | export const acSetCurrent = (value) => ({ 10 | type: SET_CURRENT, 11 | value, 12 | }) 13 | 14 | export const acClearCurrent = () => ({ 15 | type: CLEAR_CURRENT, 16 | }) 17 | 18 | export const acSetCurrentFromUi = (value) => ({ 19 | type: SET_CURRENT_FROM_UI, 20 | value, 21 | }) 22 | 23 | export const tSetCurrentFromUi = () => async (dispatch, getState) => { 24 | dispatch( 25 | acSetCurrentFromUi({ 26 | ui: sGetUi(getState()), 27 | metadata: sGetMetadata(getState()), 28 | }) 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/actions/dimensions.js: -------------------------------------------------------------------------------- 1 | import { apiFetchDimensions } from '@dhis2/analytics' 2 | import keyBy from 'lodash-es/keyBy' 3 | import sortBy from 'lodash-es/sortBy' 4 | import { SET_DIMENSIONS } from '../reducers/dimensions.js' 5 | import { sGetSettingsDisplayNameProperty } from '../reducers/settings.js' 6 | 7 | export const acSetDimensions = (dimensions) => ({ 8 | type: SET_DIMENSIONS, 9 | value: keyBy(sortBy(dimensions, [(d) => d.name.toLowerCase()]), 'id'), 10 | }) 11 | 12 | export const tSetDimensions = () => async (dispatch, getState, engine) => { 13 | const onSuccess = (dimensions) => { 14 | dispatch(acSetDimensions(dimensions)) 15 | } 16 | 17 | const onError = (error) => { 18 | console.log('Error (apiFetchDimensions): ', error) 19 | return error 20 | } 21 | 22 | try { 23 | const displayNameProp = sGetSettingsDisplayNameProperty(getState()) 24 | const dimensions = await apiFetchDimensions(engine, displayNameProp) 25 | return onSuccess(dimensions) 26 | } catch (err) { 27 | return onError(err) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/actions/loader.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_LOAD_ERROR, 3 | CLEAR_LOAD_ERROR, 4 | SET_LOADING, 5 | SET_PLUGIN_LOADING, 6 | } from '../reducers/loader.js' 7 | 8 | export const acSetLoadError = (value) => ({ 9 | type: SET_LOAD_ERROR, 10 | value, 11 | }) 12 | 13 | export const acClearLoadError = () => ({ type: CLEAR_LOAD_ERROR }) 14 | 15 | export const acSetLoading = (value) => ({ 16 | type: SET_LOADING, 17 | value, 18 | }) 19 | 20 | export const acSetPluginLoading = (value) => ({ 21 | type: SET_PLUGIN_LOADING, 22 | value, 23 | }) 24 | -------------------------------------------------------------------------------- /src/actions/metadata.js: -------------------------------------------------------------------------------- 1 | import { ADD_METADATA } from '../reducers/metadata.js' 2 | 3 | export const acAddMetadata = (value) => ({ 4 | type: ADD_METADATA, 5 | value, 6 | }) 7 | -------------------------------------------------------------------------------- /src/actions/recommendedIds.js: -------------------------------------------------------------------------------- 1 | import { SET_RECOMMENDED_IDS } from '../reducers/recommendedIds.js' 2 | 3 | export const acSetRecommendedIds = (value) => ({ 4 | type: SET_RECOMMENDED_IDS, 5 | value, 6 | }) 7 | -------------------------------------------------------------------------------- /src/actions/settings.js: -------------------------------------------------------------------------------- 1 | import { ADD_SETTINGS } from '../reducers/settings.js' 2 | 3 | export const acAddSettings = (value) => ({ 4 | type: ADD_SETTINGS, 5 | value, 6 | }) 7 | -------------------------------------------------------------------------------- /src/actions/snackbar.js: -------------------------------------------------------------------------------- 1 | import { 2 | RECEIVED_SNACKBAR_MESSAGE, 3 | CLEAR_SNACKBAR, 4 | } from '../reducers/snackbar.js' 5 | 6 | export const acReceivedSnackbarMessage = (value) => ({ 7 | type: RECEIVED_SNACKBAR_MESSAGE, 8 | value, 9 | }) 10 | 11 | export const acClearSnackbar = () => ({ 12 | type: CLEAR_SNACKBAR, 13 | }) 14 | -------------------------------------------------------------------------------- /src/actions/visualization.js: -------------------------------------------------------------------------------- 1 | import { getDimensionMetadataFromVisualization } from '../modules/visualization.js' 2 | import { 3 | SET_VISUALIZATION, 4 | CLEAR_VISUALIZATION, 5 | } from '../reducers/visualization.js' 6 | 7 | export const acSetVisualization = (visualization) => { 8 | const metadata = getDimensionMetadataFromVisualization(visualization) 9 | 10 | return { 11 | type: SET_VISUALIZATION, 12 | value: visualization, 13 | metadata, 14 | } 15 | } 16 | 17 | export const acClearVisualization = () => ({ 18 | type: CLEAR_VISUALIZATION, 19 | }) 20 | -------------------------------------------------------------------------------- /src/api/dataStatistics.js: -------------------------------------------------------------------------------- 1 | import { onError } from './index.js' 2 | 3 | export const EVENT_TYPE = 'VISUALIZATION_VIEW' 4 | 5 | const dataStatisticMutation = { 6 | resource: 'dataStatistics', 7 | params: ({ id }) => ({ 8 | favorite: id, 9 | eventType: EVENT_TYPE, 10 | }), 11 | type: 'create', 12 | } 13 | 14 | export const apiPostDataStatistics = (dataEngine, id) => 15 | dataEngine.mutate(dataStatisticMutation, { variables: { id }, onError }) 16 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | // Helper functions 2 | 3 | export const onError = (error) => console.log('Error: ', error) 4 | -------------------------------------------------------------------------------- /src/api/legendSets.js: -------------------------------------------------------------------------------- 1 | const legendSetsQuery = { 2 | resource: 'legendSets', 3 | params: ({ ids }) => ({ 4 | fields: 'id,displayName~rename(name),legends[id,displayName~rename(name),startValue,endValue,color]', 5 | filter: `id:in:[${ids.join(',')}]`, 6 | }), 7 | } 8 | 9 | export const apiFetchLegendSets = async (dataEngine, ids) => { 10 | const legendSetsData = await dataEngine.query( 11 | { legendSets: legendSetsQuery }, 12 | { 13 | variables: { ids }, 14 | } 15 | ) 16 | 17 | return legendSetsData.legendSets.legendSets 18 | } 19 | -------------------------------------------------------------------------------- /src/api/mostViewedVisualizations.js: -------------------------------------------------------------------------------- 1 | // TODO move into component StartScreen? 2 | 3 | import { EVENT_TYPE } from './dataStatistics.js' 4 | 5 | // most likely is not needed anywhere else 6 | export const apiFetchMostViewedVisualizations = ( 7 | dataEngine, 8 | pageSize, 9 | username 10 | ) => { 11 | const visualizationQuery = { 12 | resource: 'dataStatistics/favorites', 13 | params: { 14 | eventType: EVENT_TYPE, 15 | pageSize: pageSize || 10, 16 | ...(username ? { username } : {}), 17 | }, 18 | } 19 | 20 | return dataEngine.query({ visualization: visualizationQuery }) 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/AreaIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const AreaIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 14 | 19 | 20 | 21 | ) 22 | 23 | AreaIcon.propTypes = { 24 | style: PropTypes.object, 25 | } 26 | 27 | export default AreaIcon 28 | -------------------------------------------------------------------------------- /src/assets/ArrowDown.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const ArrowDown = ({ style = { width: 16, height: 16 } }) => ( 5 | 11 | 12 | 16 | 17 | 18 | ) 19 | 20 | ArrowDown.propTypes = { 21 | style: PropTypes.object, 22 | } 23 | 24 | export default ArrowDown 25 | -------------------------------------------------------------------------------- /src/assets/ArrowDownwardIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const ArrowDownwardIcon = ({ style = { width: 18, height: 18 } }) => ( 5 | 6 | 7 | 11 | 12 | ) 13 | 14 | ArrowDownwardIcon.propTypes = { 15 | style: PropTypes.object, 16 | } 17 | 18 | export default ArrowDownwardIcon 19 | -------------------------------------------------------------------------------- /src/assets/ArrowUpwardIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const ArrowUpwardIcon = ({ style = { width: 18, height: 18 } }) => ( 5 | 6 | 7 | 11 | 12 | ) 13 | 14 | ArrowUpwardIcon.propTypes = { 15 | style: PropTypes.object, 16 | } 17 | 18 | export default ArrowUpwardIcon 19 | -------------------------------------------------------------------------------- /src/assets/BarIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const BarIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | BarIcon.propTypes = { 18 | style: PropTypes.object, 19 | } 20 | 21 | export default BarIcon 22 | -------------------------------------------------------------------------------- /src/assets/BoldIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const BoldIcon = () => ( 4 | 10 | 14 | 15 | ) 16 | 17 | export default BoldIcon 18 | -------------------------------------------------------------------------------- /src/assets/BubbleIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const BubbleIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ) 15 | 16 | BubbleIcon.propTypes = { 17 | style: PropTypes.object, 18 | } 19 | 20 | export default BubbleIcon 21 | -------------------------------------------------------------------------------- /src/assets/ColumnIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const ColumnIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | ColumnIcon.propTypes = { 18 | style: PropTypes.object, 19 | } 20 | 21 | export default ColumnIcon 22 | -------------------------------------------------------------------------------- /src/assets/FontColorIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const FontColorIcon = ({ color }) => ( 5 | 11 | 12 | 13 | ) 14 | 15 | FontColorIcon.defaultProps = { 16 | color: '#000000', 17 | } 18 | 19 | FontColorIcon.propTypes = { 20 | color: PropTypes.string, 21 | } 22 | 23 | export default FontColorIcon 24 | -------------------------------------------------------------------------------- /src/assets/GaugeIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const GaugeIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 13 | 18 | 19 | 20 | ) 21 | 22 | GaugeIcon.propTypes = { 23 | style: PropTypes.object, 24 | } 25 | 26 | export default GaugeIcon 27 | -------------------------------------------------------------------------------- /src/assets/HorizontalIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const HorizontalIcon = () => ( 4 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | export default HorizontalIcon 18 | -------------------------------------------------------------------------------- /src/assets/ItalicIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const ItalicIcon = () => ( 4 | 10 | 14 | 15 | ) 16 | 17 | export default ItalicIcon 18 | -------------------------------------------------------------------------------- /src/assets/LineIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const LineIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | ) 18 | 19 | LineIcon.propTypes = { 20 | style: PropTypes.object, 21 | } 22 | 23 | export default LineIcon 24 | -------------------------------------------------------------------------------- /src/assets/OutlierTableIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const OutlierTableIcon = ({ style }) => ( 5 | 6 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | ) 30 | 31 | OutlierTableIcon.propTypes = { 32 | style: PropTypes.object, 33 | } 34 | 35 | export default OutlierTableIcon 36 | -------------------------------------------------------------------------------- /src/assets/PieIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const PieIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 14 | 19 | 20 | 21 | ) 22 | 23 | PieIcon.propTypes = { 24 | style: PropTypes.object, 25 | } 26 | 27 | export default PieIcon 28 | -------------------------------------------------------------------------------- /src/assets/PivotTableIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const PivotTableIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | 24 | PivotTableIcon.propTypes = { 25 | style: PropTypes.object, 26 | } 27 | 28 | export default PivotTableIcon 29 | -------------------------------------------------------------------------------- /src/assets/RadarIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const RadarIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | ) 21 | 22 | RadarIcon.propTypes = { 23 | style: PropTypes.object, 24 | } 25 | 26 | export default RadarIcon 27 | -------------------------------------------------------------------------------- /src/assets/ScatterIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const ScatterIcon = ({ style }) => ( 5 | 12 | 13 | 14 | 15 | 19 | 23 | 24 | 25 | ) 26 | 27 | ScatterIcon.propTypes = { 28 | style: PropTypes.object, 29 | } 30 | 31 | export default ScatterIcon 32 | -------------------------------------------------------------------------------- /src/assets/SingleValueIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const SingleValueIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 16 | 23 | 24 | 123 25 | 26 | 27 | 28 | 29 | ) 30 | 31 | SingleValueIcon.propTypes = { 32 | style: PropTypes.object, 33 | } 34 | 35 | export default SingleValueIcon 36 | -------------------------------------------------------------------------------- /src/assets/StackedAreaIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const StackedAreaIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 14 | 18 | 23 | 28 | 29 | 30 | ) 31 | 32 | StackedAreaIcon.propTypes = { 33 | style: PropTypes.object, 34 | } 35 | 36 | export default StackedAreaIcon 37 | -------------------------------------------------------------------------------- /src/assets/StackedBarIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const StackedBarIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ) 19 | 20 | StackedBarIcon.propTypes = { 21 | style: PropTypes.object, 22 | } 23 | 24 | export default StackedBarIcon 25 | -------------------------------------------------------------------------------- /src/assets/StackedColumnIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const StackedColumnIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ) 19 | 20 | StackedColumnIcon.propTypes = { 21 | style: PropTypes.object, 22 | } 23 | 24 | export default StackedColumnIcon 25 | -------------------------------------------------------------------------------- /src/assets/VerticalIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const VerticalIcon = () => ( 4 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | export default VerticalIcon 18 | -------------------------------------------------------------------------------- /src/assets/YearOverYearColumnIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const YearOverYearColumnIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ) 19 | 20 | YearOverYearColumnIcon.propTypes = { 21 | style: PropTypes.object, 22 | } 23 | 24 | export default YearOverYearColumnIcon 25 | -------------------------------------------------------------------------------- /src/assets/YearOverYearLineIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const YearOverYearLineIcon = ({ style }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 15 | 20 | 21 | 22 | ) 23 | 24 | YearOverYearLineIcon.propTypes = { 25 | style: PropTypes.object, 26 | } 27 | 28 | export default YearOverYearLineIcon 29 | -------------------------------------------------------------------------------- /src/assets/chart-error-graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhis2/data-visualizer-app/58c2d8b61b368d76600fb537179c146fc924c30c/src/assets/chart-error-graphic.png -------------------------------------------------------------------------------- /src/components/App.css: -------------------------------------------------------------------------------- 1 | /* App */ 2 | 3 | .data-visualizer-app { 4 | block-size: 100%; 5 | background-color: var(--colors-grey300); 6 | overflow: hidden; 7 | } 8 | .data-visualizer-app * { 9 | outline: none; 10 | } 11 | 12 | body { 13 | font-family: 'Roboto', sans-serif; 14 | } 15 | 16 | /* Flex */ 17 | 18 | .flex-ct { 19 | display: flex; 20 | } 21 | 22 | .flex-dir-col { 23 | flex-direction: column; 24 | } 25 | 26 | .flex-grow-1 { 27 | flex-grow: 1; 28 | } 29 | 30 | .flex-basis-0 { 31 | flex-basis: 0%; 32 | } 33 | 34 | /* Headerbar */ 35 | 36 | .section-headerbar { 37 | block-size: 48px; 38 | } 39 | 40 | /* Main */ 41 | .section-main { 42 | overflow: hidden; 43 | } 44 | 45 | .main-left { 46 | min-inline-size: 260px; 47 | } 48 | 49 | .main-right { 50 | flex: 1 1 0%; 51 | max-inline-size: 380px; 52 | background-color: var(--colors-grey100); 53 | border-inline-start: 1px solid var(--colors-grey400); 54 | box-shadow: 1px 0 -2px 0 rgba(0, 0, 0, 0.03); 55 | overflow-y: auto; 56 | overflow-x: hidden; 57 | } 58 | 59 | /* Main center */ 60 | 61 | .main-center-canvas { 62 | display: flex; 63 | justify-content: center; 64 | block-size: 100%; 65 | overflow: hidden; 66 | position: relative; 67 | } 68 | 69 | .main-center-layout { 70 | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03); 71 | } 72 | -------------------------------------------------------------------------------- /src/components/AxesTabs/AxesTabs.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React, { useState } from 'react' 3 | import VerticalTab from '../VerticalTabBar/VerticalTab.js' 4 | import VerticalTabBar from '../VerticalTabBar/VerticalTabBar.js' 5 | import tabStyles from '../VisualizationOptions/styles/VisualizationOptions.module.css' 6 | import styles from './styles/AxesTabs.module.css' 7 | 8 | const AxesTabs = ({ items, dataTest }) => { 9 | const [selectedTabIndex, setSelectedTabIndex] = useState(0) 10 | 11 | return ( 12 | <> 13 |
14 | 15 | {items.map(({ label }, index) => ( 16 | setSelectedTabIndex(index)} 19 | selected={index === selectedTabIndex} 20 | > 21 | {label} 22 | 23 | ))} 24 | 25 |
26 |
27 | 28 | {items[selectedTabIndex].label} 29 | 30 | {items[selectedTabIndex].content} 31 |
32 | 33 | ) 34 | } 35 | 36 | AxesTabs.propTypes = { 37 | dataTest: PropTypes.string, 38 | items: PropTypes.array, 39 | } 40 | 41 | export default AxesTabs 42 | -------------------------------------------------------------------------------- /src/components/AxesTabs/styles/AxesTabs.module.css: -------------------------------------------------------------------------------- 1 | .tabs { 2 | inline-size: 200px; 3 | position: absolute; 4 | inset-block: 136px 84px; /* modal title + horizontal tab bar height */ /* modal actions section height */ 5 | z-index: 1; 6 | } 7 | 8 | .content { 9 | margin-inline-start: 200px; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/ChartProvider.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React, { 3 | createContext, 4 | useCallback, 5 | useContext, 6 | useMemo, 7 | useRef, 8 | } from 'react' 9 | 10 | const throwIfNotInitialized = () => { 11 | throw new Error('ChartContext not yet initialized') 12 | } 13 | 14 | export const ChartContext = createContext({ 15 | getChart: throwIfNotInitialized, 16 | setChart: throwIfNotInitialized, 17 | }) 18 | 19 | export const useChartContext = () => useContext(ChartContext) 20 | 21 | export const ChartProvider = ({ children }) => { 22 | const chartRef = useRef(null) 23 | const getChart = useCallback(() => chartRef.current, []) 24 | const setChart = useCallback((chart = null) => { 25 | chartRef.current = chart 26 | }, []) 27 | const api = useMemo(() => ({ getChart, setChart }), [getChart, setChart]) 28 | 29 | return {children} 30 | } 31 | 32 | ChartProvider.propTypes = { 33 | children: PropTypes.node, 34 | } 35 | -------------------------------------------------------------------------------- /src/components/DetailsPanel/styles/DetailsPanel.module.css: -------------------------------------------------------------------------------- 1 | .panel { 2 | inline-size: 380px; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/DimensionsPanel/Dialogs/styles/DialogManager.module.css: -------------------------------------------------------------------------------- 1 | .tabs { 2 | padding-block-end: var(--spacers-dp16); 3 | } 4 | -------------------------------------------------------------------------------- /src/components/DimensionsPanel/styles/DimensionsPanel.style.js: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | divContainer: { 3 | height: '100%', 4 | display: 'flex', 5 | flexDirection: 'column', 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /src/components/DimensionsPanel/styles/DndDimensionItem.module.css: -------------------------------------------------------------------------------- 1 | /* 2 | This keeps the dimension item clone from causing 3 | a ripple effect in the Dimension list during drag 4 | */ 5 | 6 | :global(.item).notDragging { 7 | transform: none !important; 8 | } 9 | 10 | :global(.item).dragging, 11 | :global(.item):global(:not(.selected)).active { 12 | background-color: var(--colors-grey100); 13 | border-color: var(--colors-grey400); 14 | } 15 | 16 | :global(.item).assignedCategories { 17 | cursor: grab; 18 | } 19 | :global(.item.selected).assignedCategories { 20 | cursor: default; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/DimensionsPanel/styles/DndDimensionList.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | flex: 1 1 0%; 4 | min-block-size: 30vh; 5 | } 6 | 7 | .wrapper { 8 | position: absolute; 9 | inline-size: 100%; 10 | block-size: 100%; 11 | overflow: auto; 12 | margin-block-start: 0px; 13 | padding: var(--spacers-dp8); 14 | background: var(--colors-white); 15 | border-block-start: 1px solid var(--colors-grey300); 16 | border-block-end: 1px solid var(--colors-grey300); 17 | } 18 | 19 | .list { 20 | margin: 0; 21 | padding: 0; 22 | display: flex; 23 | flex-direction: column; 24 | gap: 4px; 25 | } 26 | 27 | .header { 28 | text-transform: uppercase; 29 | font-size: 11px; 30 | color: var(--colors-grey600); 31 | margin-block-start: 0; 32 | margin-block-end: var(--spacers-dp8); 33 | margin-inline-start: 0; 34 | margin-inline-end: 0; 35 | letter-spacing: 0.3px; 36 | font-weight: 400; 37 | } 38 | 39 | .section:not(:last-child) { 40 | margin-block-end: var(--spacers-dp24); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/DimensionsPanel/styles/DndDimensionsPanel.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | block-size: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | background-color: var(--colors-grey100); 6 | border-inline-end: 1px solid var(--colors-grey400); 7 | box-shadow: 1px 0 2px 0 rgba(0, 0, 0, 0.03); 8 | padding: 0; 9 | overflow: hidden; 10 | } 11 | 12 | .filter { 13 | padding-block-start: var(--spacers-dp8); 14 | padding-block-end: 0; 15 | padding-inline-start: var(--spacers-dp8); 16 | padding-inline-end: var(--spacers-dp8); 17 | background: var(--colors-white); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/DownloadMenu/ModalDownloadDropdown.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-block-start: var(--spacers-dp12); 3 | margin-block-end: var(--spacers-dp16); 4 | } 5 | -------------------------------------------------------------------------------- /src/components/DownloadMenu/ToolbarDownloadDropdown.js: -------------------------------------------------------------------------------- 1 | import { HoverMenuDropdown } from '@dhis2/analytics' 2 | import i18n from '@dhis2/d2-i18n' 3 | import React from 'react' 4 | import { DownloadMenu } from './DownloadMenu.js' 5 | import { useDownload } from './useDownload.js' 6 | 7 | const ToolbarDownloadDropdown = () => { 8 | const { disabled, doDownloadData, doDownloadImage, visType } = useDownload() 9 | 10 | return ( 11 | 16 | 22 | 23 | ) 24 | } 25 | 26 | export { ToolbarDownloadDropdown } 27 | -------------------------------------------------------------------------------- /src/components/DownloadMenu/constants.js: -------------------------------------------------------------------------------- 1 | export const DOWNLOAD_TYPE_PLAIN = 'plain' 2 | export const DOWNLOAD_TYPE_TABLE = 'table' 3 | export const FILE_FORMAT_CSV = 'csv' 4 | export const FILE_FORMAT_HTML_CSS = 'html+css' 5 | export const FILE_FORMAT_JRXML = 'jrxml' 6 | export const FILE_FORMAT_JSON = 'json' 7 | export const FILE_FORMAT_PDF = 'pdf' 8 | export const FILE_FORMAT_PNG = 'png' 9 | export const FILE_FORMAT_XLS = 'xls' 10 | export const FILE_FORMAT_XML = 'xml' 11 | export const FILE_FORMAT_SQL = 'sql' 12 | export const ID_SCHEME_UID = 'UID' 13 | export const ID_SCHEME_CODE = 'CODE' 14 | export const ID_SCHEME_NAME = 'NAME' 15 | -------------------------------------------------------------------------------- /src/components/DownloadMenu/index.js: -------------------------------------------------------------------------------- 1 | //export { ToolbarDownloadDropdown } from './ToolbarDownloadDropdown.js' 2 | export { ModalDownloadDropdown } from './ModalDownloadDropdown.js' 3 | -------------------------------------------------------------------------------- /src/components/HideButton/HideButton.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import { Button } from '@dhis2/ui' 3 | import PropTypes from 'prop-types' 4 | import React from 'react' 5 | 6 | const HideButton = ({ onClick, ...props }) => ( 7 | 10 | ) 11 | 12 | HideButton.propTypes = { 13 | onClick: PropTypes.func.isRequired, 14 | } 15 | 16 | export default HideButton 17 | -------------------------------------------------------------------------------- /src/components/IconButton/IconButton.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import styles from './styles/IconButton.module.css' 4 | 5 | const IconButton = ({ 6 | children, 7 | dataTest, 8 | disabled, 9 | name, 10 | onBlur, 11 | onClick, 12 | onFocus, 13 | ariaOwns, 14 | ariaHaspopup, 15 | }) => ( 16 | 29 | ) 30 | 31 | IconButton.propTypes = { 32 | ariaHaspopup: PropTypes.bool, 33 | ariaOwns: PropTypes.string, 34 | children: PropTypes.node, 35 | dataTest: PropTypes.string, 36 | disabled: PropTypes.bool, 37 | name: PropTypes.string, 38 | onBlur: PropTypes.func, 39 | onClick: PropTypes.func, 40 | onFocus: PropTypes.func, 41 | } 42 | 43 | export default IconButton 44 | -------------------------------------------------------------------------------- /src/components/IconButton/styles/IconButton.module.css: -------------------------------------------------------------------------------- 1 | .iconButton { 2 | display: inline-flex; 3 | align-items: center; 4 | justify-content: center; 5 | text-transform: none; 6 | box-sizing: border-box; 7 | background: none; 8 | border: none; 9 | cursor: pointer; 10 | padding: 0; 11 | vertical-align: middle; 12 | border-radius: 0; 13 | inline-size: 20px; 14 | block-size: 20px; 15 | margin-block-start: 0px; 16 | margin-block-end: 0px; 17 | margin-inline-start: 2px; 18 | margin-inline-end: 0px; 19 | color: var(--colors-grey700); 20 | } 21 | 22 | .iconButton:focus { 23 | outline: none; 24 | } 25 | 26 | .iconButton:hover { 27 | background-color: rgba(0, 0, 0, 0.09); 28 | color: var(--colors-grey900); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/InterpretationModal/index.js: -------------------------------------------------------------------------------- 1 | export { InterpretationModal } from './InterpretationModal.js' 2 | -------------------------------------------------------------------------------- /src/components/Layout/DefaultLayout/DefaultLayout.js: -------------------------------------------------------------------------------- 1 | import { 2 | AXIS_ID_COLUMNS, 3 | AXIS_ID_ROWS, 4 | AXIS_ID_FILTERS, 5 | } from '@dhis2/analytics' 6 | import React from 'react' 7 | import DefaultAxis from './DefaultAxis.js' 8 | import defaultAxisStyles from './styles/DefaultAxis.style.js' 9 | import styles from './styles/DefaultLayout.style.js' 10 | 11 | const Layout = () => ( 12 |
13 |
17 | 24 | 31 |
32 |
36 | 37 |
38 |
39 | ) 40 | 41 | Layout.displayName = 'Layout' 42 | 43 | export default Layout 44 | -------------------------------------------------------------------------------- /src/components/Layout/DefaultLayout/styles/DefaultAxis.module.css: -------------------------------------------------------------------------------- 1 | .content { 2 | inline-size: 100%; 3 | display: flex; 4 | align-items: flex-start; 5 | align-content: flex-start; 6 | flex-wrap: wrap; 7 | min-block-size: 26px; 8 | } 9 | 10 | .content > div { 11 | cursor: pointer; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Layout/DefaultLayout/styles/DefaultAxis.style.js: -------------------------------------------------------------------------------- 1 | import { colors } from '@dhis2/ui' 2 | import * as layoutStyle from '../../styles/style.js' 3 | 4 | export default { 5 | axisContainer: { 6 | display: 'flex', 7 | backgroundColor: layoutStyle.AXIS_BACKGROUND_COLOR, 8 | borderColor: layoutStyle.AXIS_BORDER_COLOR, 9 | borderStyle: layoutStyle.AXIS_BORDER_STYLE, 10 | borderWidth: layoutStyle.AXIS_BORDER_WIDTH, 11 | padding: layoutStyle.AXIS_PADDING, 12 | }, 13 | axisContainerLeft: { 14 | borderInlineStartWidth: 0, 15 | }, 16 | label: { 17 | minWidth: 55, 18 | maxWidth: 55, 19 | padding: '2px 0px 0px 0px', 20 | fontSize: 11, 21 | color: colors.grey700, 22 | userSelect: 'none', 23 | letterSpacing: '0.2px', 24 | }, 25 | } 26 | // TODO: Refactor this file and all other affected files (DefaultLayout + everything in ../../styles/) to css modules 27 | -------------------------------------------------------------------------------- /src/components/Layout/DefaultLayout/styles/DefaultLayout.style.js: -------------------------------------------------------------------------------- 1 | import { LAYOUT_HEIGHT } from '../../styles/style.js' 2 | 3 | // Axis 4 | export const FILTER_AXIS_WIDTH = '50%' 5 | export const DIMENSION_AXIS_CONTENT_HEIGHT = '36px' 6 | 7 | // Axis (generated) 8 | export const DIMENSION_AXIS_WIDTH = `${100 - parseInt(FILTER_AXIS_WIDTH, 10)}%` 9 | 10 | export default { 11 | ct: { 12 | display: 'flex', 13 | minHeight: LAYOUT_HEIGHT, 14 | }, 15 | axisGroup: { 16 | display: 'flex', 17 | flexDirection: 'column', 18 | }, 19 | axisGroupLeft: { 20 | flexBasis: DIMENSION_AXIS_WIDTH, 21 | }, 22 | axisGroupRight: { 23 | flexBasis: FILTER_AXIS_WIDTH, 24 | }, 25 | columns: { 26 | flexBasis: '50%', 27 | }, 28 | rows: { 29 | flexBasis: '50%', 30 | }, 31 | filters: { 32 | flexBasis: '100%', 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Layout/OutlierTable/OutlierTableLayout.js: -------------------------------------------------------------------------------- 1 | import { AXIS_ID_COLUMNS } from '@dhis2/analytics' 2 | import React from 'react' 3 | import DefaultAxis from '../DefaultLayout/DefaultAxis.js' 4 | import defaultAxisStyles from '../DefaultLayout/styles/DefaultAxis.style.js' 5 | import defaultLayoutStyles from '../DefaultLayout/styles/DefaultLayout.style.js' 6 | import outlierTableLayoutStyles from './styles/OutlierTableLayout.style.js' 7 | 8 | const Layout = () => ( 9 |
10 |
17 | 24 |
25 |
26 | ) 27 | 28 | Layout.displayName = 'Layout' 29 | 30 | export default Layout 31 | -------------------------------------------------------------------------------- /src/components/Layout/OutlierTable/styles/OutlierTableLayout.style.js: -------------------------------------------------------------------------------- 1 | export default { 2 | axisGroupLeft: { 3 | flexBasis: '100%', 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Layout/PieLayout/PieLayout.js: -------------------------------------------------------------------------------- 1 | import { AXIS_ID_COLUMNS, AXIS_ID_FILTERS } from '@dhis2/analytics' 2 | import React from 'react' 3 | import DefaultAxis from '../DefaultLayout/DefaultAxis.js' 4 | import defaultAxisStyles from '../DefaultLayout/styles/DefaultAxis.style.js' 5 | import defaultLayoutStyles from '../DefaultLayout/styles/DefaultLayout.style.js' 6 | import pieLayoutStyles from './styles/PieLayout.style.js' 7 | 8 | const Layout = () => ( 9 |
10 |
17 | 24 |
25 |
32 | 36 |
37 |
38 | ) 39 | 40 | export default Layout 41 | -------------------------------------------------------------------------------- /src/components/Layout/PieLayout/styles/PieLayout.style.js: -------------------------------------------------------------------------------- 1 | // Axis 2 | export const FILTER_AXIS_WIDTH = '70%' 3 | 4 | // Axis (generated) 5 | export const DIMENSION_AXIS_WIDTH = `${100 - parseInt(FILTER_AXIS_WIDTH, 10)}%` 6 | 7 | export default { 8 | axisGroupLeft: { 9 | flexBasis: DIMENSION_AXIS_WIDTH, 10 | }, 11 | axisGroupRight: { 12 | flexBasis: FILTER_AXIS_WIDTH, 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Layout/PivotTableLayout/styles/PivotTableLayout.style.js: -------------------------------------------------------------------------------- 1 | // Axis 2 | export const FILTER_AXIS_WIDTH = '50%' 3 | 4 | // Axis (generated) 5 | export const DIMENSION_AXIS_WIDTH = `${100 - parseInt(FILTER_AXIS_WIDTH, 10)}%` 6 | 7 | export default { 8 | axisGroupLeft: { 9 | flexBasis: DIMENSION_AXIS_WIDTH, 10 | }, 11 | axisGroupRight: { 12 | flexBasis: FILTER_AXIS_WIDTH, 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Layout/ScatterLayout/styles/ScatterAxis.style.js: -------------------------------------------------------------------------------- 1 | import { colors } from '@dhis2/ui' 2 | import * as layoutStyle from '../../styles/style.js' 3 | 4 | export default { 5 | axisContainer: { 6 | display: 'flex', 7 | alignItems: 'flex-start', 8 | backgroundColor: layoutStyle.AXIS_BACKGROUND_COLOR, 9 | borderColor: layoutStyle.AXIS_BORDER_COLOR, 10 | borderStyle: layoutStyle.AXIS_BORDER_STYLE, 11 | borderWidth: layoutStyle.AXIS_BORDER_WIDTH, 12 | padding: layoutStyle.AXIS_PADDING, 13 | }, 14 | axisContainerLeft: { 15 | borderInlineStartWidth: 0, 16 | }, 17 | label: { 18 | minWidth: 55, 19 | maxWidth: 55, 20 | padding: '2px 0px 0px 0px', 21 | fontSize: 11, 22 | color: colors.grey700, 23 | userSelect: 'none', 24 | letterSpacing: '0.2px', 25 | display: 'flex', 26 | flexDirection: 'column', 27 | }, 28 | } 29 | // TODO: Refactor this file and all other affected files (DefaultLayout + everything in ../../styles/) to css modules 30 | -------------------------------------------------------------------------------- /src/components/Layout/ScatterLayout/styles/ScatterLayout.style.js: -------------------------------------------------------------------------------- 1 | // Axis 2 | export const FILTER_AXIS_WIDTH = '70%' 3 | 4 | // Axis (generated) 5 | export const DIMENSION_AXIS_WIDTH = `${100 - parseInt(FILTER_AXIS_WIDTH, 10)}%` 6 | 7 | export default { 8 | axisGroupLeft: { 9 | flexBasis: DIMENSION_AXIS_WIDTH, 10 | }, 11 | axisGroupRight: { 12 | flexBasis: FILTER_AXIS_WIDTH, 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Layout/YearOverYearLayout/styles/YearOverYearAxis.style.js: -------------------------------------------------------------------------------- 1 | export default { 2 | content: { 3 | flex: '1 1 0%', 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Layout/YearOverYearLayout/styles/YearOverYearLayout.style.js: -------------------------------------------------------------------------------- 1 | import { FILTER_AXIS_WIDTH } from '../../DefaultLayout/styles/DefaultLayout.style.js' 2 | 3 | // Axis (generated) 4 | export const DIMENSION_AXIS_WIDTH = `${100 - parseInt(FILTER_AXIS_WIDTH, 10)}%` 5 | 6 | export default { 7 | axisGroupLeft: { 8 | flexBasis: DIMENSION_AXIS_WIDTH, 9 | }, 10 | axisGroupRight: { 11 | flexBasis: FILTER_AXIS_WIDTH, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Layout/styles/Tooltip.style.js: -------------------------------------------------------------------------------- 1 | import { colors } from '@dhis2/ui' 2 | 3 | export const styles = { 4 | tooltip: { 5 | fontFamily: 'roboto', 6 | padding: '7px 9px', 7 | color: colors.white, 8 | fontSize: '12px', 9 | backgroundColor: colors.grey900, 10 | boxShadow: 'none', 11 | borderRadius: '3px', 12 | position: 'relative', 13 | top: '5px', 14 | maxWidth: '300px', 15 | }, 16 | list: { 17 | listStyleType: 'none', 18 | margin: '0px', 19 | marginBottom: '-3px', 20 | padding: '0px', 21 | }, 22 | item: { 23 | marginBottom: '3px', 24 | whiteSpace: 'nowrap', 25 | overflow: 'hidden', 26 | textOverflow: 'ellipsis', 27 | }, 28 | iconWrapper: { 29 | display: 'flex', 30 | alignItems: 'center', 31 | marginBottom: '5px', 32 | }, 33 | label: { 34 | whiteSpace: 'normal', 35 | marginInlineStart: '6px', 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Layout/styles/style.js: -------------------------------------------------------------------------------- 1 | import { colors } from '@dhis2/ui' 2 | 3 | // Layout 4 | export const LAYOUT_HEIGHT = '70px' 5 | 6 | // Axis 7 | export const AXIS_PADDING = '4px 4px 4px 6px' 8 | export const AXIS_LABEL_PADDING = '2px 0px 0px 4px' 9 | export const AXIS_BORDER_COLOR = colors.grey300 10 | export const AXIS_BORDER_STYLE = 'solid' 11 | export const AXIS_BORDER_WIDTH = '0px 0px 1px 1px' 12 | export const AXIS_BACKGROUND_COLOR = colors.white 13 | -------------------------------------------------------------------------------- /src/components/MenuBar/InterpretationsButton.js: -------------------------------------------------------------------------------- 1 | import { InterpretationsAndDetailsToggler } from '@dhis2/analytics' 2 | import React from 'react' 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import { acToggleUiRightSidebarOpen } from '../../actions/ui.js' 5 | import { sGetCurrent } from '../../reducers/current.js' 6 | import { sGetUiRightSidebarOpen } from '../../reducers/ui.js' 7 | 8 | export const InterpretationsButton = () => { 9 | const isShowing = useSelector(sGetUiRightSidebarOpen) 10 | const current = useSelector(sGetCurrent) 11 | const isDisabled = !current?.id 12 | const dispatch = useDispatch() 13 | const onClick = () => { 14 | dispatch(acToggleUiRightSidebarOpen()) 15 | } 16 | 17 | return ( 18 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Snackbar/styles/Snackbar.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: fixed; 3 | inset-block-end: 0; 4 | inset-inline-start: 50%; 5 | transform: translateX(-50%); 6 | } 7 | -------------------------------------------------------------------------------- /src/components/TitleBar/styles/TitleBar.style.js: -------------------------------------------------------------------------------- 1 | import { colors } from '@dhis2/ui' 2 | 3 | export default { 4 | titleBar: { 5 | display: 'flex', 6 | justifyContent: 'center', 7 | }, 8 | cell: { 9 | display: 'flex', 10 | alignItems: 'center', 11 | background: colors.white, 12 | padding: '6px', 13 | borderRadius: '5px', 14 | margin: '4px', 15 | }, 16 | title: { 17 | fontSize: '14px', 18 | }, 19 | suffix: { 20 | paddingInlineStart: '4px', 21 | }, 22 | titleUnsaved: { 23 | color: colors.grey500, 24 | fontStyle: 'italic', 25 | }, 26 | titleDirty: { 27 | color: colors.grey700, 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /src/components/UpdateButton/UpdateButton.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import { Button } from '@dhis2/ui' 3 | import PropTypes from 'prop-types' 4 | import React from 'react' 5 | 6 | const UpdateButton = ({ onClick, ...props }) => { 7 | return ( 8 | 11 | ) 12 | } 13 | 14 | UpdateButton.propTypes = { 15 | onClick: PropTypes.func.isRequired, 16 | } 17 | 18 | export default UpdateButton 19 | -------------------------------------------------------------------------------- /src/components/UpdateButton/styles/UpdateButton.style.js: -------------------------------------------------------------------------------- 1 | import { colors } from '@dhis2/ui' 2 | 3 | export default { 4 | flat: { 5 | backgroundColor: colors.primary600, 6 | color: colors.white, 7 | fontSize: '13px', 8 | padding: '6px 15px', 9 | boxShadow: 'none', 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /src/components/VerticalTabBar/VerticalTab.js: -------------------------------------------------------------------------------- 1 | import { Label } from '@dhis2/ui' 2 | import cx from 'classnames' 3 | import PropTypes from 'prop-types' 4 | import React from 'react' 5 | import styles from './styles/VerticalTab.module.css' 6 | 7 | const VerticalTab = ({ selected, onClick, children }) => ( 8 |
14 | 15 |
16 | ) 17 | 18 | VerticalTab.propTypes = { 19 | children: PropTypes.node, 20 | selected: PropTypes.bool, 21 | onClick: PropTypes.func, 22 | } 23 | 24 | export default VerticalTab 25 | -------------------------------------------------------------------------------- /src/components/VerticalTabBar/VerticalTabBar.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import styles from './styles/VerticalTabBar.module.css' 4 | 5 | const VerticalTabBar = ({ children }) => ( 6 |
{children}
7 | ) 8 | 9 | VerticalTabBar.propTypes = { 10 | children: PropTypes.node, 11 | } 12 | 13 | export default VerticalTabBar 14 | -------------------------------------------------------------------------------- /src/components/VerticalTabBar/styles/VerticalTab.module.css: -------------------------------------------------------------------------------- 1 | .tab { 2 | padding: var(--spacers-dp12); 3 | color: var(--colors-grey600); 4 | } 5 | 6 | .tab:hover { 7 | background: var(--colors-grey100); 8 | } 9 | 10 | .selected, 11 | .selected:hover { 12 | background: var(--colors-blue050); 13 | border-inline-end: 4px solid var(--colors-blue700); 14 | color: var(--colors-blue700); 15 | margin-inline-end: -1px; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/VerticalTabBar/styles/VerticalTabBar.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | block-size: 100%; 3 | border-inline-end: 1px solid var(--colors-grey400); 4 | margin-inline-end: var(--spacers-dp24); 5 | background: var(--colors-white); 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Visualization/styles/Visualization.style.js: -------------------------------------------------------------------------------- 1 | export default { 2 | chartCanvas: { 3 | display: 'flex', 4 | justifyContent: 'flex-start', 5 | alignItems: 'flex-start', 6 | height: '100%', 7 | }, 8 | loadingCover: { 9 | position: 'absolute', 10 | height: '100%', 11 | width: '100%', 12 | insetInlineStart: 0, 13 | insetBlockStart: 0, 14 | zIndex: 100, 15 | background: 'var(--colors-grey300)', 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Visualization/utils.js: -------------------------------------------------------------------------------- 1 | export const matchVisualizationWithType = ( 2 | visualizations, 3 | visualizationsWithType 4 | ) => { 5 | const result = [] 6 | visualizations.forEach((visualization) => { 7 | const type = visualizationsWithType.find( 8 | (visWithType) => visWithType.id === visualization.id 9 | )?.type 10 | if (type) { 11 | result.push({ 12 | ...visualization, 13 | type, 14 | }) 15 | } 16 | }) 17 | return result 18 | } 19 | -------------------------------------------------------------------------------- /src/components/VisualizationErrorInfo/VisualizationErrorInfo.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { GenericError } from '../../assets/ErrorIcons.js' 4 | import { VisualizationError, genericErrorTitle } from '../../modules/error.js' 5 | import styles from './styles/VisualizationErrorInfo.module.css' 6 | 7 | export const VisualizationErrorInfo = ({ error }) => ( 8 |
12 | {error instanceof VisualizationError ? ( 13 | <> 14 |
{error.icon()}
15 |

{error.title}

16 |

{error.description}

17 | 18 | ) : ( 19 | <> 20 |
{GenericError()}
21 |

{genericErrorTitle}

22 |

23 | {error.message || error} 24 |

25 | 26 | )} 27 |
28 | ) 29 | 30 | VisualizationErrorInfo.propTypes = { 31 | error: PropTypes.oneOfType([ 32 | PropTypes.instanceOf(Error), 33 | PropTypes.instanceOf(VisualizationError), 34 | ]), 35 | } 36 | -------------------------------------------------------------------------------- /src/components/VisualizationErrorInfo/styles/VisualizationErrorInfo.module.css: -------------------------------------------------------------------------------- 1 | .errorContainer { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | text-align: center; 6 | justify-content: center; 7 | inline-size: 100%; 8 | } 9 | .errorIcon { 10 | inline-size: 136px; 11 | block-size: 136px; 12 | margin-block-start: 0; 13 | margin-block-end: var(--spacers-dp24); 14 | margin-inline-start: auto; 15 | margin-inline-end: auto; 16 | } 17 | .errorTitle { 18 | font-weight: 500; 19 | font-size: 20px; 20 | color: var(--colors-grey800); 21 | letter-spacing: 0.15px; 22 | line-height: 24px; 23 | margin-block-start: 0; 24 | margin-block-end: var(--spacers-dp12); 25 | margin-inline-start: auto; 26 | margin-inline-end: auto; 27 | } 28 | .errorDescription { 29 | font-weight: 400; 30 | font-size: 14px; 31 | color: var(--colors-grey700); 32 | line-height: 19px; 33 | margin-block-start: 0; 34 | margin-block-end: 0; 35 | margin-inline-start: auto; 36 | margin-inline-end: auto; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/InfoText.js: -------------------------------------------------------------------------------- 1 | import { IconInfo16 } from '@dhis2/ui' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import styles from './styles/VisualizationOptions.module.css' 5 | 6 | export const InfoText = ({ text }) => ( 7 |
8 |

9 | 10 | 11 | 12 | {text} 13 |

14 |
15 | ) 16 | 17 | InfoText.propTypes = { 18 | text: PropTypes.string, 19 | } 20 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/NoticeBox.js: -------------------------------------------------------------------------------- 1 | import { NoticeBox as UiNoticeBox } from '@dhis2/ui' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import styles from './styles/VisualizationOptions.module.css' 5 | 6 | const NoticeBox = ({ title, text, warning = false, error = false }) => ( 7 |
8 | 9 | {text} 10 | 11 |
12 | ) 13 | 14 | NoticeBox.propTypes = { 15 | error: PropTypes.bool, 16 | text: PropTypes.string, 17 | title: PropTypes.string, 18 | warning: PropTypes.bool, 19 | } 20 | 21 | export default NoticeBox 22 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/AggregationType.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_AGGREGATION_TYPE } from '../../../modules/options.js' 4 | import { SelectBaseOption } from './SelectBaseOption.js' 5 | 6 | const AggregationType = () => ( 7 | 33 | ) 34 | 35 | export default AggregationType 36 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/AxisDecimals.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { OPTION_AXIS_DECIMALS } from '../../../modules/options.js' 5 | import { PositiveNumberBaseType } from './PositiveNumberBaseType.js' 6 | 7 | const AxisDecimals = ({ disabled, axisId }) => ( 8 | 19 | ) 20 | 21 | AxisDecimals.propTypes = { 22 | axisId: PropTypes.string, 23 | disabled: PropTypes.bool, 24 | } 25 | 26 | export default AxisDecimals 27 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/AxisLabels.js: -------------------------------------------------------------------------------- 1 | import { FONT_STYLE_AXIS_LABELS } from '@dhis2/analytics' 2 | import i18n from '@dhis2/d2-i18n' 3 | import { Label } from '@dhis2/ui' 4 | import PropTypes from 'prop-types' 5 | import React from 'react' 6 | import styles from '../styles/VisualizationOptions.module.css' 7 | import TextStyle from './TextStyle.js' 8 | 9 | const AxisLabels = ({ disabled, axisId }) => ( 10 |
11 | 12 | 18 |
19 | ) 20 | 21 | AxisLabels.propTypes = { 22 | axisId: PropTypes.string, 23 | disabled: PropTypes.bool, 24 | } 25 | 26 | export default AxisLabels 27 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/AxisMaxValue.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { OPTION_AXIS_MAX_VALUE } from '../../../modules/options.js' 5 | import NumberBaseType from './NumberBaseType.js' 6 | 7 | const AxisMaxValue = ({ disabled, axisId }) => ( 8 | 19 | ) 20 | 21 | AxisMaxValue.propTypes = { 22 | axisId: PropTypes.string, 23 | disabled: PropTypes.bool, 24 | } 25 | 26 | export default AxisMaxValue 27 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/AxisMinValue.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { OPTION_AXIS_MIN_VALUE } from '../../../modules/options.js' 5 | import NumberBaseType from './NumberBaseType.js' 6 | 7 | const AxisMinValue = ({ disabled, axisId }) => ( 8 | 19 | ) 20 | 21 | AxisMinValue.propTypes = { 22 | axisId: PropTypes.string, 23 | disabled: PropTypes.bool, 24 | } 25 | 26 | export default AxisMinValue 27 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/AxisRange.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import { Label, Help } from '@dhis2/ui' 3 | import PropTypes from 'prop-types' 4 | import React from 'react' 5 | import styles from '../styles/AxisRange.module.css' 6 | import optionsStyles from '../styles/VisualizationOptions.module.css' 7 | import AxisMaxValue from './AxisMaxValue.js' 8 | import AxisMinValue from './AxisMinValue.js' 9 | 10 | const AxisRange = ({ disabled, axisId }) => ( 11 |
12 | 13 |
14 | 15 |
16 | {'\u00A0\u2013\u00A0'} 17 |
18 | 19 |
20 | {!disabled ? ( 21 | 22 | {i18n.t('Values outside of the range will not be displayed')} 23 | 24 | ) : null} 25 |
26 | ) 27 | 28 | AxisRange.propTypes = { 29 | axisId: PropTypes.string, 30 | disabled: PropTypes.bool, 31 | } 32 | 33 | export default AxisRange 34 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/AxisSteps.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { OPTION_AXIS_STEPS } from '../../../modules/options.js' 5 | import { PositiveNumberBaseType } from './PositiveNumberBaseType.js' 6 | 7 | export const AxisSteps = ({ disabled, axisId }) => ( 8 | 22 | ) 23 | 24 | AxisSteps.propTypes = { 25 | axisId: PropTypes.string, 26 | disabled: PropTypes.bool, 27 | } 28 | 29 | export default AxisSteps 30 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/BaseLine.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { 5 | OPTION_BASE_LINE_ENABLED, 6 | OPTION_BASE_LINE_TITLE, 7 | OPTION_BASE_LINE_TITLE_FONT_STYLE, 8 | OPTION_BASE_LINE_VALUE, 9 | } from '../../../modules/options.js' 10 | import { default as RegressionLine } from './RegressionLine.js' 11 | 12 | export const BaseLine = ({ disabled, axisId, isVertical }) => ( 13 | 24 | ) 25 | 26 | BaseLine.propTypes = { 27 | axisId: PropTypes.string, 28 | disabled: PropTypes.bool, 29 | isVertical: PropTypes.bool, 30 | } 31 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/ColSubTotals.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_COL_SUB_TOTALS } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const ColSubTotals = () => ( 7 | 14 | ) 15 | 16 | export default ColSubTotals 17 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/ColTotals.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_COL_TOTALS } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const ColTotals = () => ( 7 | 14 | ) 15 | 16 | export default ColTotals 17 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/CompletedOnly.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import { Label } from '@dhis2/ui' 3 | import React from 'react' 4 | import { OPTION_COMPLETED_ONLY } from '../../../modules/options.js' 5 | import styles from '../styles/VisualizationOptions.module.css' 6 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 7 | 8 | const CompletedOnly = () => ( 9 |
10 | 11 | 17 |
18 | ) 19 | 20 | export default CompletedOnly 21 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/Cumulative.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_CUMULATIVE } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const Cumulative = () => ( 7 | 13 | ) 14 | 15 | export default Cumulative 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/CumulativeValues.js: -------------------------------------------------------------------------------- 1 | import { VIS_TYPE_PIVOT_TABLE } from '@dhis2/analytics' 2 | import i18n from '@dhis2/d2-i18n' 3 | import React from 'react' 4 | import { useSelector } from 'react-redux' 5 | import { OPTION_CUMULATIVE_VALUES } from '../../../modules/options.js' 6 | import { sGetUiType } from '../../../reducers/ui.js' 7 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 8 | 9 | const CumulativeValues = () => { 10 | const visType = useSelector(sGetUiType) 11 | 12 | return ( 13 | 25 | ) 26 | } 27 | 28 | export default CumulativeValues 29 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/DataIcon.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_ICONS } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const DataIcon = () => ( 7 | 17 | ) 18 | 19 | export default DataIcon 20 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/DigitGroupSeparator.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_DIGIT_GROUP_SEPARATOR } from '../../../modules/options.js' 4 | import { SelectBaseOption } from './SelectBaseOption.js' 5 | 6 | const DigitGroupSeparator = () => ( 7 | 18 | ) 19 | 20 | export default DigitGroupSeparator 21 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/DisplayDensity.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_DISPLAY_DENSITY } from '../../../modules/options.js' 4 | import { SelectBaseOption } from './SelectBaseOption.js' 5 | 6 | const DisplayDensity = () => ( 7 | 18 | ) 19 | 20 | export default DisplayDensity 21 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/FixColumnHeaders.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { OPTION_FIX_COLUMN_HEADERS } from '../../../modules/options.js' 5 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 6 | 7 | const FixColumnHeaders = ({ columnsHasItems }) => ( 8 | 21 | ) 22 | 23 | FixColumnHeaders.propTypes = { 24 | columnsHasItems: PropTypes.bool, 25 | } 26 | 27 | export default FixColumnHeaders 28 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/FixRowHeaders.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { OPTION_FIX_ROW_HEADERS } from '../../../modules/options.js' 5 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 6 | 7 | const FixRowHeaders = ({ rowsHasItems }) => ( 8 | 19 | ) 20 | 21 | FixRowHeaders.propTypes = { 22 | rowsHasItems: PropTypes.bool, 23 | } 24 | 25 | export default FixRowHeaders 26 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/FontSize.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_FONT_SIZE } from '../../../modules/options.js' 4 | import { SelectBaseOption } from './SelectBaseOption.js' 5 | 6 | const FontSize = () => ( 7 | 18 | ) 19 | 20 | export default FontSize 21 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/HideEmptyColumns.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_HIDE_EMPTY_COLUMNS } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const HideEmptyColumns = () => ( 7 | 13 | ) 14 | 15 | export default HideEmptyColumns 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/HideEmptyRowItems.js: -------------------------------------------------------------------------------- 1 | import { 2 | HIDE_EMPTY_ROW_ITEMS_BEFORE_FIRST, 3 | HIDE_EMPTY_ROW_ITEMS_AFTER_LAST, 4 | HIDE_EMPTY_ROW_ITEMS_BEFORE_FIRST_AFTER_LAST, 5 | HIDE_EMPTY_ROW_ITEMS_ALL, 6 | } from '@dhis2/analytics' 7 | import i18n from '@dhis2/d2-i18n' 8 | import React from 'react' 9 | import { 10 | options, 11 | OPTION_HIDE_EMPTY_ROW_ITEMS, 12 | } from '../../../modules/options.js' 13 | import { SelectBaseOption } from './SelectBaseOption.js' 14 | 15 | const optionName = OPTION_HIDE_EMPTY_ROW_ITEMS 16 | const defaultValue = options[optionName].defaultValue 17 | 18 | const HideEmptyRowItems = () => ( 19 | 42 | ) 43 | 44 | export default HideEmptyRowItems 45 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/HideEmptyRows.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_HIDE_EMPTY_ROWS } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const HideEmptyRows = () => ( 7 | 13 | ) 14 | 15 | export default HideEmptyRows 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/LegendDisplayStyle.js: -------------------------------------------------------------------------------- 1 | import { 2 | LEGEND_DISPLAY_STYLE_FILL, 3 | LEGEND_DISPLAY_STYLE_TEXT, 4 | } from '@dhis2/analytics' 5 | import i18n from '@dhis2/d2-i18n' 6 | import PropTypes from 'prop-types' 7 | import React from 'react' 8 | import { OPTION_LEGEND_DISPLAY_STYLE } from '../../../modules/options.js' 9 | import { default as RadioBaseOption } from './RadioBaseOption.js' 10 | 11 | const LegendDisplayStyle = ({ disabled }) => ( 12 | 29 | ) 30 | 31 | LegendDisplayStyle.propTypes = { 32 | disabled: PropTypes.bool, 33 | } 34 | 35 | export default LegendDisplayStyle 36 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/NoSpaceBetweenColumns.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_NO_SPACE_BETWEEN_COLUMNS } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const NoSpaceBetweenColumns = () => ( 7 | 13 | ) 14 | 15 | export default NoSpaceBetweenColumns 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/NumberType.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_NUMBER_TYPE } from '../../../modules/options.js' 4 | import { SelectBaseOption } from './SelectBaseOption.js' 5 | 6 | const NumberType = () => ( 7 | 26 | ) 27 | 28 | export default NumberType 29 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/ParamOrganisationUnit.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_ORGANISATION_UNIT } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const ParamOrganisationUnit = () => ( 7 | 13 | ) 14 | 15 | export default ParamOrganisationUnit 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/ParamParentOrganisationUnit.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_PARENT_ORGANISATION_UNIT } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const ParamParentOrganisationUnit = () => ( 7 | 13 | ) 14 | 15 | export default ParamParentOrganisationUnit 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/ParamReportingPeriod.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_REPORTING_PERIOD } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const ParamReportingPeriod = () => ( 7 | 13 | ) 14 | 15 | export default ParamReportingPeriod 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/PercentStackedValues.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_PERCENT_STACKED_VALUES } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const PercentStackedValues = () => ( 7 | 13 | ) 14 | 15 | export default PercentStackedValues 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/Regression.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_REGRESSION } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const Regression = () => ( 7 | 13 | ) 14 | 15 | export default Regression 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/RegressionLineTitle.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { TextBaseOption } from './TextBaseOption.js' 5 | 6 | const RegressionLineTitle = ({ 7 | dataTest, 8 | axisId, 9 | fontStyleKey, 10 | id, 11 | isVertical, 12 | }) => ( 13 | 27 | ) 28 | 29 | RegressionLineTitle.propTypes = { 30 | axisId: PropTypes.string, 31 | dataTest: PropTypes.string, 32 | fontStyleKey: PropTypes.string, 33 | id: PropTypes.string, 34 | isVertical: PropTypes.bool, 35 | } 36 | 37 | export default RegressionLineTitle 38 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/RegressionLineValue.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import NumberBaseType from './NumberBaseType.js' 5 | 6 | const RegressionLineValue = ({ dataTest, axisId, id }) => ( 7 | 18 | ) 19 | 20 | RegressionLineValue.propTypes = { 21 | axisId: PropTypes.string, 22 | dataTest: PropTypes.string, 23 | id: PropTypes.string, 24 | } 25 | 26 | export default RegressionLineValue 27 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/RegressionType.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { options, OPTION_REGRESSION_TYPE } from '../../../modules/options.js' 5 | import { SelectBaseOption } from './SelectBaseOption.js' 6 | 7 | const optionName = OPTION_REGRESSION_TYPE 8 | const defaultValue = options[optionName].defaultValue 9 | 10 | const RegressionType = ({ disabled }) => ( 11 | 26 | ) 27 | 28 | RegressionType.propTypes = { 29 | disabled: PropTypes.bool, 30 | } 31 | 32 | export default RegressionType 33 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/RowSubTotals.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_ROW_SUB_TOTALS } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const RowSubTotals = () => ( 7 | 14 | ) 15 | 16 | export default RowSubTotals 17 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/RowTotals.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_ROW_TOTALS } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const RowTotals = () => ( 7 | 14 | ) 15 | 16 | export default RowTotals 17 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/ShowData.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_SHOW_DATA } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const ShowData = () => ( 7 | 13 | ) 14 | 15 | export default ShowData 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/ShowDimensionLabels.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_SHOW_DIMENSION_LABELS } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const ShowDimensionLabels = () => ( 7 | 13 | ) 14 | 15 | export default ShowDimensionLabels 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/ShowHierarchy.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_SHOW_HIERARCHY } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const ShowHierarchy = () => ( 7 | 13 | ) 14 | 15 | export default ShowHierarchy 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/ShowLegendKey.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { OPTION_SHOW_LEGEND_KEY } from '../../../modules/options.js' 5 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 6 | 7 | const ShowLegendKey = ({ disabled }) => ( 8 | 16 | ) 17 | 18 | ShowLegendKey.propTypes = { 19 | disabled: PropTypes.bool, 20 | } 21 | 22 | export default ShowLegendKey 23 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/ShowSeriesKey.js: -------------------------------------------------------------------------------- 1 | import { FONT_STYLE_LEGEND } from '@dhis2/analytics' 2 | import i18n from '@dhis2/d2-i18n' 3 | import React from 'react' 4 | import { OPTION_SHOW_SERIES_KEY } from '../../../modules/options.js' 5 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 6 | 7 | const ShowSeriesKey = () => ( 8 | 17 | ) 18 | 19 | export default ShowSeriesKey 20 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/SkipRounding.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_SKIP_ROUNDING } from '../../../modules/options.js' 4 | import { CheckboxBaseOption } from './CheckboxBaseOption.js' 5 | 6 | const SkipRounding = () => ( 7 | 13 | ) 14 | 15 | export default SkipRounding 16 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/SortOrder.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { options, OPTION_SORT_ORDER } from '../../../modules/options.js' 4 | import { SelectBaseOption } from './SelectBaseOption.js' 5 | 6 | const optionName = OPTION_SORT_ORDER 7 | const defaultValue = options[optionName].defaultValue 8 | 9 | const SortOrder = () => ( 10 | 22 | ) 23 | export default SortOrder 24 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/Subtitle.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { OPTION_SUBTITLE } from '../../../modules/options.js' 5 | import { TextBaseOption } from './TextBaseOption.js' 6 | 7 | const Subtitle = ({ dataTest, label }) => ( 8 | 18 | ) 19 | 20 | Subtitle.defaultProps = { 21 | dataTest: 'visualization-option-subtitle', 22 | } 23 | 24 | Subtitle.propTypes = { 25 | dataTest: PropTypes.string, 26 | label: PropTypes.string, 27 | } 28 | 29 | export default Subtitle 30 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/TargetLine.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { 5 | OPTION_TARGET_LINE_ENABLED, 6 | OPTION_TARGET_LINE_TITLE, 7 | OPTION_TARGET_LINE_TITLE_FONT_STYLE, 8 | OPTION_TARGET_LINE_VALUE, 9 | } from '../../../modules/options.js' 10 | import { default as RegressionLine } from './RegressionLine.js' 11 | 12 | export const TargetLine = ({ disabled, axisId, isVertical }) => ( 13 | 24 | ) 25 | 26 | TargetLine.propTypes = { 27 | axisId: PropTypes.string, 28 | disabled: PropTypes.bool, 29 | isVertical: PropTypes.bool, 30 | } 31 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/Title.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | import { OPTION_TITLE } from '../../../modules/options.js' 5 | import { TextBaseOption } from './TextBaseOption.js' 6 | 7 | const Title = ({ label, inline }) => ( 8 | 18 | ) 19 | 20 | Title.defaultProps = { 21 | inline: false, 22 | } 23 | 24 | Title.propTypes = { 25 | inline: PropTypes.bool, 26 | label: PropTypes.string, 27 | } 28 | 29 | export default Title 30 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/Options/TopLimit.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { OPTION_TOP_LIMIT } from '../../../modules/options.js' 4 | import { SelectBaseOption } from './SelectBaseOption.js' 5 | 6 | const TopLimit = () => ( 7 | 21 | ) 22 | 23 | export default TopLimit 24 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/styles/AxisRange.module.css: -------------------------------------------------------------------------------- 1 | .label { 2 | margin-block-end: var(--spacers-dp4); 3 | } 4 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/styles/Outliers.module.css: -------------------------------------------------------------------------------- 1 | .divider { 2 | margin-block-start: var(--spacers-dp16); 3 | margin-block-end: var(--spacers-dp16); 4 | margin-inline-start: 22px; 5 | margin-inline-end: 22px; 6 | border-block-end: 1px solid var(--colors-grey300); 7 | } 8 | -------------------------------------------------------------------------------- /src/components/VisualizationOptions/styles/TextStyle.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | flex-wrap: wrap; 3 | } 4 | 5 | .container, 6 | .buttonStrip { 7 | display: flex; 8 | position: relative; 9 | margin-block-start: var(--spacers-dp4); 10 | margin-block-end: var(--spacers-dp4); 11 | margin-inline-start: 0; 12 | margin-inline-end: 0; 13 | } 14 | 15 | .container, 16 | .buttonStrip > *:not(:last-child) { 17 | margin-inline-end: var(--spacers-dp4); 18 | } 19 | 20 | .fontSizeSelect, 21 | .textAlignSelect { 22 | min-inline-size: 180px; 23 | margin-inline-end: var(--spacers-dp8); 24 | } 25 | 26 | .textColorLabel { 27 | block-size: 28px; 28 | font-size: 14px; 29 | line-height: 16px; 30 | border: 1px solid var(--colors-grey400); 31 | border-radius: 4px; 32 | cursor: pointer; 33 | padding-block-start: 0; 34 | padding-block-end: 0; 35 | padding-inline-start: 1px; 36 | padding-inline-end: 1px; 37 | } 38 | 39 | .textColorInput { 40 | opacity: 0; 41 | pointer-events: none; 42 | position: absolute; 43 | } 44 | 45 | .textColorIcon { 46 | margin-block-start: 1px; 47 | } 48 | 49 | .disabled { 50 | background-color: var(--colors-grey100); 51 | box-shadow: none; 52 | color: var(--colors-grey600); 53 | fill: var(--colors-grey600); 54 | border-color: var(--colors-grey400); 55 | cursor: not-allowed; 56 | } 57 | -------------------------------------------------------------------------------- /src/components/VisualizationPlugin/PivotPlugin.js: -------------------------------------------------------------------------------- 1 | import { PivotTable } from '@dhis2/analytics' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | 5 | const PivotPlugin = ({ 6 | responses, 7 | legendSets, 8 | visualization, 9 | style, 10 | id: renderCounter, 11 | onToggleContextualMenu, 12 | }) => { 13 | return ( 14 |
15 | 22 |
23 | ) 24 | } 25 | 26 | PivotPlugin.defaultProps = { 27 | style: {}, 28 | } 29 | 30 | PivotPlugin.propTypes = { 31 | legendSets: PropTypes.arrayOf(PropTypes.object).isRequired, 32 | responses: PropTypes.arrayOf(PropTypes.object).isRequired, 33 | visualization: PropTypes.object.isRequired, 34 | id: PropTypes.number, 35 | style: PropTypes.object, 36 | onToggleContextualMenu: PropTypes.func, 37 | } 38 | 39 | export default PivotPlugin 40 | -------------------------------------------------------------------------------- /src/components/VisualizationPlugin/styles/ChartPlugin.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | inline-size: 100%; 3 | } 4 | 5 | /* This forces all SVG descendants to have the same 6 | * font-family as the SVG root element, which is required 7 | * for offline export to PNG to work correctly. */ 8 | :global(svg.highcharts-root *) { 9 | font-family: inherit; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/VisualizationPlugin/styles/PivotPlugin.style.js: -------------------------------------------------------------------------------- 1 | import css from 'styled-jsx/css' 2 | 3 | export const pivotTableStyles = css.global` 4 | .pivot td { 5 | border: 1px solid #b2b2b2; 6 | padding: 5px; 7 | } 8 | 9 | .pivot-empty { 10 | background-color: #cddaed; 11 | } 12 | 13 | .pivot-dim { 14 | background-color: #dae6f8; 15 | text-align: center; 16 | } 17 | 18 | .pivot-dim-total { 19 | background-color: #bac6d8; 20 | text-align: center; 21 | } 22 | 23 | .pivot-value { 24 | background-color: #fff; 25 | text-align: end; 26 | } 27 | 28 | .pivot-value-total-subgrandtotal { 29 | background-color: #d8d8d8; 30 | white-space: nowrap; 31 | text-align: end; 32 | } 33 | 34 | .pointer { 35 | cursor: pointer; 36 | } 37 | ` 38 | -------------------------------------------------------------------------------- /src/components/VisualizationPlugin/styles/VisualizationPlugin.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | inline-size: 100%; 4 | block-size: 100%; 5 | display: flex; 6 | } 7 | .modal { 8 | max-block-size: calc(-285px + 100vh); 9 | } 10 | 11 | .chartWrapper { 12 | display: flex; 13 | flex-grow: 1; 14 | } 15 | 16 | .legendKey { 17 | margin-block-start: 0; 18 | margin-block-end: 0; 19 | margin-inline-start: 8px; 20 | margin-inline-end: 8px; 21 | inline-size: 180px; 22 | } 23 | .legendKeyToggle { 24 | margin-block-start: 1px; 25 | margin-block-end: 0; 26 | margin-inline-start: 4px; 27 | margin-inline-end: 4px; 28 | } 29 | .wrapper { 30 | position: absolute; 31 | inset-block-start: 0; 32 | inset-block-end: 0; 33 | overflow: auto; 34 | border: 1px solid var(--colors-grey400); 35 | } 36 | .buttonMargin { 37 | margin-block-start: 34px; 38 | } 39 | 40 | @media print { 41 | .legendKeyToggle { 42 | display: none; 43 | } 44 | .buttonMargin { 45 | margin-block-start: 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/VisualizationTypeSelector/__tests__/VisualizationTypeListItem.spec.js: -------------------------------------------------------------------------------- 1 | import { VIS_TYPE_COLUMN } from '@dhis2/analytics' 2 | import { shallow } from 'enzyme' 3 | import React from 'react' 4 | import ListItemIcon from '../ListItemIcon.js' 5 | import VisualizationTypeListItem from '../VisualizationTypeListItem.js' 6 | 7 | describe('VisualizationTypeListItem component ', () => { 8 | let props 9 | let shallowElement 10 | 11 | const element = () => { 12 | if (!shallowElement) { 13 | shallowElement = shallow() 14 | } 15 | return shallowElement 16 | } 17 | 18 | beforeEach(() => { 19 | props = { 20 | type: VIS_TYPE_COLUMN, 21 | visualizationType: VIS_TYPE_COLUMN, 22 | } 23 | shallowElement = undefined 24 | }) 25 | 26 | it('renders a div', () => { 27 | expect(element().find('div').first().length).toEqual(1) 28 | }) 29 | 30 | it('renders ListItemIcon', () => { 31 | expect(element().find(ListItemIcon).first().length).toEqual(1) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /src/components/scrollbar.css: -------------------------------------------------------------------------------- 1 | /* Width */ 2 | ::-webkit-scrollbar { 3 | inline-size: 7px; 4 | block-size: 7px; 5 | } 6 | 7 | /* Handle */ 8 | ::-webkit-scrollbar-thumb { 9 | background: #d1d1d1; 10 | border-radius: 3px; 11 | } 12 | -------------------------------------------------------------------------------- /src/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux' 2 | import { createLogger } from 'redux-logger' 3 | import reducer from './reducers/index.js' 4 | 5 | const configureStore = (middleware) => { 6 | // Enable Redux devtools if extension is installed instead of redux-logger 7 | // const composeEnhancers = 8 | // window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 9 | const composeEnhancers = 10 | typeof window === 'object' && 11 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 12 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 13 | name: 'data-visualizer-app', 14 | }) 15 | : compose 16 | 17 | if ( 18 | !window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ && 19 | process.env.NODE_ENV !== 'production' 20 | ) { 21 | middleware.push(createLogger()) 22 | } 23 | 24 | return createStore( 25 | reducer, 26 | composeEnhancers(applyMiddleware(...middleware)) 27 | ) 28 | } 29 | 30 | export default configureStore 31 | -------------------------------------------------------------------------------- /src/middleware/metadata.js: -------------------------------------------------------------------------------- 1 | import { acAddMetadata } from '../actions/metadata.js' 2 | 3 | export default ({ dispatch }) => 4 | (next) => 5 | (action) => { 6 | if (typeof action.metadata === 'object') { 7 | if (Array.isArray(action.metadata)) { 8 | dispatch( 9 | acAddMetadata( 10 | action.metadata.reduce( 11 | (meta, obj) => ({ 12 | ...meta, 13 | ...obj, 14 | }), 15 | {} 16 | ) 17 | ) 18 | ) 19 | } else { 20 | dispatch(acAddMetadata(action.metadata)) 21 | } 22 | } 23 | 24 | next(action) 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/__tests__/error.spec.js: -------------------------------------------------------------------------------- 1 | import { getErrorVariantByStatusCode } from '../error.js' 2 | 3 | describe('getErrorVariantByStatusCode', () => { 4 | let error 5 | 6 | beforeEach(() => { 7 | error = { 8 | message: 'test', 9 | httpStatusCode: 409, 10 | httpStatus: 'Conflict', 11 | status: 'ERROR', 12 | } 13 | }) 14 | 15 | it('should return type warning by default', () => { 16 | const expectedResult = 'warning' 17 | 18 | expect(getErrorVariantByStatusCode(error.httpStatusCode)).toEqual( 19 | expectedResult 20 | ) 21 | }) 22 | 23 | it('should return type warning when HTTP status code is not 500', () => { 24 | const expectedResult = 'warning' 25 | 26 | expect(getErrorVariantByStatusCode(error.httpStatusCode)).toEqual( 27 | expectedResult 28 | ) 29 | }) 30 | 31 | it('should return type error when HTTP status code is 500', () => { 32 | error.httpStatusCode = 500 33 | 34 | const expectedResult = 'error' 35 | 36 | expect(getErrorVariantByStatusCode(error.httpStatusCode)).toEqual( 37 | expectedResult 38 | ) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /src/modules/__tests__/layout.spec.js: -------------------------------------------------------------------------------- 1 | import { getRetransfer } from '../layout.js' 2 | 3 | const layout = { 4 | columns: ['dx'], 5 | rows: ['pe'], 6 | filters: ['ou', 'abc'], 7 | } 8 | 9 | describe('layout', () => { 10 | describe('getRetransfer', () => { 11 | it('transfers "pe" to filters axis when new dimension added', () => { 12 | const transfer = { 13 | xyz: { axisId: 'rows' }, 14 | } 15 | 16 | expect(getRetransfer(layout, transfer, 'RADAR')).toMatchObject({ 17 | pe: { 18 | axisId: 'filters', 19 | }, 20 | }) 21 | }) 22 | 23 | it('transfers "pe" to filters axis when filter dimension moved to rows', () => { 24 | const transfer = { 25 | abc: { axisId: 'rows' }, 26 | } 27 | 28 | expect(getRetransfer(layout, transfer, 'RADAR')).toMatchObject({ 29 | pe: { 30 | axisId: 'filters', 31 | }, 32 | }) 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/modules/__tests__/yearOverYear.spec.js: -------------------------------------------------------------------------------- 1 | import { seriesOptions } from '../yearOverYear.js' 2 | 3 | describe('yearOverYear', () => { 4 | it('year has matching id and name', () => { 5 | const lastOption = seriesOptions[seriesOptions.length - 1] 6 | expect(lastOption.id).toEqual(lastOption.getName()) 7 | }) 8 | 9 | it('has sequential years', () => { 10 | const lastOption = seriesOptions[seriesOptions.length - 1] 11 | const secondLastOption = seriesOptions[seriesOptions.length - 2] 12 | 13 | expect(parseInt(lastOption.getName(), 10)).toEqual( 14 | parseInt(secondLastOption.getName(), 10) - 1 15 | ) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /src/modules/dataSets.js: -------------------------------------------------------------------------------- 1 | export const REPORTING_RATE = 'REPORTING_RATE' 2 | export const REPORTING_RATE_ON_TIME = 'REPORTING_RATE_ON_TIME' 3 | export const ACTUAL_REPORTS = 'ACTUAL_REPORTS' 4 | export const ACTUAL_REPORTS_ON_TIME = 'ACTUAL_REPORTS_ON_TIME' 5 | export const EXPECTED_REPORTS = 'EXPECTED_REPORTS' 6 | -------------------------------------------------------------------------------- /src/modules/disabledOptions.js: -------------------------------------------------------------------------------- 1 | import { VIS_TYPE_PIVOT_TABLE } from '@dhis2/analytics' 2 | import i18n from '@dhis2/d2-i18n' 3 | import { 4 | OPTION_COL_SUB_TOTALS, 5 | OPTION_COL_TOTALS, 6 | OPTION_CUMULATIVE_VALUES, 7 | OPTION_LEGEND, 8 | OPTION_NUMBER_TYPE, 9 | OPTION_ROW_SUB_TOTALS, 10 | OPTION_ROW_TOTALS, 11 | } from './options.js' 12 | 13 | export const getDisabledOptions = ({ visType, options }) => { 14 | const disabledOptions = {} 15 | 16 | for (const [option, value] of Object.entries(options)) { 17 | switch (option) { 18 | case OPTION_CUMULATIVE_VALUES: { 19 | const helpText = i18n.t( 20 | 'Not supported when using cumulative values' 21 | ) 22 | 23 | // when checked, disabled totals and numberType options 24 | if (visType === VIS_TYPE_PIVOT_TABLE && value) { 25 | disabledOptions[OPTION_COL_TOTALS] = {} 26 | disabledOptions[OPTION_COL_SUB_TOTALS] = {} 27 | disabledOptions[OPTION_ROW_TOTALS] = {} 28 | disabledOptions[OPTION_ROW_SUB_TOTALS] = {} 29 | disabledOptions[OPTION_NUMBER_TYPE] = { 30 | helpText, 31 | } 32 | disabledOptions[OPTION_LEGEND] = { 33 | helpText, 34 | } 35 | } 36 | 37 | break 38 | } 39 | } 40 | } 41 | 42 | return disabledOptions 43 | } 44 | -------------------------------------------------------------------------------- /src/modules/dnd.js: -------------------------------------------------------------------------------- 1 | const DATA_TYPE_TEXT = 'text' 2 | 3 | export const setDataTransfer = (e, source) => { 4 | e.dataTransfer.setData( 5 | DATA_TYPE_TEXT, 6 | JSON.stringify({ 7 | dimensionId: e.target.dataset.dimensionid, 8 | source, 9 | }) 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/fields/__tests__/baseFields.spec.js: -------------------------------------------------------------------------------- 1 | import { getAllFieldObjectsByType, BASE_FIELD_NAME } from '../baseFields.js' 2 | 3 | describe('getAllFieldObjectsByType', () => { 4 | it('returns all field objects for a given type', () => { 5 | const fields = getAllFieldObjectsByType('reportTable', true) 6 | expect(Array.isArray(fields)).toBe(true) 7 | expect(fields.some((f) => f[BASE_FIELD_NAME] === 'cumulative')).toBe( 8 | true 9 | ) 10 | }) 11 | 12 | it('returns an empty array for unknown type', () => { 13 | const fields = getAllFieldObjectsByType('unknownType', true) 14 | expect(fields).toEqual([]) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /src/modules/fields/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | getAllFieldObjectsByType, 3 | extractName, 4 | markExcluded, 5 | moveExcludedToEnd, 6 | } from './baseFields.js' 7 | import { extendFields } from './nestedFields.js' 8 | 9 | export const getFieldsStringByType = (type) => 10 | getAllFieldObjectsByType(type) 11 | .map(markExcluded) 12 | .map(extractName) 13 | .sort() 14 | .reduce(moveExcludedToEnd, null) 15 | .map(extendFields) 16 | .join(',') 17 | -------------------------------------------------------------------------------- /src/modules/fields/nestedFields.js: -------------------------------------------------------------------------------- 1 | // constants 2 | 3 | import { dimensionMetadataProps } from '../visualization.js' 4 | 5 | const ID = 'id' 6 | const NAME = 'name,displayName,displayShortName' 7 | 8 | const DIMENSION_ITEM = `dimensionItem~rename(${ID})` 9 | const LEGEND_SET = `${ID},${NAME}` 10 | 11 | const ITEMS = `${DIMENSION_ITEM},${NAME},dimensionItemType,expression,access` 12 | 13 | const AXIS = `dimension,filter,legendSet[${LEGEND_SET}],items[${ITEMS}]` 14 | const INTERPRETATIONS = 'id,created' 15 | const LEGEND = `showKey,style,strategy,set[${LEGEND_SET}]` 16 | 17 | const OPTION_SET = `optionSet[${ID}]` 18 | 19 | const DIMENSION_PROPS = dimensionMetadataProps 20 | .map((prop) => { 21 | if (prop === 'programAttribute') { 22 | return `${prop}[${ITEMS},attribute[${OPTION_SET}]]` 23 | } else if (prop === 'programDataElement') { 24 | return `${prop}[${ITEMS},dataElement[${OPTION_SET}]]` 25 | } else { 26 | return `${prop}[${ITEMS}]` 27 | } 28 | }) 29 | .join(',') 30 | 31 | // nested fields map 32 | const nestedFields = { 33 | columns: AXIS, 34 | rows: AXIS, 35 | filters: AXIS, 36 | interpretations: INTERPRETATIONS, 37 | legend: LEGEND, 38 | dataDimensionItems: DIMENSION_PROPS, 39 | } 40 | 41 | export const extendFields = (field) => 42 | `${field}${nestedFields[field] ? `[${nestedFields[field]}]` : ''}` 43 | -------------------------------------------------------------------------------- /src/modules/getRequestOptions.js: -------------------------------------------------------------------------------- 1 | import { getOptionsForRequest } from './options.js' 2 | 3 | export const getRequestOptions = (visualization, filters, displayProperty) => { 4 | const options = getOptionsForRequest().reduce((map, [option, props]) => { 5 | // only add parameter if value !== default 6 | if ( 7 | visualization[option] !== undefined && 8 | visualization[option] !== props.defaultValue 9 | ) { 10 | map[option] = visualization[option] 11 | } 12 | 13 | return map 14 | }, {}) 15 | 16 | // interpretation filter 17 | if (filters.relativePeriodDate) { 18 | options.relativePeriodDate = filters.relativePeriodDate 19 | } 20 | 21 | if (displayProperty) { 22 | options.displayProperty = displayProperty.toUpperCase() 23 | } 24 | 25 | // global filters 26 | // userOrgUnit 27 | if (filters.userOrgUnit && filters.userOrgUnit.length) { 28 | const ouIds = filters.userOrgUnit.map( 29 | (ouPath) => ouPath.split('/').slice(-1)[0] 30 | ) 31 | 32 | options.userOrgUnit = ouIds.join(';') 33 | } 34 | 35 | return options 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/history.js: -------------------------------------------------------------------------------- 1 | import { createHashHistory } from 'history' 2 | 3 | export default createHashHistory() 4 | -------------------------------------------------------------------------------- /src/modules/options/sections/advanced.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AggregationType from '../../../components/VisualizationOptions/Options/AggregationType.js' 3 | import CompletedOnly from '../../../components/VisualizationOptions/Options/CompletedOnly.js' 4 | import getAdvancedTemplate from './templates/advanced.js' 5 | 6 | export default () => ({ 7 | ...getAdvancedTemplate({ 8 | content: React.Children.toArray([ 9 | , 10 | , 11 | ]), 12 | }), 13 | }) 14 | -------------------------------------------------------------------------------- /src/modules/options/sections/chartStyle.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-key */ 2 | import React from 'react' 3 | import NoSpaceBetweenColumns from '../../../components/VisualizationOptions/Options/NoSpaceBetweenColumns.js' 4 | import ShowData from '../../../components/VisualizationOptions/Options/ShowData.js' 5 | import ShowSeriesKey from '../../../components/VisualizationOptions/Options/ShowSeriesKey.js' 6 | import getChartStyleTemplate from './templates/chartStyle.js' 7 | 8 | const defaultContent = [, ] 9 | 10 | const columnContent = [ 11 | , 12 | , 13 | , 14 | /* TODO new option */ 15 | ] 16 | 17 | export default (isColumnBased) => ({ 18 | ...getChartStyleTemplate({ 19 | content: React.Children.toArray([ 20 | isColumnBased ? columnContent : defaultContent, 21 | ]), 22 | }), 23 | }) 24 | -------------------------------------------------------------------------------- /src/modules/options/sections/colorSet.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import ColorSet from '../../../components/VisualizationOptions/Options/ColorSet.js' 4 | 5 | export default (hasDisabledSections) => ({ 6 | key: 'style-color-set', 7 | label: i18n.t('Color set'), 8 | helpText: hasDisabledSections 9 | ? i18n.t('Color sets are not supported yet when using multiple axes') 10 | : null, 11 | content: React.Children.toArray([ 12 | , 13 | ]), 14 | }) 15 | -------------------------------------------------------------------------------- /src/modules/options/sections/display.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-key */ 2 | import React from 'react' 3 | import CumulativeValues from '../../../components/VisualizationOptions/Options/CumulativeValues.js' 4 | import HideEmptyRowItems from '../../../components/VisualizationOptions/Options/HideEmptyRowItems.js' 5 | import PercentStackedValues from '../../../components/VisualizationOptions/Options/PercentStackedValues.js' 6 | import SkipRounding from '../../../components/VisualizationOptions/Options/SkipRounding.js' 7 | import SortOrder from '../../../components/VisualizationOptions/Options/SortOrder.js' 8 | import getDisplayTemplate from './templates/display.js' 9 | 10 | const defaultContent = [ 11 | , 12 | , 13 | , 14 | , 15 | ] 16 | 17 | const stackedContent = [, ...defaultContent] 18 | 19 | export default (isStacked) => ({ 20 | ...getDisplayTemplate({ 21 | content: React.Children.toArray( 22 | isStacked ? stackedContent : defaultContent 23 | ), 24 | }), 25 | }) 26 | -------------------------------------------------------------------------------- /src/modules/options/sections/domainAxis.js: -------------------------------------------------------------------------------- 1 | import { 2 | FONT_STYLE_HORIZONTAL_AXIS_TITLE, 3 | FONT_STYLE_VERTICAL_AXIS_TITLE, 4 | } from '@dhis2/analytics' 5 | import React from 'react' 6 | import AxisLabels from '../../../components/VisualizationOptions/Options/AxisLabels.js' 7 | import AxisTitle from '../../../components/VisualizationOptions/Options/AxisTitle.js' 8 | import getHorizontalAxisTemplate from './templates/horizontalAxis.js' 9 | import getVerticalAxisTemplate from './templates/verticalAxis.js' 10 | 11 | export default ({ axisId, isVertical }) => { 12 | const template = isVertical 13 | ? getVerticalAxisTemplate 14 | : getHorizontalAxisTemplate 15 | 16 | const fontStyleKey = isVertical 17 | ? FONT_STYLE_VERTICAL_AXIS_TITLE 18 | : FONT_STYLE_HORIZONTAL_AXIS_TITLE 19 | 20 | return { 21 | ...template({ 22 | content: React.Children.toArray([ 23 | , 24 | , 25 | ]), 26 | }), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/options/sections/lines.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import { InfoText } from '../../../components/VisualizationOptions/InfoText.js' 4 | import { BaseLine } from '../../../components/VisualizationOptions/Options/BaseLine.js' 5 | import RegressionType from '../../../components/VisualizationOptions/Options/RegressionType.js' 6 | import { TargetLine } from '../../../components/VisualizationOptions/Options/TargetLine.js' 7 | import getLinesTemplate from './templates/lines.js' 8 | 9 | export default (hasDisabledSections, axisId) => ({ 10 | ...getLinesTemplate({ 11 | content: React.Children.toArray([ 12 | [], 13 | ...(!hasDisabledSections 14 | ? [, ] 15 | : [ 16 | , 21 | ]), 22 | ]), 23 | }), 24 | }) 25 | -------------------------------------------------------------------------------- /src/modules/options/sections/templates/advanced.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default ({ content }) => ({ 4 | key: 'data-advanced', 5 | label: i18n.t('Advanced'), 6 | content, 7 | }) 8 | -------------------------------------------------------------------------------- /src/modules/options/sections/templates/chartStyle.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default ({ content }) => ({ 4 | key: 'style-chart-style', 5 | label: i18n.t('Chart style'), 6 | content, 7 | }) 8 | -------------------------------------------------------------------------------- /src/modules/options/sections/templates/display.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default ({ content }) => ({ 4 | key: 'data-display', 5 | label: i18n.t('Display'), 6 | content, 7 | }) 8 | -------------------------------------------------------------------------------- /src/modules/options/sections/templates/emptyData.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default ({ content }) => ({ 4 | key: 'data-empty-data', 5 | label: i18n.t('Empty data'), 6 | content, 7 | }) 8 | -------------------------------------------------------------------------------- /src/modules/options/sections/templates/horizontalAxis.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default ({ content, helpText, axisId }) => ({ 4 | key: axisId ? `axes-horizontal-axis-${axisId}` : 'axes-horizontal-axis', 5 | label: axisId 6 | ? i18n.t('Horizontal (x) axis {{axisId}}', { 7 | axisId: Number(axisId.slice(-1)) + 1, 8 | }) 9 | : i18n.t('Horizontal (x) axis'), 10 | content, 11 | helpText, 12 | }) 13 | -------------------------------------------------------------------------------- /src/modules/options/sections/templates/lines.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default ({ content, helpText }) => ({ 4 | key: 'data-lines', 5 | label: i18n.t('Lines'), 6 | content, 7 | helpText, 8 | }) 9 | -------------------------------------------------------------------------------- /src/modules/options/sections/templates/totals.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default ({ hasCumulativeValuesInPt, content }) => ({ 4 | key: 'data-totals', 5 | label: i18n.t('Totals'), 6 | helpText: hasCumulativeValuesInPt 7 | ? i18n.t('Totals are not supported when using cumulative values') 8 | : null, 9 | content, 10 | }) 11 | -------------------------------------------------------------------------------- /src/modules/options/sections/templates/verticalAxis.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default ({ content, helpText, axisId }) => ({ 4 | key: axisId ? `axes-vertical-axis-${axisId}` : 'axes-vertical-axis', 5 | label: axisId 6 | ? i18n.t('Vertical (y) axis {{axisId}}', { 7 | axisId: Number(axisId.slice(-1)) + 1, 8 | }) 9 | : i18n.t('Vertical (y) axis'), 10 | content, 11 | helpText, 12 | }) 13 | -------------------------------------------------------------------------------- /src/modules/options/sections/titles.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import HideSubtitle from '../../../components/VisualizationOptions/Options/HideSubtitle.js' 4 | import HideTitle from '../../../components/VisualizationOptions/Options/HideTitle.js' 5 | 6 | export default () => ({ 7 | key: 'style-titles', 8 | label: i18n.t('Titles'), 9 | content: React.Children.toArray([, ]), 10 | }) 11 | -------------------------------------------------------------------------------- /src/modules/options/tabs/axes.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import AxesTabs from '../../../components/AxesTabs/AxesTabs.js' 4 | 5 | export default (content) => ({ 6 | key: 'axes-tab', 7 | label: i18n.t('Axes'), 8 | content: [ 9 | { 10 | key: 'axis-tabs', 11 | content: React.Children.toArray([ 12 | , 13 | ]), 14 | }, 15 | ], 16 | }) 17 | -------------------------------------------------------------------------------- /src/modules/options/tabs/data.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default (content) => ({ 4 | key: 'data-tab', 5 | label: i18n.t('Data'), 6 | content, 7 | }) 8 | -------------------------------------------------------------------------------- /src/modules/options/tabs/legend.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import Legend from '../../../components/VisualizationOptions/Options/Legend.js' 4 | 5 | export default ({ hideStyleOptions } = {}) => ({ 6 | key: 'legend-tab', 7 | label: i18n.t('Legend'), 8 | content: [ 9 | { 10 | key: 'legend-section-1', 11 | content: React.Children.toArray([ 12 | , 13 | ]), 14 | }, 15 | ], 16 | }) 17 | -------------------------------------------------------------------------------- /src/modules/options/tabs/limitValues.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import MeasureCriteria from '../../../components/VisualizationOptions/Options/MeasureCriteria.js' 4 | 5 | export default () => ({ 6 | key: 'limitValues-tab', 7 | label: i18n.t('Limit values'), 8 | content: [ 9 | /* 10 | { 11 | key: 'limitValues-limit-numbers', 12 | label: i18n.t('Limit number of values'), 13 | content: [], 14 | },*/ 15 | { 16 | key: 'limitValues-limit-min-max', 17 | label: i18n.t('Limit minimum/maximum values'), 18 | content: React.Children.toArray([]), 19 | }, 20 | ], 21 | }) 22 | -------------------------------------------------------------------------------- /src/modules/options/tabs/outliers.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default (content) => ({ 4 | key: 'outliers-tab', 5 | label: i18n.t('Outliers'), 6 | content, 7 | }) 8 | -------------------------------------------------------------------------------- /src/modules/options/tabs/series.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | import React from 'react' 3 | import SeriesTable from '../../../components/VisualizationOptions/Options/SeriesTable.js' 4 | 5 | export default ({ showAxisOptions, showTypeOptions } = {}) => ({ 6 | key: 'series-tab', 7 | label: i18n.t('Series'), 8 | content: [ 9 | { 10 | key: 'series-table', 11 | content: React.Children.toArray([ 12 | , 16 | ]), 17 | }, 18 | ], 19 | }) 20 | -------------------------------------------------------------------------------- /src/modules/options/tabs/style.js: -------------------------------------------------------------------------------- 1 | import i18n from '@dhis2/d2-i18n' 2 | 3 | export default (content) => ({ 4 | key: 'style-tab', 5 | label: i18n.t('Style'), 6 | content, 7 | }) 8 | -------------------------------------------------------------------------------- /src/modules/orgUnit.js: -------------------------------------------------------------------------------- 1 | export const removeLastPathSegment = (path) => { 2 | // if root path, then return unprocessed path 3 | if (path.match(/\//g).length === 1) { 4 | return path 5 | } 6 | 7 | return path.substr(0, path.lastIndexOf('/')) 8 | } 9 | 10 | /** 11 | * Get org unit path by ou id 12 | * @param id 13 | * @param metadata 14 | * @param parentGraphMap 15 | * @returns {*} 16 | */ 17 | export const getOuPath = (id, metadata, parentGraphMap) => { 18 | if (metadata[id] && metadata[id].path) { 19 | return metadata[id].path 20 | } 21 | 22 | // for root org units 23 | if (parentGraphMap[id] === id || parentGraphMap[id] === '') { 24 | return `/${id}` 25 | } 26 | 27 | return parentGraphMap[id] ? `/${parentGraphMap[id]}/${id}` : undefined 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/systemSettings.js: -------------------------------------------------------------------------------- 1 | const SYSTEM_SETTING_DATE_FORMAT = 'keyDateFormat' 2 | const SYSTEM_SETTINGS_RELATIVE_PERIOD = 'keyAnalysisRelativePeriod' 3 | export const SYSTEM_SETTINGS_DIGIT_GROUP_SEPARATOR = 4 | 'keyAnalysisDigitGroupSeparator' 5 | export const SYSTEM_SETTINGS_HIDE_DAILY_PERIODS = 'keyHideDailyPeriods' 6 | export const SYSTEM_SETTINGS_HIDE_WEEKLY_PERIODS = 'keyHideWeeklyPeriods' 7 | export const SYSTEM_SETTINGS_HIDE_BIWEEKLY_PERIODS = 'keyHideBiWeeklyPeriods' 8 | export const SYSTEM_SETTINGS_HIDE_MONTHLY_PERIODS = 'keyHideMonthlyPeriods' 9 | export const SYSTEM_SETTINGS_HIDE_BIMONTHLY_PERIODS = 'keyHideBiMonthlyPeriods' 10 | export const SYSTEM_SETTINGS_IGNORE_ANALYTICS_APPROVAL_YEAR_THRESHOLD = 11 | 'keyIgnoreAnalyticsApprovalYearThreshold' 12 | 13 | export const systemSettingsKeys = [ 14 | SYSTEM_SETTING_DATE_FORMAT, 15 | SYSTEM_SETTINGS_RELATIVE_PERIOD, 16 | SYSTEM_SETTINGS_DIGIT_GROUP_SEPARATOR, 17 | SYSTEM_SETTINGS_HIDE_DAILY_PERIODS, 18 | SYSTEM_SETTINGS_HIDE_WEEKLY_PERIODS, 19 | SYSTEM_SETTINGS_HIDE_BIWEEKLY_PERIODS, 20 | SYSTEM_SETTINGS_HIDE_MONTHLY_PERIODS, 21 | SYSTEM_SETTINGS_HIDE_BIMONTHLY_PERIODS, 22 | SYSTEM_SETTINGS_IGNORE_ANALYTICS_APPROVAL_YEAR_THRESHOLD, 23 | ] 24 | -------------------------------------------------------------------------------- /src/modules/userSettings.js: -------------------------------------------------------------------------------- 1 | export const USER_SETTINGS_UI_LOCALE = 'keyUiLocale' 2 | export const USER_SETTINGS_DISPLAY_PROPERTY = 'keyAnalysisDisplayProperty' 3 | 4 | export const DERIVED_USER_SETTINGS_DISPLAY_NAME_PROPERTY = 5 | 'DERIVED_USER_SETTINGS_DISPLAY_NAME_PROPERTY' 6 | -------------------------------------------------------------------------------- /src/reducers/__tests__/dimensions.spec.js: -------------------------------------------------------------------------------- 1 | import reducer, { SET_DIMENSIONS, DEFAULT_DIMENSIONS } from '../dimensions.js' 2 | 3 | describe('reducer: dimensions', () => { 4 | const dimensionsToSet = { 5 | abc: { 6 | id: 'abc', 7 | name: 'ABC', 8 | }, 9 | } 10 | 11 | it('should return the default state', () => { 12 | const actualState = reducer(undefined, { type: 'NO_MATCH' }) 13 | expect(actualState).toEqual(DEFAULT_DIMENSIONS) 14 | }) 15 | 16 | it(`${SET_DIMENSIONS}: should set the new dimensions object`, () => { 17 | const actualState = reducer( 18 | {}, 19 | { 20 | type: SET_DIMENSIONS, 21 | value: dimensionsToSet, 22 | } 23 | ) 24 | 25 | const expectedState = { 26 | ...DEFAULT_DIMENSIONS, 27 | ...dimensionsToSet, 28 | } 29 | 30 | expect(actualState).toEqual(expectedState) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/reducers/__tests__/loader.spec.js: -------------------------------------------------------------------------------- 1 | import reducer, { 2 | DEFAULT_LOADING, 3 | SET_LOAD_ERROR, 4 | CLEAR_LOAD_ERROR, 5 | SET_LOADING, 6 | } from '../loader.js' 7 | 8 | describe('reducer: loader', () => { 9 | const errorMsg = 'fail!' 10 | 11 | it('should return the default state', () => { 12 | const actualState = reducer(undefined, { type: 'NO_MATCH' }) 13 | expect(actualState).toEqual(DEFAULT_LOADING) 14 | }) 15 | 16 | it(`${SET_LOAD_ERROR}: should set the new loadError`, () => { 17 | const actualState = reducer(DEFAULT_LOADING, { 18 | type: SET_LOAD_ERROR, 19 | value: errorMsg, 20 | }) 21 | 22 | expect(actualState.loadingError).toEqual(errorMsg) 23 | }) 24 | 25 | it(`${CLEAR_LOAD_ERROR}: should clear the loadError`, () => { 26 | const actualState = reducer( 27 | { 28 | isLoading: false, 29 | loadingError: errorMsg, 30 | isPluginLoading: true, 31 | }, 32 | { type: CLEAR_LOAD_ERROR } 33 | ) 34 | 35 | expect(actualState).toEqual(DEFAULT_LOADING) 36 | }) 37 | 38 | it(`${SET_LOADING}: should set the loading flag`, () => { 39 | const actualState = reducer(DEFAULT_LOADING, { 40 | type: SET_LOADING, 41 | value: true, 42 | }) 43 | 44 | expect(actualState.isLoading).toEqual(true) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /src/reducers/__tests__/recommendedIds.spec.js: -------------------------------------------------------------------------------- 1 | import reducer, { 2 | DEFAULT_RECOMMENDED_IDS, 3 | SET_RECOMMENDED_IDS, 4 | } from '../recommendedIds.js' 5 | 6 | describe('reducer: recommendedIds', () => { 7 | const ids = ['abc', 'bcd'] 8 | 9 | it('should return the default state', () => { 10 | const actualState = reducer(undefined, { type: 'NO_MATCH' }) 11 | expect(actualState).toEqual(DEFAULT_RECOMMENDED_IDS) 12 | }) 13 | 14 | it(`${SET_RECOMMENDED_IDS}: should set the new recommendedIds`, () => { 15 | const actualState = reducer(DEFAULT_RECOMMENDED_IDS, { 16 | type: SET_RECOMMENDED_IDS, 17 | value: ids, 18 | }) 19 | 20 | expect(actualState).toEqual(ids) 21 | }) 22 | 23 | it(`${SET_RECOMMENDED_IDS}: should clear recommendedIds`, () => { 24 | const actualState = reducer(ids, { 25 | type: SET_RECOMMENDED_IDS, 26 | value: undefined, 27 | }) 28 | 29 | expect(actualState).toEqual(DEFAULT_RECOMMENDED_IDS) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /src/reducers/__tests__/settings.spec.js: -------------------------------------------------------------------------------- 1 | import reducer, { DEFAULT_SETTINGS, ADD_SETTINGS } from '../settings.js' 2 | 3 | describe('reducer: settings', () => { 4 | const currentState = { 5 | keyAnalysisRelativePeriod: 'LAST_12_MONTHS', 6 | keyAnalysisDisplayProperty: 'name', 7 | } 8 | 9 | it('should return the default state', () => { 10 | const actualState = reducer(DEFAULT_SETTINGS, { type: 'NO_MATCH' }) 11 | 12 | expect(actualState).toEqual(DEFAULT_SETTINGS) 13 | }) 14 | 15 | it(`${ADD_SETTINGS}: should add settings`, () => { 16 | const stateToAdd = { 17 | keyAnalysisDisplayProperty: 'shortName', 18 | keyAnalysisDigitGroupSeparator: 'SPACE', 19 | } 20 | 21 | const actualState = reducer(currentState, { 22 | type: ADD_SETTINGS, 23 | value: stateToAdd, 24 | }) 25 | 26 | const expectedState = { 27 | // Keep this prop 28 | keyAnalysisRelativePeriod: 'LAST_12_MONTHS', 29 | // Update this prop 30 | keyAnalysisDisplayProperty: 'shortName', 31 | // Add this prop 32 | keyAnalysisDigitGroupSeparator: 'SPACE', 33 | } 34 | 35 | expect(actualState).toEqual(expectedState) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /src/reducers/__tests__/visualization.spec.js: -------------------------------------------------------------------------------- 1 | import reducer, { 2 | DEFAULT_VISUALIZATION, 3 | SET_VISUALIZATION, 4 | CLEAR_VISUALIZATION, 5 | } from '../visualization.js' 6 | 7 | describe('reducer: visualization', () => { 8 | it('should return the default state', () => { 9 | const actualState = reducer(undefined, { type: 'NO_MATCH' }) 10 | 11 | expect(actualState).toEqual(DEFAULT_VISUALIZATION) 12 | }) 13 | 14 | it('SET_VISUALIZATION: should set the new visualization', () => { 15 | const visualization = {} 16 | const actualState = reducer(undefined, { 17 | type: SET_VISUALIZATION, 18 | value: visualization, 19 | }) 20 | 21 | expect(visualization).toEqual(actualState) 22 | }) 23 | 24 | it('CLEAR_VISUALIZATION should set the default state', () => { 25 | const actualState = reducer( 26 | { currentVal: 123 }, 27 | { type: CLEAR_VISUALIZATION } 28 | ) 29 | 30 | expect(actualState).toEqual(DEFAULT_VISUALIZATION) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/reducers/dimensions.js: -------------------------------------------------------------------------------- 1 | import { getPredefinedDimensions } from '@dhis2/analytics' 2 | 3 | export const SET_DIMENSIONS = 'SET_DIMENSIONS' 4 | export const SET_SELECTED_DIMENSION = 'SET_SELECTED_DIMENSION' 5 | 6 | export const DEFAULT_DIMENSIONS = getPredefinedDimensions() 7 | 8 | export default (state = DEFAULT_DIMENSIONS, action) => { 9 | switch (action.type) { 10 | case SET_DIMENSIONS: { 11 | return { 12 | ...DEFAULT_DIMENSIONS, 13 | ...action.value, 14 | } 15 | } 16 | default: 17 | return state 18 | } 19 | } 20 | 21 | // Selectors 22 | 23 | /** 24 | * Selector which returns dimensions from the state object 25 | * If state.dimensions is null, then the dimensions api request 26 | * has not yet completed. If state.dimensions is an empty object 27 | * then the dimensions api request is complete, and there are no 28 | * dimensions 29 | * 30 | * @function 31 | * @param {Object} state The current state 32 | * @returns {Array} 33 | */ 34 | export const sGetDimensions = (state) => state.dimensions 35 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import current, * as fromCurrent from './current.js' 3 | import dimensions, * as fromDimensions from './dimensions.js' 4 | import loader, * as fromLoader from './loader.js' 5 | import metadata, * as fromMetadata from './metadata.js' 6 | import recommendedIds, * as fromRecommendedIds from './recommendedIds.js' 7 | import settings, * as fromSettings from './settings.js' 8 | import snackbar, * as fromSnackbar from './snackbar.js' 9 | import ui, * as fromUi from './ui.js' 10 | import visualization, * as fromVisualization from './visualization.js' 11 | 12 | // Reducers 13 | 14 | export default combineReducers({ 15 | visualization, 16 | current, 17 | dimensions, 18 | recommendedIds, 19 | ui, 20 | metadata, 21 | settings, 22 | snackbar, 23 | loader, 24 | }) 25 | 26 | // Selectors 27 | 28 | export { 29 | fromVisualization, 30 | fromCurrent, 31 | fromDimensions, 32 | fromRecommendedIds, 33 | fromUi, 34 | fromMetadata, 35 | fromSettings, 36 | fromSnackbar, 37 | fromLoader, 38 | } 39 | 40 | export const sGetSeriesSetupItems = (state) => 41 | fromUi.sGetAxisSetup(state).map((item) => ({ 42 | dimensionItem: item.id, 43 | axis: item.axis, 44 | name: fromMetadata.sGetMetadata(state)[item.id]?.name, 45 | })) 46 | -------------------------------------------------------------------------------- /src/reducers/loader.js: -------------------------------------------------------------------------------- 1 | export const SET_LOAD_ERROR = 'SET_LOAD_ERROR' 2 | export const CLEAR_LOAD_ERROR = 'CLEAR_LOAD_ERROR' 3 | export const SET_LOADING = 'SET_LOADING' 4 | export const SET_PLUGIN_LOADING = 'SET_PLUGIN_LOADING' 5 | 6 | export const DEFAULT_LOADING = { 7 | isLoading: false, 8 | loadingError: null, 9 | isPluginLoading: true, 10 | } 11 | 12 | export default (state = DEFAULT_LOADING, action) => { 13 | switch (action.type) { 14 | case SET_LOAD_ERROR: { 15 | return { 16 | ...state, 17 | loadingError: action.value, 18 | } 19 | } 20 | case CLEAR_LOAD_ERROR: 21 | return { 22 | ...state, 23 | loadingError: DEFAULT_LOADING.loadingError, 24 | } 25 | case SET_LOADING: 26 | return { 27 | ...state, 28 | isLoading: action.value, 29 | } 30 | case SET_PLUGIN_LOADING: 31 | return { 32 | ...state, 33 | isPluginLoading: action.value, 34 | } 35 | default: 36 | return state 37 | } 38 | } 39 | 40 | // Selectors 41 | export const sGetLoadError = (state) => state.loader.loadingError 42 | export const sGetIsLoading = (state) => state.loader.isLoading 43 | export const sGetIsPluginLoading = (state) => state.loader.isPluginLoading 44 | -------------------------------------------------------------------------------- /src/reducers/metadata.js: -------------------------------------------------------------------------------- 1 | import getDefaultMetadata from '../modules/metadata.js' 2 | 3 | export const ADD_METADATA = 'ADD_METADATA' 4 | 5 | export const DEFAULT_METADATA = getDefaultMetadata() 6 | 7 | export default (state = DEFAULT_METADATA, action) => { 8 | switch (action.type) { 9 | case ADD_METADATA: { 10 | const result = { ...state } 11 | Object.entries(action.value).forEach(([key, value]) => { 12 | // Default metadata is translated by the client, so never overwrite with values from the server 13 | if (value.id in DEFAULT_METADATA) { 14 | return 15 | } 16 | 17 | result[key] = { ...result[key], ...value } 18 | }) 19 | return result 20 | } 21 | default: 22 | return state 23 | } 24 | } 25 | 26 | // Selectors 27 | 28 | export const sGetMetadata = (state) => state.metadata 29 | -------------------------------------------------------------------------------- /src/reducers/recommendedIds.js: -------------------------------------------------------------------------------- 1 | export const SET_RECOMMENDED_IDS = 'SET_RECOMMENDED_IDS' 2 | 3 | export const DEFAULT_RECOMMENDED_IDS = [] 4 | 5 | export default (state = DEFAULT_RECOMMENDED_IDS, action) => { 6 | switch (action.type) { 7 | case SET_RECOMMENDED_IDS: { 8 | return action.value || DEFAULT_RECOMMENDED_IDS 9 | } 10 | default: 11 | return state 12 | } 13 | } 14 | 15 | // Selectors 16 | export const sGetRecommendedIds = (state) => state.recommendedIds 17 | -------------------------------------------------------------------------------- /src/reducers/settings.js: -------------------------------------------------------------------------------- 1 | export const ADD_SETTINGS = 'ADD_SETTINGS' 2 | 3 | export const DEFAULT_SETTINGS = { 4 | keyDateFormat: 'yyyy-MM-dd', 5 | keyAnalysisRelativePeriod: 'LAST_12_MONTHS', 6 | keyAnalysisDigitGroupSeparator: 'SPACE', 7 | keyIgnoreAnalyticsApprovalYearThreshold: -1, 8 | displayNameProperty: 'displayName', 9 | uiLocale: 'en', 10 | rootOrganisationUnits: [], 11 | } 12 | 13 | export default (state = DEFAULT_SETTINGS, action) => { 14 | switch (action.type) { 15 | case ADD_SETTINGS: { 16 | return { 17 | ...state, 18 | ...action.value, 19 | } 20 | } 21 | default: 22 | return state 23 | } 24 | } 25 | 26 | // Selectors 27 | 28 | export const sGetSettings = (state) => state.settings 29 | 30 | export const sGetSettingsDisplayProperty = (state) => 31 | sGetSettings(state).displayProperty 32 | 33 | export const sGetSettingsDisplayNameProperty = (state) => 34 | sGetSettings(state).displayNameProperty 35 | 36 | export const sGetSettingsDigitGroupSeparator = (state) => 37 | sGetSettings(state).keyAnalysisDigitGroupSeparator 38 | 39 | export const sGetRootOrgUnits = (state) => 40 | sGetSettings(state).rootOrganisationUnits 41 | 42 | export const sGetRelativePeriod = (state) => 43 | sGetSettings(state).keyAnalysisRelativePeriod 44 | 45 | export const sGetUiLocale = (state) => sGetSettings(state).uiLocale 46 | -------------------------------------------------------------------------------- /src/reducers/snackbar.js: -------------------------------------------------------------------------------- 1 | export const RECEIVED_SNACKBAR_MESSAGE = 'RECEIVED_SNACKBAR_MESSAGE' 2 | export const CLEAR_SNACKBAR = 'CLEAR_SNACKBAR' 3 | 4 | export const DEFAULT_SNACKBAR = { 5 | message: null, 6 | duration: null, 7 | } 8 | 9 | export default (state = DEFAULT_SNACKBAR, action) => { 10 | switch (action.type) { 11 | case RECEIVED_SNACKBAR_MESSAGE: { 12 | return { ...state, ...action.value } 13 | } 14 | case CLEAR_SNACKBAR: { 15 | return DEFAULT_SNACKBAR 16 | } 17 | default: 18 | return state 19 | } 20 | } 21 | 22 | // Selectors 23 | 24 | export const sGetSnackbar = (state) => state.snackbar 25 | -------------------------------------------------------------------------------- /src/reducers/visualization.js: -------------------------------------------------------------------------------- 1 | export const SET_VISUALIZATION = 'SET_VISUALIZATION' 2 | export const CLEAR_VISUALIZATION = 'CLEAR_VISUALIZATION' 3 | 4 | export const DEFAULT_VISUALIZATION = null 5 | 6 | export default (state = DEFAULT_VISUALIZATION, action) => { 7 | switch (action.type) { 8 | case SET_VISUALIZATION: { 9 | return action.value 10 | } 11 | case CLEAR_VISUALIZATION: { 12 | return DEFAULT_VISUALIZATION 13 | } 14 | default: 15 | return state 16 | } 17 | } 18 | 19 | // Selectors 20 | 21 | export const sGetVisualization = (state) => state.visualization 22 | export const sGetInterpretations = (state) => 23 | state.visualization ? state.visualization.interpretations : [] 24 | -------------------------------------------------------------------------------- /src/widgets/LoadingMask.js: -------------------------------------------------------------------------------- 1 | import { CircularLoader } from '@dhis2/ui' 2 | import React from 'react' 3 | import styles from './styles/LoadingMask.module.css' 4 | 5 | function LoadingMask() { 6 | return ( 7 |
8 | 9 |
10 | ) 11 | } 12 | 13 | export default LoadingMask 14 | -------------------------------------------------------------------------------- /src/widgets/styles/LoadingMask.module.css: -------------------------------------------------------------------------------- 1 | .progress { 2 | margin: var(--spacers-dp12); 3 | max-inline-size: 200; 4 | text-align: center; 5 | align-self: center; 6 | } 7 | 8 | .outer { 9 | display: flex; 10 | justify-content: center; 11 | block-size: 100%; 12 | } 13 | --------------------------------------------------------------------------------