diff --git a/cypress/integration/dataTable.cy.js b/cypress/integration/dataTable.cy.js index 2bcaefea5..0fce15a16 100644 --- a/cypress/integration/dataTable.cy.js +++ b/cypress/integration/dataTable.cy.js @@ -177,7 +177,7 @@ describe('data table', () => { // check number of columns cy.getByDataTest('bottom-panel') .findByDataTest('dhis2-uicore-datatablecellhead') - .should('have.length', 10) + .should('have.length', 11) cy.getByDataTest('bottom-panel') .findByDataTest('dhis2-uicore-datatablecellhead') diff --git a/i18n/en.pot b/i18n/en.pot index df97c59df..5143cecaf 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-03-12T18:11:56.380Z\n" -"PO-Revision-Date: 2026-03-12T18:11:56.380Z\n" +"POT-Creation-Date: 2026-04-22T17:25:30.092Z\n" +"PO-Revision-Date: 2026-04-22T17:25:30.092Z\n" msgid "2020" msgstr "2020" @@ -32,24 +32,39 @@ msgstr "This app could not retrieve required data." msgid "Network error" msgstr "Network error" +msgid "Auto" +msgstr "Auto" + msgid "Classification" msgstr "Classification" msgid "Classes" msgstr "Classes" -msgid "Legend set" -msgstr "Legend set" +msgid "Decimal places" +msgstr "Decimal places" + +msgid "Isolated class" +msgstr "Isolated class" + +msgid "Min" +msgstr "Min" + +msgid "Max" +msgstr "Max" msgid "Color" msgstr "Color" -msgid "Size" -msgstr "Size" - msgid "Name" msgstr "Name" +msgid "Legend set" +msgstr "Legend set" + +msgid "Size" +msgstr "Size" + msgid "Name and value" msgstr "Name and value" @@ -249,6 +264,9 @@ msgstr "Point color" msgid "Point radius" msgstr "Point radius" +msgid "Count org units without coordinates" +msgstr "Count org units without coordinates" + msgid "event" msgstr "event" @@ -356,15 +374,6 @@ msgstr "Max should be greater than min" msgid "Valid steps are {{minSteps}} to {{maxSteps}}" msgstr "Valid steps are {{minSteps}} to {{maxSteps}}" -msgid "Min" -msgstr "Min" - -msgid "Max" -msgstr "Max" - -msgid "Steps" -msgstr "Steps" - msgid "Facility buffer" msgstr "Facility buffer" @@ -383,9 +392,18 @@ msgstr "View all events" msgid "Radius" msgstr "Radius" +msgid "Count events without coordinates" +msgstr "Count events without coordinates" + msgid "You can style events by data element after selecting a program." msgstr "You can style events by data element after selecting a program." +msgid "Include unclassified events" +msgstr "Include unclassified events" + +msgid "Include events with no data" +msgstr "Include events with no data" + msgid "Program is required" msgstr "Program is required" @@ -443,15 +461,24 @@ msgstr "Polygons are represented by their centroids." msgid "Labels" msgstr "Labels" +msgid "Include org units with no data" +msgstr "Include org units with no data" + +msgid "No data" +msgstr "No data" + +msgid "Include unclassified org units" +msgstr "Include unclassified org units" + +msgid "Unclassified" +msgstr "Unclassified" + msgid "Aggregation type" msgstr "Aggregation type" msgid "Only show completed events" msgstr "Only show completed events" -msgid "Include org units with no data" -msgstr "Include org units with no data" - msgid "Low radius" msgstr "Low radius" @@ -656,6 +683,19 @@ msgstr "Edit layer" msgid "Remove layer" msgstr "Remove layer" +msgid "Data quality" +msgstr "Data quality" + +msgid "{{count}} event without coordinates" +msgid_plural "{{count}} event without coordinates" +msgstr[0] "{{count}} event without coordinates" +msgstr[1] "{{count}} events without coordinates" + +msgid "{{count}} org unit without coordinates" +msgid_plural "{{count}} org unit without coordinates" +msgstr[0] "{{count}} org unit without coordinates" +msgstr[1] "{{count}} org units without coordinates" + msgid "Filters" msgstr "Filters" @@ -714,9 +754,6 @@ msgstr "Groups" msgid "Parent unit" msgstr "Parent unit" -msgid "No data" -msgstr "No data" - msgid "Not set" msgstr "Not set" @@ -967,6 +1004,12 @@ msgstr "Click to unpin legend" msgid "Click to pin legend" msgstr "Click to pin legend" +msgid "Hide layer" +msgstr "Hide layer" + +msgid "Show layer" +msgstr "Show layer" + msgid "No program" msgstr "No program" @@ -1638,6 +1681,18 @@ msgstr "Equal intervals" msgid "Equal counts" msgstr "Equal counts" +msgid "Natural breaks (intervals)" +msgstr "Natural breaks (intervals)" + +msgid "Natural breaks (clusters)" +msgstr "Natural breaks (clusters)" + +msgid "Pretty breaks" +msgstr "Pretty breaks" + +msgid "Logarithmic scale" +msgstr "Logarithmic scale" + msgid "Symbol" msgstr "Symbol" @@ -1707,15 +1762,15 @@ msgstr "Displaying first {{pageSize}} events out of {{total}}" msgid "Event" msgstr "Event" +msgid "GroupSet used for styling was not found" +msgstr "GroupSet used for styling was not found" + msgid "Facilities" msgstr "Facilities" msgid "an error occurred" msgstr "an error occurred" -msgid "GroupSet used for styling was not found" -msgstr "GroupSet used for styling was not found" - msgid "No coordinates found for selected facilities" msgstr "No coordinates found for selected facilities" @@ -1746,12 +1801,12 @@ msgstr "Data item was not found" msgid "Thematic layer" msgstr "Thematic layer" -msgid "Tracked entity" -msgstr "Tracked entity" - msgid "related" msgstr "related" +msgid "Tracked entity" +msgstr "Tracked entity" + msgid "not one of" msgstr "not one of" @@ -1801,9 +1856,6 @@ msgstr "Org units" msgid "Facility" msgstr "Facility" -msgid "Other" -msgstr "Other" - msgid "Start date is invalid" msgstr "Start date is invalid" diff --git a/package.json b/package.json index 2d7ecc54c..d3066fd9e 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "redux": "^4.2.1", "redux-logger": "^3.0.6", "redux-thunk": "^2.4.2", + "simple-statistics": "^7.8.9", "styled-jsx": "^4.0.1", "url-polyfill": "^1.1.14" }, diff --git a/src/actions/layerEdit.js b/src/actions/layerEdit.js index 836dfae32..ad7ff065d 100644 --- a/src/actions/layerEdit.js +++ b/src/actions/layerEdit.js @@ -92,6 +92,16 @@ export const setColorScale = (colorScale) => ({ colorScale, }) +export const setLegendDecimalPlaces = (legendDecimalPlaces) => ({ + type: types.LAYER_EDIT_LEGEND_DECIMAL_PLACES_SET, + legendDecimalPlaces, +}) + +export const setLegendIsolated = (legendIsolated) => ({ + type: types.LAYER_EDIT_LEGEND_ISOLATED_SET, + legendIsolated, +}) + // Set event status export const setEventStatus = (status) => ({ type: types.LAYER_EDIT_EVENT_STATUS_SET, @@ -117,6 +127,12 @@ export const setEventClustering = (checked) => ({ checked, }) +// Set if events without coordinates should be counted and added to data table (event) +export const setCountEventsWithoutCoordinates = (checked) => ({ + type: types.LAYER_EDIT_COUNT_EVENTS_WITHOUT_COORDS_SET, + checked, +}) + // Set event point radius (event layer) export const setEventPointRadius = (radius) => ({ type: types.LAYER_EDIT_EVENT_POINT_RADIUS_SET, @@ -161,6 +177,12 @@ export const setOrganisationUnitField = (payload) => ({ payload, }) +// Set if organisation unit without coordinates should be counted and added to data table +export const setCountOrgUnitsWithoutCoordinates = (checked) => ({ + type: types.LAYER_EDIT_ORGANISATION_UNIT_WITHOUT_COORDS_SET, + checked, +}) + // Set period label (earth engine) export const setPeriodName = (periodName) => ({ type: types.LAYER_EDIT_PERIOD_NAME_SET, @@ -347,10 +369,14 @@ export const setRenderingStrategy = (display) => ({ payload: display, }) -// Set no data color -export const setNoDataColor = (color) => ({ - type: types.LAYER_EDIT_NO_DATA_COLOR_SET, - payload: color, +export const setNoDataLegend = (noDataLegend) => ({ + type: types.LAYER_EDIT_NO_DATA_LEGEND_SET, + payload: noDataLegend, +}) + +export const setUnclassifiedLegend = (unclassifiedLegend) => ({ + type: types.LAYER_EDIT_UNCLASSIFIED_LEGEND_SET, + payload: unclassifiedLegend, }) // Set period for EE layer diff --git a/src/components/app/FileMenu.jsx b/src/components/app/FileMenu.jsx index 5f3bc9f26..cfd1b6c18 100644 --- a/src/components/app/FileMenu.jsx +++ b/src/components/app/FileMenu.jsx @@ -4,7 +4,7 @@ import { preparePayloadForSaveAs, VIS_TYPE_MAP, } from '@dhis2/analytics' -import { useDataMutation, useDataEngine } from '@dhis2/app-runtime' +import { useDataMutation, useDataEngine, useConfig } from '@dhis2/app-runtime' import { useAlert } from '@dhis2/app-service-alerts' import i18n from '@dhis2/d2-i18n' import PropTypes from 'prop-types' @@ -67,6 +67,7 @@ const FileMenu = ({ onFileMenuAction }) => { const map = useSelector((state) => state.map) const dispatch = useDispatch() const engine = useDataEngine() + const { serverVersion } = useConfig() const { systemSettings, currentUser } = useCachedData() const defaultBasemap = systemSettings.keyDefaultBaseMap //alerts @@ -119,6 +120,7 @@ const FileMenu = ({ onFileMenuAction }) => { const cleanedMap = cleanMapConfig({ config: map, defaultBasemapId: defaultBasemap, + serverVersion, }) const config = preparePayloadForSave({ @@ -160,6 +162,7 @@ const FileMenu = ({ onFileMenuAction }) => { config: latestMap, defaultBasemapId: defaultBasemap, cleanMapviewConfig: false, + serverVersion, }) const config = preparePayloadForSave({ @@ -189,6 +192,7 @@ const FileMenu = ({ onFileMenuAction }) => { const cleanedMap = cleanMapConfig({ config: map, defaultBasemapId: defaultBasemap, + serverVersion, }) const config = preparePayloadForSaveAs({ diff --git a/src/components/classification/Classification.jsx b/src/components/classification/Classification.jsx index 5227c6168..66b367bf0 100644 --- a/src/components/classification/Classification.jsx +++ b/src/components/classification/Classification.jsx @@ -3,7 +3,11 @@ import { range } from 'lodash/fp' import PropTypes from 'prop-types' import React from 'react' import { connect } from 'react-redux' -import { setClassification, setColorScale } from '../../actions/layerEdit.js' +import { + setClassification, + setColorScale, + setLegendDecimalPlaces, +} from '../../actions/layerEdit.js' import { getClassificationTypes, CLASSIFICATION_EQUAL_INTERVALS, @@ -16,6 +20,7 @@ import { getColorScale, } from '../../util/colors.js' import { SelectField, ColorScaleSelect } from '../core/index.js' +import IsolatedClass from './IsolatedClass.jsx' import styles from './styles/Classification.module.css' const classRange = range(3, 10).map((num) => ({ @@ -23,12 +28,21 @@ const classRange = range(3, 10).map((num) => ({ name: num.toString(), })) // 3 - 9 +const DECIMAL_PLACES_AUTO = 'auto' + +const decimalPlacesItems = [ + { id: DECIMAL_PLACES_AUTO, name: i18n.t('Auto') }, + ...range(0, 5).map((num) => ({ id: num, name: num.toString() })), +] // Auto, 0 - 4 + const Classification = ({ method, classes, colorScale, + legendDecimalPlaces, setClassification, setColorScale, + setLegendDecimalPlaces, }) => { const colorScaleName = colorScale ? getColorScale(colorScale) @@ -44,22 +58,37 @@ const Classification = ({ className={styles.select} />,