From 7f8ab4236e1c1c954903e60d8574cad3dc7a8e79 Mon Sep 17 00:00:00 2001 From: jasminenguyennn Date: Tue, 6 Dec 2022 16:25:50 +0000 Subject: [PATCH 01/49] feat: [DHIS2-13237] add coordinate with map --- .../MapCoordinates/MapCoordinates.js | 40 +++++++++++++++++++ .../components/MapCoordinates/index.js | 2 + .../WidgetEnrollment.component.js | 6 +-- .../capture-core/converters/clientToView.js | 2 + .../metaData/DataElement/dataElementTypes.js | 1 + 5 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js create mode 100644 src/core_modules/capture-core/components/MapCoordinates/index.js diff --git a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js new file mode 100644 index 0000000000..1a9107f278 --- /dev/null +++ b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js @@ -0,0 +1,40 @@ +// @flow +import React from 'react'; +import { Map, TileLayer, Marker } from 'react-leaflet'; +import { withStyles } from '@material-ui/core'; + +type Props = $ReadOnly<{| + latitude: number | string, + longitude: number | string, + classes: Object +|}>; + +const styles = () => ({ + map: { + width: 150, + height: 120, + }, +}); + +const MapCoordinatesPlain = withStyles(styles)(({ latitude, longitude, classes }: Props) => { + const position = [longitude, latitude]; + return ( + + + + + ); +}, +); + +export const MapCoordinates = props => ; diff --git a/src/core_modules/capture-core/components/MapCoordinates/index.js b/src/core_modules/capture-core/components/MapCoordinates/index.js new file mode 100644 index 0000000000..d71881efac --- /dev/null +++ b/src/core_modules/capture-core/components/MapCoordinates/index.js @@ -0,0 +1,2 @@ +// @flow +export { MapCoordinates } from './MapCoordinates'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js index fd3c71f856..5e0aae505a 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js @@ -5,7 +5,6 @@ import { IconClock16, IconDimensionOrgUnit16, IconCalendar16, - IconLocation16, colors, Tag, spacersNum, @@ -145,12 +144,9 @@ export const WidgetEnrollmentPlain = ({ {enrollment.geometry && (
- - - {convertValueClientToView( convertValueServerToClient(enrollment.geometry.coordinates, geometryType), - geometryType, + dataElementTypes.MAP_COORDINATE, )}
)} diff --git a/src/core_modules/capture-core/converters/clientToView.js b/src/core_modules/capture-core/converters/clientToView.js index 1b5d7bfcc1..41ec7e614a 100644 --- a/src/core_modules/capture-core/converters/clientToView.js +++ b/src/core_modules/capture-core/converters/clientToView.js @@ -6,6 +6,7 @@ import { dataElementTypes, type DataElement } from '../metaData'; import { convertMomentToDateFormatString } from '../utils/converters/date'; import { stringifyNumber } from './common/stringifyNumber'; import { MinimalCoordinates } from '../components/MinimalCoordinates'; +import { MapCoordinates } from '../components/MapCoordinates'; function convertDateForView(rawValue: string): string { @@ -58,6 +59,7 @@ const valueConvertersForType = { [dataElementTypes.TRUE_ONLY]: () => i18n.t('Yes'), [dataElementTypes.BOOLEAN]: (rawValue: boolean) => (rawValue ? i18n.t('Yes') : i18n.t('No')), [dataElementTypes.COORDINATE]: MinimalCoordinates, + [dataElementTypes.MAP_COORDINATE]: MapCoordinates, [dataElementTypes.AGE]: convertDateForView, [dataElementTypes.FILE_RESOURCE]: convertResourceForView, [dataElementTypes.IMAGE]: convertResourceForView, diff --git a/src/core_modules/capture-core/metaData/DataElement/dataElementTypes.js b/src/core_modules/capture-core/metaData/DataElement/dataElementTypes.js index 87ffecb6de..860a4bf362 100644 --- a/src/core_modules/capture-core/metaData/DataElement/dataElementTypes.js +++ b/src/core_modules/capture-core/metaData/DataElement/dataElementTypes.js @@ -29,6 +29,7 @@ export const dataElementTypes = { IMAGE: 'IMAGE', AGE: 'AGE', COORDINATE: 'COORDINATE', + MAP_COORDINATE: 'MAP_COORDINATE', POLYGON: 'POLYGON', USERNAME: 'USERNAME', ASSIGNEE: 'ASSIGNEE', From c9e22f46cc81c0f7ab497a37c4db9c3fa1177733 Mon Sep 17 00:00:00 2001 From: jasminenguyennn Date: Tue, 6 Dec 2022 16:43:17 +0000 Subject: [PATCH 02/49] chore: minor fix --- .../capture-core/components/MapCoordinates/MapCoordinates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js index 1a9107f278..1f7ea11a55 100644 --- a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js +++ b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js @@ -37,4 +37,4 @@ const MapCoordinatesPlain = withStyles(styles)(({ latitude, longitude, classes } }, ); -export const MapCoordinates = props => ; +export const MapCoordinates = (props: Props) => ; From 29b5ee94a31fed5760540ca8a53f6be3c2be4019 Mon Sep 17 00:00:00 2001 From: jasminenguyennn Date: Wed, 7 Dec 2022 10:47:48 +0000 Subject: [PATCH 03/49] feat: [DHIS2-13237] handle polygon in the map --- .../MapCoordinates/MapCoordinates.js | 28 +++++++++++-------- .../WidgetEnrollment.component.js | 7 ++--- .../capture-core/converters/clientToView.js | 5 +++- .../capture-core/converters/index.js | 5 +++- .../metaData/DataElement/dataElementTypes.js | 1 - 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js index 1f7ea11a55..5beae7b770 100644 --- a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js +++ b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js @@ -1,12 +1,15 @@ // @flow import React from 'react'; -import { Map, TileLayer, Marker } from 'react-leaflet'; +import { Map, TileLayer, Marker, Polygon } from 'react-leaflet'; import { withStyles } from '@material-ui/core'; +import { dataElementTypes } from '../../metaData'; + +type Coordinate = Array; type Props = $ReadOnly<{| - latitude: number | string, - longitude: number | string, - classes: Object + coordinates: Coordinate | Array>, + type: string, + classes: Object |}>; const styles = () => ({ @@ -16,13 +19,14 @@ const styles = () => ({ }, }); -const MapCoordinatesPlain = withStyles(styles)(({ latitude, longitude, classes }: Props) => { - const position = [longitude, latitude]; +const MapCoordinatesPlain = ({ coordinates, type, classes }: Props) => { + const center = type === dataElementTypes.COORDINATE ? coordinates : coordinates[0][0]; + return ( - + {type === dataElementTypes.COORDINATE && } + {type === dataElementTypes.POLYGON && } ); -}, -); +}; -export const MapCoordinates = (props: Props) => ; +export const MapCoordinates = withStyles(styles)(MapCoordinatesPlain); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js index 5e0aae505a..efb063e2cc 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js @@ -16,7 +16,7 @@ import { Widget } from '../Widget'; import type { PlainProps } from './enrollment.types'; import { Status } from './Status'; import { convertValue as convertValueServerToClient } from '../../converters/serverToClient'; -import { convertValue as convertValueClientToView } from '../../converters/clientToView'; +import { convertValue as convertValueClientToView, convertGeometryForMapView } from '../../converters/clientToView'; import { dataElementTypes } from '../../metaData'; import { Actions } from './Actions'; @@ -144,10 +144,7 @@ export const WidgetEnrollmentPlain = ({ {enrollment.geometry && (
- {convertValueClientToView( - convertValueServerToClient(enrollment.geometry.coordinates, geometryType), - dataElementTypes.MAP_COORDINATE, - )} + {convertGeometryForMapView(enrollment.geometry.coordinates, geometryType)}
)} i18n.t('Yes'), [dataElementTypes.BOOLEAN]: (rawValue: boolean) => (rawValue ? i18n.t('Yes') : i18n.t('No')), [dataElementTypes.COORDINATE]: MinimalCoordinates, - [dataElementTypes.MAP_COORDINATE]: MapCoordinates, [dataElementTypes.AGE]: convertDateForView, [dataElementTypes.FILE_RESOURCE]: convertResourceForView, [dataElementTypes.IMAGE]: convertResourceForView, @@ -87,3 +86,7 @@ export function convertDateWithTimeForView(rawValue?: ?string): string { } return convertDateTimeForView(rawValue); } + +export function convertGeometryForMapView(value: any, type: $Keys): any { + return ; +} diff --git a/src/core_modules/capture-core/converters/index.js b/src/core_modules/capture-core/converters/index.js index 74b4d0e019..221a56b9ba 100644 --- a/src/core_modules/capture-core/converters/index.js +++ b/src/core_modules/capture-core/converters/index.js @@ -1,7 +1,10 @@ // @flow export { convertValue as convertClientToForm } from './clientToForm'; export { convertValue as convertClientToList } from './clientToList'; -export { convertValue as convertClientToView, convertDateWithTimeForView } from './clientToView'; +export { convertValue as convertClientToView, + convertDateWithTimeForView, + convertGeometryForMapView, +} from './clientToView'; export { convertValue as convertClientToServer } from './clientToServer'; export { convertValue as convertFormToClient } from './formToClient'; export { diff --git a/src/core_modules/capture-core/metaData/DataElement/dataElementTypes.js b/src/core_modules/capture-core/metaData/DataElement/dataElementTypes.js index 860a4bf362..87ffecb6de 100644 --- a/src/core_modules/capture-core/metaData/DataElement/dataElementTypes.js +++ b/src/core_modules/capture-core/metaData/DataElement/dataElementTypes.js @@ -29,7 +29,6 @@ export const dataElementTypes = { IMAGE: 'IMAGE', AGE: 'AGE', COORDINATE: 'COORDINATE', - MAP_COORDINATE: 'MAP_COORDINATE', POLYGON: 'POLYGON', USERNAME: 'USERNAME', ASSIGNEE: 'ASSIGNEE', From 02b7e6dfe469bfc69750cc5d993e513710dcb39a Mon Sep 17 00:00:00 2001 From: jasminenguyennn Date: Wed, 7 Dec 2022 11:02:55 +0000 Subject: [PATCH 04/49] chore: fix flow --- .../capture-core/components/MapCoordinates/MapCoordinates.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js index 5beae7b770..e5f47cb836 100644 --- a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js +++ b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js @@ -4,10 +4,9 @@ import { Map, TileLayer, Marker, Polygon } from 'react-leaflet'; import { withStyles } from '@material-ui/core'; import { dataElementTypes } from '../../metaData'; -type Coordinate = Array; type Props = $ReadOnly<{| - coordinates: Coordinate | Array>, + coordinates: any, type: string, classes: Object |}>; From 6e392c7f68c4aed914e80bdc078ea2b8f1549f2f Mon Sep 17 00:00:00 2001 From: jasminenguyennn Date: Mon, 12 Dec 2022 17:58:08 +0000 Subject: [PATCH 05/49] chore: [DHIS2-13237] convert coordinates --- .../components/MapCoordinates/MapCoordinates.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js index e5f47cb836..d8364efa6a 100644 --- a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js +++ b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js @@ -18,8 +18,18 @@ const styles = () => ({ }, }); +const convertToClientCoordinates = (coordinates, type) => { + switch (type) { + case dataElementTypes.COORDINATE: + return [coordinates[1], coordinates[0]]; + default: + return coordinates.map(coord => [coord[1], coord[0]]); + } +}; + const MapCoordinatesPlain = ({ coordinates, type, classes }: Props) => { - const center = type === dataElementTypes.COORDINATE ? coordinates : coordinates[0][0]; + const clientValues = convertToClientCoordinates(coordinates, type); + const center = type === dataElementTypes.COORDINATE ? clientValues : clientValues[0][0]; return ( { url="http://{s}.tile.osm.org/{z}/{x}/{y}.png" attribution="© OpenStreetMap contributors" /> - {type === dataElementTypes.COORDINATE && } - {type === dataElementTypes.POLYGON && } + {type === dataElementTypes.COORDINATE && } + {type === dataElementTypes.POLYGON && } ); }; From 10c02377600e072a211d4aa081ddbbf5f605a331 Mon Sep 17 00:00:00 2001 From: jasminenguyennn Date: Tue, 13 Dec 2022 16:09:33 +0000 Subject: [PATCH 06/49] feat: [DHIS2-13237] open on click and add new location --- i18n/en.pot | 19 ++- .../MapCoordinates/MapCoordinates.js | 59 +++++--- .../MapCoordinates/MapCoordinatesModal.js | 142 ++++++++++++++++++ .../components/MapCoordinates/index.js | 1 + .../Actions/Actions.component.js | 6 + .../AddLocation/AddLocation.component.js | 51 +++++++ .../Actions/AddLocation/addLocation.types.js | 6 + .../Actions/AddLocation/index.js | 2 + 8 files changed, 266 insertions(+), 20 deletions(-) create mode 100644 src/core_modules/capture-core/components/MapCoordinates/MapCoordinatesModal.js create mode 100644 src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/AddLocation.component.js create mode 100644 src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/addLocation.types.js create mode 100644 src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/index.js diff --git a/i18n/en.pot b/i18n/en.pot index 66c0d89343..0b9b6c27a8 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2022-11-14T13:43:34.731Z\n" -"PO-Revision-Date: 2022-11-14T13:43:34.731Z\n" +"POT-Creation-Date: 2022-12-13T15:16:47.439Z\n" +"PO-Revision-Date: 2022-12-13T15:16:47.439Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -553,6 +553,15 @@ msgstr "Program doesn't exist" msgid "Selected program is invalid for selected registering unit" msgstr "Selected program is invalid for selected registering unit" +msgid "coordinates" +msgstr "coordinates" + +msgid "area" +msgstr "area" + +msgid "Set" +msgstr "Set" + msgid "Online" msgstr "Online" @@ -1033,6 +1042,12 @@ msgstr "Enrollment actions" msgid "We are processing your request." msgstr "We are processing your request." +msgid "Add coordinates" +msgstr "Add coordinates" + +msgid "Add area" +msgstr "Add area" + msgid "Only one enrollment per {{tetName}} is allowed in this program" msgstr "Only one enrollment per {{tetName}} is allowed in this program" diff --git a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js index d8364efa6a..ef5ab6d4fc 100644 --- a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js +++ b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js @@ -1,8 +1,9 @@ // @flow -import React from 'react'; +import React, { useState } from 'react'; import { Map, TileLayer, Marker, Polygon } from 'react-leaflet'; import { withStyles } from '@material-ui/core'; import { dataElementTypes } from '../../metaData'; +import { MapCoordinatesModal } from './MapCoordinatesModal'; type Props = $ReadOnly<{| @@ -12,41 +13,63 @@ type Props = $ReadOnly<{| |}>; const styles = () => ({ - map: { + mapContainer: { width: 150, height: 120, }, + map: { + width: '100%', + height: '100%', + }, }); const convertToClientCoordinates = (coordinates, type) => { switch (type) { case dataElementTypes.COORDINATE: return [coordinates[1], coordinates[0]]; + case dataElementTypes.POLYGON: + return coordinates[0].map(coord => [coord[1], coord[0]]); default: - return coordinates.map(coord => [coord[1], coord[0]]); + return coordinates; } }; + const MapCoordinatesPlain = ({ coordinates, type, classes }: Props) => { + const [isModalOpen, setModalOpen] = useState(false); const clientValues = convertToClientCoordinates(coordinates, type); - const center = type === dataElementTypes.COORDINATE ? clientValues : clientValues[0][0]; + const center = type === dataElementTypes.COORDINATE ? clientValues : clientValues[0]; return ( - - +
+ { + setModalOpen(true); + }} + > + + {type === dataElementTypes.COORDINATE && } + {type === dataElementTypes.POLYGON && } + +
+ { console.log({ pos }); }} /> - {type === dataElementTypes.COORDINATE && } - {type === dataElementTypes.POLYGON && } -
+ ); }; diff --git a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinatesModal.js b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinatesModal.js new file mode 100644 index 0000000000..746a6720e9 --- /dev/null +++ b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinatesModal.js @@ -0,0 +1,142 @@ +// @flow +import React, { useState } from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { Modal, ModalTitle, ModalContent, ModalActions, Button, ButtonStrip } from '@dhis2/ui'; +import { Map, TileLayer, Marker, FeatureGroup } from 'react-leaflet'; +import { EditControl } from 'react-leaflet-draw'; +import { withStyles } from '@material-ui/core'; +import { dataElementTypes } from '../../metaData'; + +const styles = () => ({ + modalContent: { + width: '100%', + height: '75vh', + }, + map: { + width: '100%', + height: '100%', + }, + title: { + textTransform: 'capitalize', + }, +}); + +type Props = { + center: ?[number, number], + isOpen: boolean, + type: string, + setOpen: (open: boolean) => void, + onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, + classes: Object +} + +const MapCoordinatesModalPlain = ({ classes, center, isOpen, setOpen, type, onSetCoordinates }: Props) => { + const [position, setPosition] = useState(null); + const [coordinates, setCoordinates] = useState(null); + + const onHandleMapClicked = (mapCoordinates) => { + if (type === dataElementTypes.COORDINATE) { + const { lat, lng } = mapCoordinates.latlng; + const pos: [number, number] = [lat, lng]; + setPosition(pos); + } + }; + + const onMapPolygonCreated = (e: any) => { + const polygonCoordinates = e.layer.toGeoJSON().geometry.coordinates; + setCoordinates(polygonCoordinates); + }; + + const onMapPolygonEdited = (e: any) => { + const polygonCoordinates = e.layers.getLayers()[0].toGeoJSON().geometry.coordinates; + setCoordinates(polygonCoordinates); + }; + const onMapPolygonDelete = () => { + setCoordinates(null); + }; + + const renderMap = () => ( { + if (ref?.leafletElement) { + setTimeout(() => { ref.leafletElement.invalidateSize(); }, 250); + } + }} + className={classes.map} + onClick={onHandleMapClicked} + > + + {type === dataElementTypes.POLYGON && + + } + {type === dataElementTypes.COORDINATE && position && } + ); + + const getTitle = () => { + switch (type) { + case dataElementTypes.COORDINATE: + return i18n.t('coordinates'); + case dataElementTypes.POLYGON: + return i18n.t('area'); + default: + console.error(`${type} is not handled`); + return ''; + } + }; + + const renderActions = () => ( + + + ); + + return ( + + +
{getTitle()}
+
+ +
{renderMap()}
+
+ + {renderActions()} + +
+ ); +}; +export const MapCoordinatesModal = withStyles(styles)(MapCoordinatesModalPlain); diff --git a/src/core_modules/capture-core/components/MapCoordinates/index.js b/src/core_modules/capture-core/components/MapCoordinates/index.js index d71881efac..d3cf2eacb7 100644 --- a/src/core_modules/capture-core/components/MapCoordinates/index.js +++ b/src/core_modules/capture-core/components/MapCoordinates/index.js @@ -1,2 +1,3 @@ // @flow export { MapCoordinates } from './MapCoordinates'; +export { MapCoordinatesModal } from './MapCoordinatesModal'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js index 4d07c56a61..6fdb7331d7 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js @@ -8,6 +8,7 @@ import { Complete } from './Complete'; import { Delete } from './Delete'; import { Followup } from './Followup'; import { AddNew } from './AddNew'; +import { AddLocation } from './AddLocation'; import type { PlainProps } from './actions.types'; import { LoadingMaskForButton } from '../../LoadingMasks'; @@ -72,6 +73,10 @@ export const ActionsPlain = ({ enrollment={enrollment} onUpdate={handleOnUpdate} /> + { console.log('add'); }} + /> + ) } diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/AddLocation.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/AddLocation.component.js new file mode 100644 index 0000000000..4896c3e4dd --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/AddLocation.component.js @@ -0,0 +1,51 @@ +// @flow +import { IconLocation16, MenuItem } from '@dhis2/ui'; +import React, { useState, useMemo } from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { dataElementTypes } from '../../../../metaData'; +import { MapCoordinatesModal } from '../../../../components/MapCoordinates'; +import { useProgramFromIndexedDB } from '../../../../utils/cachedDataHooks/useProgramFromIndexedDB'; +import type { Props } from './addLocation.types'; + +const DEFAULT_CENTER = [51.505, -0.09]; +export const AddLocation = ({ enrollment, onAddLocation }: Props) => { + const [isOpen, setOpen] = useState(false); + const { program, loading, error } = useProgramFromIndexedDB(enrollment.program); + const geometryType = useMemo(() => { + if (!program) { return undefined; } + return program.featureType === 'POINT' ? dataElementTypes.COORDINATE : dataElementTypes.POLYGON; + }, [program]); + + if (loading || error) { + return null; + } + if (enrollment.geometry || !program?.featureType) { + return null; + } + + const getLabel = () => { + switch (geometryType) { + case dataElementTypes.COORDINATE: + return i18n.t('Add coordinates'); + default: + return i18n.t('Add area'); + } + }; + + return (<> + } + label={getLabel()} + onClick={() => { setOpen(true); }} + /> + + ); +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/addLocation.types.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/addLocation.types.js new file mode 100644 index 0000000000..7424930193 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/addLocation.types.js @@ -0,0 +1,6 @@ +// @flow + +export type Props = {| + enrollment: Object, + onAddLocation: (arg: Object) => void, +|}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/index.js new file mode 100644 index 0000000000..bdf4bdca7c --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/index.js @@ -0,0 +1,2 @@ +// @flow +export { AddLocation } from './AddLocation.component'; From d610ef6b5200e9002a15708aa3431627c099a451 Mon Sep 17 00:00:00 2001 From: jasminenguyennn Date: Wed, 14 Dec 2022 10:03:26 +0000 Subject: [PATCH 07/49] feat: [DHIS2-13237] handle add geometry --- .../MapCoordinates/MapCoordinates.js | 12 ++----- .../MapCoordinates/MapCoordinatesModal.js | 33 ++++++++++++------- .../MapCoordinates/mapCoordinates.types.js | 17 ++++++++++ .../EnrollmentPageDefault.component.js | 2 ++ .../EnrollmentPageDefault.container.js | 3 ++ .../EnrollmentPageDefault.types.js | 1 + ...EnrollmentAddEventPageDefault.component.js | 2 ++ ...EnrollmentAddEventPageDefault.container.js | 4 +++ .../EnrollmentAddEventPageDefault.types.js | 1 + .../EnrollmentEditEventPage.component.js | 2 ++ .../EnrollmentEditEventPage.container.js | 6 ++++ .../EnrollmentEditEventPage.types.js | 1 + .../Actions/Actions.component.js | 2 +- .../AddLocation/AddLocation.component.js | 17 ++++++++-- .../Actions/AddLocation/addLocation.types.js | 2 +- .../WidgetEnrollment.component.js | 10 ++++-- .../WidgetEnrollment.container.js | 5 ++- .../WidgetEnrollment/enrollment.types.js | 2 ++ .../capture-core/converters/clientToView.js | 4 --- .../capture-core/converters/index.js | 1 - 20 files changed, 93 insertions(+), 34 deletions(-) create mode 100644 src/core_modules/capture-core/components/MapCoordinates/mapCoordinates.types.js diff --git a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js index ef5ab6d4fc..85969f4a5a 100644 --- a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js +++ b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinates.js @@ -4,13 +4,7 @@ import { Map, TileLayer, Marker, Polygon } from 'react-leaflet'; import { withStyles } from '@material-ui/core'; import { dataElementTypes } from '../../metaData'; import { MapCoordinatesModal } from './MapCoordinatesModal'; - - -type Props = $ReadOnly<{| - coordinates: any, - type: string, - classes: Object -|}>; +import type { MiniMapProps } from './mapCoordinates.types'; const styles = () => ({ mapContainer: { @@ -35,7 +29,7 @@ const convertToClientCoordinates = (coordinates, type) => { }; -const MapCoordinatesPlain = ({ coordinates, type, classes }: Props) => { +const MapCoordinatesPlain = ({ coordinates, type, classes, onSetCoordinates }: MiniMapProps) => { const [isModalOpen, setModalOpen] = useState(false); const clientValues = convertToClientCoordinates(coordinates, type); const center = type === dataElementTypes.COORDINATE ? clientValues : clientValues[0]; @@ -67,7 +61,7 @@ const MapCoordinatesPlain = ({ coordinates, type, classes }: Props) => { center={center} isOpen={isModalOpen} setOpen={setModalOpen} - onSetCoordinates={(pos) => { console.log({ pos }); }} + onSetCoordinates={onSetCoordinates} /> ); diff --git a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinatesModal.js b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinatesModal.js index 746a6720e9..cce2fa7fba 100644 --- a/src/core_modules/capture-core/components/MapCoordinates/MapCoordinatesModal.js +++ b/src/core_modules/capture-core/components/MapCoordinates/MapCoordinatesModal.js @@ -6,6 +6,7 @@ import { Map, TileLayer, Marker, FeatureGroup } from 'react-leaflet'; import { EditControl } from 'react-leaflet-draw'; import { withStyles } from '@material-ui/core'; import { dataElementTypes } from '../../metaData'; +import type { ModalProps } from './mapCoordinates.types'; const styles = () => ({ modalContent: { @@ -21,24 +22,28 @@ const styles = () => ({ }, }); -type Props = { - center: ?[number, number], - isOpen: boolean, - type: string, - setOpen: (open: boolean) => void, - onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, - classes: Object -} +const convertToServerCoordinates = (coordinates, type) => { + if (!coordinates) { return null; } + switch (type) { + case dataElementTypes.COORDINATE: + return [coordinates[1], coordinates[0]]; + case dataElementTypes.POLYGON: + return coordinates[0].map(coord => [coord[1], coord[0]]); + default: + return coordinates; + } +}; -const MapCoordinatesModalPlain = ({ classes, center, isOpen, setOpen, type, onSetCoordinates }: Props) => { +const MapCoordinatesModalPlain = ({ classes, center, isOpen, setOpen, type, onSetCoordinates }: ModalProps) => { const [position, setPosition] = useState(null); const [coordinates, setCoordinates] = useState(null); const onHandleMapClicked = (mapCoordinates) => { if (type === dataElementTypes.COORDINATE) { const { lat, lng } = mapCoordinates.latlng; - const pos: [number, number] = [lat, lng]; - setPosition(pos); + const newPosition: [number, number] = [lat, lng]; + // $FlowFixMe + setPosition(newPosition); } }; @@ -114,7 +119,11 @@ const MapCoordinatesModalPlain = ({ classes, center, isOpen, setOpen, type, onSe : + ) : ( + : + + ); - const renderActions = () => ( - - - ); - return ( - - - {capitalizeFirstLetter(getTitle())} - + + {capitalizeFirstLetter(title)}
{renderMap()} - {isPoint &&
-
- {renderLatitude()} -
-
- {renderLongitude()} + {isPoint && ( +
+
+
{renderLatitude()}
+
{renderLongitude()}
+ {renderFieldButton()} +
+ {hasErrors && ( +
{i18n.t('Please provide valid coordinates')}
+ )}
- {renderFieldButton()} -
} + )}
- - {renderActions()} - + {renderActions()} ); }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/convertor.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/convertor.js new file mode 100644 index 0000000000..b2076a3028 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/convertor.js @@ -0,0 +1,22 @@ +// @flow +import { dataElementTypes } from '../../../metaData'; + +export const convertToServerCoordinates = ( + coordinates?: Array<[number, number]> | null, + type: string, +): ?[number, number] | ?Array<[number, number]> | ?[number, number] => { + if (!coordinates) { + return null; + } + switch (type) { + case dataElementTypes.COORDINATE: { + const lng: number = coordinates[0][1]; + const lat: number = coordinates[0][0]; + return [lng, lat]; + } + case dataElementTypes.POLYGON: + return Array<[number, number]>(coordinates.map(c => [c[1], c[0]])); + default: + return coordinates; + } +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/coordinate.validator.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/coordinate.validator.js new file mode 100644 index 0000000000..1d37df3233 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/coordinate.validator.js @@ -0,0 +1,32 @@ +// @flow + +type Location = { + longitude: number, + latitude: number, +}; + +function isNumValid(num) { + if (typeof num === 'number') { + return true; + } else if (typeof num === 'string') { + return num.match(/[^0-9.,-]+/) === null; + } + + return false; +} + +export const isValidCoordinate = (value: Location) => { + if (!value) { + return false; + } + + const { longitude, latitude } = value; + if (!isNumValid(latitude) || !isNumValid(longitude)) { + return false; + } + + const ld = parseInt(longitude, 10); + const lt = parseInt(latitude, 10); + + return ld >= -180 && ld <= 180 && lt >= -90 && lt <= 90; +}; From eb276a97a75c6cf46a227653113e74a76f44bace Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 8 Jun 2023 15:38:57 +0200 Subject: [PATCH 32/49] fix: delete polygon error --- .../MapCoordinates/MapCoordinatesModal.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.js index 2bc0974d75..43ea5ae930 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.js @@ -144,13 +144,11 @@ const MapCoordinatesModalPlain = ({ zoom={13} ref={(ref) => { if (ref?.leafletElement) { - setTimeout(() => { - ref?.leafletElement?.invalidateSize(); - if (ref.contextValue && type === dataElementTypes.POLYGON && coordinates) { - const { map } = ref.contextValue; - map?.fitBounds(coordinates); - } - }, 250); + ref.leafletElement.invalidateSize(); + if (ref.contextValue && type === dataElementTypes.POLYGON && coordinates) { + const { map } = ref.contextValue; + map?.fitBounds(coordinates); + } } }} className={classes.map} @@ -286,7 +284,7 @@ const MapCoordinatesModalPlain = ({ ) : (
- ) => { + switch (type) { + case dataElementTypes.COORDINATE: + return [coordinates[1], coordinates[0]]; + case dataElementTypes.POLYGON: + return coordinates[0].map(coord => [coord[1], coord[0]]); + default: + return coordinates; + } +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/helpers.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/helpers.js deleted file mode 100644 index 5a9895ac33..0000000000 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/helpers.js +++ /dev/null @@ -1,15 +0,0 @@ - -// @flow -import { dataElementTypes } from '../../../metaData'; - - -export const convertToClientCoordinates = (coordinates: any[], type: $Values) => { - switch (type) { - case dataElementTypes.COORDINATE: - return [coordinates[1], coordinates[0]]; - case dataElementTypes.POLYGON: - return coordinates[0].map(coord => [coord[1], coord[0]]); - default: - return coordinates; - } -}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/index.js index d3cf2eacb7..10afaaf1ce 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/index.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/index.js @@ -1,3 +1,3 @@ // @flow -export { MapCoordinates } from './MapCoordinates'; -export { MapCoordinatesModal } from './MapCoordinatesModal'; +export { MiniMap } from './MiniMap.container'; +export { MapCoordinatesModal } from './MapCoordinatesModal.container'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/mapCoordinates.types.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/mapCoordinates.types.js index 4af960a670..92dc214851 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/mapCoordinates.types.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/mapCoordinates.types.js @@ -1,6 +1,4 @@ // @flow - - type Feature = { type: string, properties: Object, @@ -15,7 +13,6 @@ export type FeatureCollection = { features: Array, }; - export type MiniMapProps = { coordinates: any, type: string, @@ -32,3 +29,10 @@ export type ModalProps = { onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, ...CssClasses } + +export type MapCoordinatesProps = {| + enrollment: Object, + onUpdate: (arg: Object) => void, + isOpenMap: boolean, + setOpenMap: (toggle: boolean) => void, +|}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js index 7b89d23a38..65ba85d187 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js @@ -20,7 +20,7 @@ import { convertValue as convertValueServerToClient } from '../../converters/ser import { convertValue as convertValueClientToView } from '../../converters/clientToView'; import { dataElementTypes } from '../../metaData'; import { Actions } from './Actions'; -import { MapCoordinates } from './MapCoordinates'; +import { MiniMap } from './MapCoordinates'; const styles = { enrollment: { @@ -144,7 +144,7 @@ export const WidgetEnrollmentPlain = ({ {enrollment.geometry && (
- { + const { program, error } = useProgram(enrollment.program); + const dataElementType = useMemo(() => { + if (!program) { + return undefined; + } + return program.featureType === 'POINT' ? dataElementTypes.COORDINATE : dataElementTypes.POLYGON; + }, [program]); + + if (error || enrollment.geometry || !program?.featureType || program.featureType === 'NONE') { + return { geometryType: undefined, label: undefined, dataElementType }; + } + + switch (dataElementType) { + case dataElementTypes.COORDINATE: + return { geometryType: 'Point', dataElementType, label: i18n.t('Add coordinates') }; + case dataElementTypes.POLYGON: + return { geometryType: 'Polygon', dataElementType, label: i18n.t('Add area') }; + default: + return { geometryType: undefined, dataElementType, label: undefined }; + } +}; From d8582d99727bd459133dda3a7041e6ec7cb1c55c Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 4 Jul 2023 11:37:15 +0200 Subject: [PATCH 37/49] fix: resetToDefaultValues and adjust height --- .../MapCoordinatesModal.component.js | 74 ++++++++++++------- .../MapCoordinates/mapCoordinates.types.js | 6 +- 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.component.js index 2fd357a073..17055bdd16 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.component.js @@ -1,5 +1,6 @@ // @flow import React, { useState, useMemo } from 'react'; +import classNames from 'classnames'; import i18n from '@dhis2/d2-i18n'; import { IconCross24, spacers, Modal, ModalTitle, ModalContent, ModalActions, Button, ButtonStrip } from '@dhis2/ui'; import log from 'loglevel'; @@ -22,7 +23,7 @@ const styles = (theme: Theme) => ({ }, map: { width: '100%', - height: 'calc(100vh - 320px)', + height: 'calc(100vh - 380px)', }, inputWrapper: { paddingTop: spacers.dp8, @@ -75,17 +76,18 @@ const MapCoordinatesModalPlain = ({ onSetCoordinates, }: ModalProps) => { const isPoint = useMemo(() => type === dataElementTypes.COORDINATE, [type]); + const isPolygon = useMemo(() => type === dataElementTypes.POLYGON, [type]); const [position, setPosition] = useState(isPoint ? defaultValues : null); - const [coordinates, setCoordinates] = useState(type === dataElementTypes.POLYGON ? defaultValues : null); + const [polygonArea, setPolygonArea] = useState(isPolygon ? defaultValues : null); const [center, setCenter] = useState(initialCenter); const [tempLat, setLat] = useState(position?.[0]); const [tempLng, setLng] = useState(position?.[1]); const [isEditing, setEditing] = useState(!(isPoint && defaultValues)); const [isValid, setValid] = useState(true); const hasErrors = useMemo(() => { - const changed = isPoint ? !isEqual(position, defaultValues) : !isEqual(coordinates, defaultValues); + const changed = isPoint ? !isEqual(position, defaultValues) : !isEqual(polygonArea, defaultValues); return changed && !isValid; - }, [position, coordinates, defaultValues, isPoint, isValid]); + }, [position, polygonArea, defaultValues, isPoint, isValid]); const title = useMemo(() => { switch (type) { case dataElementTypes.COORDINATE: @@ -98,6 +100,24 @@ const MapCoordinatesModalPlain = ({ } }, [type]); + const resetToDefaultValues = () => { + setCenter(initialCenter); + if (isPoint) { + setPosition(defaultValues); + if (defaultValues) { + setLat(defaultValues[0]); + setLng(defaultValues[1]); + setEditing(false); + } else { + setLat(null); + setLng(null); + } + } + if (isPolygon) { + setPolygonArea(defaultValues); + } + }; + const onHandleMapClicked = (mapCoordinates) => { if (isPoint && isEditing) { const { lat, lng } = mapCoordinates.latlng; @@ -111,7 +131,7 @@ const MapCoordinatesModalPlain = ({ const onMapPolygonCreated = (e: any) => { const polygonCoordinates = e.layer.toGeoJSON().geometry.coordinates[0].map(c => [c[1], c[0]]); - setCoordinates(polygonCoordinates); + setPolygonArea(polygonCoordinates); }; const onMapPolygonEdited = (e: any) => { @@ -119,11 +139,11 @@ const MapCoordinatesModalPlain = ({ .getLayers()[0] .toGeoJSON() .geometry.coordinates[0].map(c => [c[1], c[0]]); - setCoordinates(polygonCoordinates); + setPolygonArea(polygonCoordinates); }; const onMapPolygonDelete = () => { - setCoordinates(null); + setPolygonArea(null); }; const onSearch = (searchPosition: any) => { @@ -136,7 +156,7 @@ const MapCoordinatesModalPlain = ({ } }; - const getFeatureCollection = () => (Array.isArray(coordinates) ? coordsToFeatureCollection(coordinates) : null); + const getFeatureCollection = () => (Array.isArray(polygonArea) ? coordsToFeatureCollection(polygonArea) : null); const renderMap = () => ( { if (ref?.leafletElement) { ref.leafletElement.invalidateSize(); - if (ref.contextValue && type === dataElementTypes.POLYGON && coordinates) { + if (ref.contextValue && isPolygon && polygonArea) { const { map } = ref.contextValue; - map?.fitBounds(coordinates); + map?.fitBounds(polygonArea); } } }} @@ -167,7 +187,7 @@ const MapCoordinatesModalPlain = ({ url="http://{s}.tile.osm.org/{z}/{x}/{y}.png" attribution='© OpenStreetMap contributors' /> - {type === dataElementTypes.POLYGON && ( + {isPolygon && ( { onFeatureGroupReady(reactFGref, getFeatureCollection()); @@ -300,8 +320,8 @@ const MapCoordinatesModalPlain = ({ - ) : ( -
- ); - - const renderActions = () => ( - - - - - ); - - return ( - - {capitalizeFirstLetter(title)} - - {renderMap()} - {isPoint && ( -
-
-
{renderLatitude()}
-
{renderLongitude()}
- {renderFieldButton()} -
- {hasErrors && ( -
{i18n.t('Please provide valid coordinates')}
- )} -
- )} -
- {renderActions()} -
- ); -}; -export const MapCoordinatesModalComponent = withStyles(styles)(MapCoordinatesModalPlain); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/converters.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/converters.js deleted file mode 100644 index 0877dbecad..0000000000 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/converters.js +++ /dev/null @@ -1,33 +0,0 @@ -// @flow -import { dataElementTypes } from '../../../metaData'; - -export const convertToServerCoordinates = ( - coordinates?: Array<[number, number]> | null, - type: string, -): ?[number, number] | ?Array<[number, number]> | ?[number, number] => { - if (!coordinates) { - return null; - } - switch (type) { - case dataElementTypes.COORDINATE: { - const lng: number = coordinates[0][1]; - const lat: number = coordinates[0][0]; - return [lng, lat]; - } - case dataElementTypes.POLYGON: - return Array<[number, number]>(coordinates.map(c => [c[1], c[0]])); - default: - return coordinates; - } -}; - -export const convertToClientCoordinates = (coordinates: any[], type: $Values) => { - switch (type) { - case dataElementTypes.COORDINATE: - return [coordinates[1], coordinates[0]]; - case dataElementTypes.POLYGON: - return coordinates[0].map(coord => [coord[1], coord[0]]); - default: - return coordinates; - } -}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/index.js deleted file mode 100644 index 6c1371423b..0000000000 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -export { MiniMap } from './MiniMap.component'; -export { MapCoordinatesModal } from './MapCoordinatesModal.container'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js new file mode 100644 index 0000000000..7f1e2bbaca --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js @@ -0,0 +1,257 @@ +// @flow +import React, { useState, useMemo } from 'react'; +import classNames from 'classnames'; +import i18n from '@dhis2/d2-i18n'; +import { IconCross24, spacers, Modal, ModalTitle, ModalContent, ModalActions, Button, ButtonStrip } from '@dhis2/ui'; +import { ReactLeafletSearch } from 'react-leaflet-search-unpolyfilled'; +import { Map, TileLayer, Marker, withLeaflet } from 'react-leaflet'; +import { withStyles } from '@material-ui/core'; +import type { CoordinatesProps } from './MapModal.types'; +import { CoordinateInput } from '../../../../capture-ui/internal/CoordinateInput/CoordinateInput.component'; +import { isEqual } from '../../../utils/valueEqualityChecker'; +import { isValidCoordinate } from './coordinate.validator'; +import { convertCoordinatesToServer } from './converters'; + +const styles = (theme: Theme) => ({ + modalContent: { + width: '100%', + }, + map: { + width: '100%', + height: 'calc(100vh - 380px)', + }, + inputWrapper: { + paddingTop: spacers.dp8, + display: 'flex', + }, + inputContent: { + flexGrow: 1, + }, + fieldButton: { + height: '42px !important', + width: 42, + borderRadius: '0 !important', + }, + errorContainer: { + backgroundColor: theme.palette.error.lighter, + color: theme.palette.error.main, + }, +}); + +const WrappedLeafletSearch = withLeaflet(ReactLeafletSearch); + +const CoordinatesPlain = ({ + classes, + center: initialCenter, + isOpen, + setOpen, + defaultValues, + onSetCoordinates, +}: CoordinatesProps) => { + const [position, setPosition] = useState(defaultValues); + const [center, setCenter] = useState(initialCenter); + const [tempLatitude, setTempLatitude] = useState(position?.[0]); + const [tempLongitude, setTempLongitude] = useState(position?.[1]); + const [isEditing, setEditing] = useState(!defaultValues); + const [isValid, setValid] = useState(true); + const hasErrors = useMemo(() => { + const changed = !isEqual(position, defaultValues); + return changed && !isValid; + }, [position, defaultValues, isValid]); + + const resetToDefaultValues = () => { + setCenter(initialCenter); + setPosition(defaultValues); + if (defaultValues) { + setTempLatitude(defaultValues[0]); + setTempLongitude(defaultValues[1]); + setEditing(false); + } else { + setTempLatitude(null); + setTempLongitude(null); + } + }; + + const onHandleMapClicked = (mapCoordinates) => { + if (isEditing) { + const { lat, lng } = mapCoordinates.latlng; + const newPosition: [number, number] = [lat, lng]; + setValid(true); + setPosition(newPosition); + setTempLatitude(lat); + setTempLongitude(lng); + } + }; + + const onSearch = (searchPosition: any) => { + setCenter(searchPosition); + setValid(true); + setTempLatitude(searchPosition[0]); + setTempLongitude(searchPosition[1]); + setPosition(searchPosition); + }; + + const renderMap = () => ( + { + if (ref?.leafletElement) { + ref.leafletElement.invalidateSize(); + } + }} + className={classes.map} + onClick={onHandleMapClicked} + > + + + {position && } + + ); + + const renderLatitude = () => ( + { + if (!latitude) { + return; + } + const longitude = tempLongitude || (position?.[1] ? position[1] : undefined); + if (!longitude) { + return; + } + if (!isValidCoordinate({ longitude: Number(longitude), latitude })) { + setPosition(null); + setValid(false); + return; + } + setValid(true); + const newPosition = [Number(latitude), longitude]; + setPosition(newPosition); + setCenter(newPosition); + }} + onChange={(latitude) => { + setTempLatitude(latitude); + }} + /> + ); + + const renderLongitude = () => ( + { + if (!longitude) { + return; + } + const latitude = tempLatitude || (position?.[1] ? position[0] : undefined); + if (!latitude) { + return; + } + if (!isValidCoordinate({ longitude, latitude: Number(latitude) })) { + setPosition(null); + setValid(false); + return; + } + setValid(true); + const newPosition = [latitude, Number(longitude)]; + setPosition(newPosition); + setCenter(newPosition); + }} + onChange={(longitude) => { + setTempLongitude(longitude); + }} + /> + ); + + const renderFieldButton = () => ( +
+ {!isEditing ? ( + + ) : ( +
+ ); + + const renderActions = () => ( + + + + + ); + + return ( + + {i18n.t('Coordinates')} + + {renderMap()} +
+
+
{renderLatitude()}
+
{renderLongitude()}
+ {renderFieldButton()} +
+ {hasErrors && ( +
{i18n.t('Please provide valid coordinates')}
+ )} +
+
+ {renderActions()} +
+ ); +}; +export const Coordinates = withStyles(styles)(CoordinatesPlain); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.component.js new file mode 100644 index 0000000000..20120b8b50 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.component.js @@ -0,0 +1,29 @@ +// @flow +import React from 'react'; +import { dataElementTypes } from '../../../metaData'; +import type { ModalProps } from './MapModal.types'; +import { Coordinates } from './Coordinates'; +import { Polygon } from './Polygon'; + +export const MapModal = ({ type, center, isOpen, setOpen, onSetCoordinates, defaultValues }: ModalProps) => ( + <> + {type === dataElementTypes.COORDINATE && ( + + )} + {type === dataElementTypes.POLYGON && ( + + )} + +); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.container.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js similarity index 55% rename from src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.container.js rename to src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js index 9eeb935112..1f8389d79b 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MapCoordinatesModal.container.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js @@ -1,34 +1,39 @@ // @flow import React from 'react'; -import { MapCoordinatesModalComponent } from './MapCoordinatesModal.component'; import { useGeometry } from '../hooks/useGeometry'; -import type { MapCoordinatesProps } from './mapCoordinates.types'; +import type { MapModalProps } from './MapModal.types'; +import { MapModal as MapModalComponent } from './MapModal.component'; const DEFAULT_CENTER = [51.505, -0.09]; -export const MapCoordinatesModal = ({ enrollment, onUpdate, isOpenMap, setOpenMap }: MapCoordinatesProps) => { - const { geometryType, dataElementType } = useGeometry(enrollment); - if (!geometryType) { - return null; - } +export const MapModal = ({ + enrollment, + onUpdate, + isOpenMap, + setOpenMap, + defaultValues, + center, +}: MapModalProps) => { + const { geometryType, dataElementType } = useGeometry(enrollment); - const onSetCoordinates = (coord) => { - if (!coord) { + const onSetCoordinates = (coordinates) => { + if (enrollment && !coordinates) { const copyEnrollment = { ...enrollment }; delete copyEnrollment.geometry; onUpdate(copyEnrollment); return; } - onUpdate({ ...enrollment, geometry: { type: geometryType, coordinates: coord } }); + onUpdate({ ...enrollment, geometry: { type: geometryType, coordinates } }); }; return ( - ); }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/mapCoordinates.types.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.types.js similarity index 52% rename from src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/mapCoordinates.types.js rename to src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.types.js index 61d3f3e276..017b6df7d3 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/mapCoordinates.types.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.types.js @@ -6,7 +6,7 @@ type Feature = { properties: Object, geometry: { type: string, - coordinates: Array>>, + coordinates: Array | number>>, }, } @@ -15,26 +15,37 @@ export type FeatureCollection = { features: Array, }; -export type MiniMapProps = { - coordinates: any, +export type ModalProps = { + center: ?[number, number], + isOpen: boolean, type: typeof dataElementTypes.COORDINATE | typeof dataElementTypes.POLYGON, + defaultValues?: ?Array> | ?[number, number], + setOpen: (open: boolean) => void, onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, - ...CssClasses } -export type ModalProps = { +export type PolygonProps = { center: ?[number, number], isOpen: boolean, - type: typeof dataElementTypes.COORDINATE | typeof dataElementTypes.POLYGON, - defaultValues?: ?any, setOpen: (open: boolean) => void, onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, - ...CssClasses + defaultValues?: ?Array>, + ...CssClasses, } -export type MapCoordinatesProps = {| +export type CoordinatesProps = { + center: ?[number, number], + isOpen: boolean, + setOpen: (open: boolean) => void, + onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, + defaultValues?: ?[number, number], + ...CssClasses, +} +export type MapModalProps = {| + center?: ?[number, number], enrollment: Object, onUpdate: (arg: Object) => void, isOpenMap: boolean, setOpenMap: (toggle: boolean) => void, + defaultValues?: ?Array> | ?[number, number], |}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js new file mode 100644 index 0000000000..4dfb2026a0 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js @@ -0,0 +1,207 @@ +// @flow +import React, { useState, useMemo } from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { Modal, ModalTitle, ModalContent, ModalActions, Button, ButtonStrip } from '@dhis2/ui'; +import { ReactLeafletSearch } from 'react-leaflet-search-unpolyfilled'; +import { Map, TileLayer, FeatureGroup, withLeaflet } from 'react-leaflet'; +import { EditControl } from 'react-leaflet-draw'; +import L from 'leaflet'; +import { withStyles } from '@material-ui/core'; +import type { PolygonProps, FeatureCollection } from './MapModal.types'; +import { isEqual } from '../../../utils/valueEqualityChecker'; +import { convertPolygonToServer } from './converters'; + +const styles = () => ({ + modalContent: { + width: '100%', + }, + map: { + width: '100%', + height: 'calc(100vh - 380px)', + }, +}); + +const coordsToFeatureCollection = (inputCoordinates: any): ?FeatureCollection => { + if (!inputCoordinates) { + return null; + } + const list = inputCoordinates[0].length > 2 ? inputCoordinates[0] : inputCoordinates.map(c => [c[1], c[0]]); + + return { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: {}, + geometry: { + type: 'Polygon', + coordinates: [list], + }, + }, + ], + }; +}; + +const WrappedLeafletSearch = withLeaflet(ReactLeafletSearch); + +const PolygonPlain = ({ + classes, + center: initialCenter, + isOpen, + setOpen, + defaultValues, + onSetCoordinates, +}: PolygonProps) => { + const [polygonArea, setPolygonArea] = useState(defaultValues); + const [isEditing, setIsEditing] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const [isDrawing, setIsDrawing] = useState(false); + const [center, setCenter] = useState(initialCenter); + const disabledCancel = useMemo(() => isEditing || isDeleting || isDrawing, [isEditing, isDeleting, isDrawing]); + + const disabledSetArea = useMemo(() => { + if (disabledCancel) { + return true; + } + const changed = !isEqual(polygonArea, defaultValues); + return !changed; + }, [polygonArea, defaultValues, disabledCancel]); + + const resetToDefaultValues = () => { + setCenter(initialCenter); + setPolygonArea(defaultValues); + }; + + const onMapPolygonCreated = (e: any) => { + const polygonCoordinates = e.layer.toGeoJSON().geometry.coordinates[0].map(c => [c[1], c[0]]); + setPolygonArea(polygonCoordinates); + }; + + const onMapPolygonEdited = (e: any) => { + const polygonCoordinates = e.layers + .getLayers()[0] + .toGeoJSON() + .geometry.coordinates[0].map(c => [c[1], c[0]]); + setPolygonArea(polygonCoordinates); + }; + + const onMapPolygonDelete = () => { + setPolygonArea(null); + }; + + const onSearch = (searchPosition: any) => { + setCenter(searchPosition); + }; + + const getFeatureCollection = () => (Array.isArray(polygonArea) ? coordsToFeatureCollection(polygonArea) : null); + + const renderMap = () => ( + { + if (ref?.leafletElement) { + ref.leafletElement.invalidateSize(); + if (ref.contextValue && polygonArea) { + const { map } = ref.contextValue; + map?.fitBounds(polygonArea); + } + } + }} + className={classes.map} + > + + + { + onFeatureGroupReady(reactFGref, getFeatureCollection()); + }} + > + setIsEditing(true)} + onEditStop={() => setIsEditing(false)} + onDeleteStart={() => setIsDeleting(true)} + onDeleteStop={() => setIsDeleting(false)} + onDrawStart={() => setIsDrawing(true)} + onDrawStop={() => setIsDrawing(false)} + draw={{ + rectangle: false, + polyline: false, + circle: false, + marker: false, + circlemarker: false, + }} + /> + + + ); + + const onFeatureGroupReady = (reactFGref: any, featureCollection: ?FeatureCollection) => { + if (featureCollection) { + const leafletGeoJSON = new L.GeoJSON(featureCollection); + if (reactFGref) { + const leafletFG = reactFGref.leafletElement; + leafletFG.clearLayers(); + + leafletGeoJSON.eachLayer((layer) => { + leafletFG.addLayer(layer); + }); + } + } else if (reactFGref) { + const leafletFG = reactFGref.leafletElement; + leafletFG.clearLayers(); + } + }; + + const renderActions = () => ( + + + + + ); + + return ( + + {i18n.t('Area')} + {renderMap()} + {renderActions()} + + ); +}; + +export const Polygon = withStyles(styles)(PolygonPlain); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/converters.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/converters.js new file mode 100644 index 0000000000..a3def47da2 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/converters.js @@ -0,0 +1,18 @@ +// @flow + +export const convertPolygonToServer = (coordinates?: Array> | null): ?Array<[number, number]> => { + if (!coordinates) { + return null; + } + return Array<[number, number]>(coordinates.map(c => (c ? [c[1], c[0]] : null))); +}; + +export const convertCoordinatesToServer = (coordinates?: Array | null): ?[number, number] => { + if (!coordinates || !coordinates[0]) { + return null; + } + + const lng: number = coordinates[0][1]; + const lat: number = coordinates[0][0]; + return [lng, lat]; +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/coordinate.validator.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/coordinate.validator.js similarity index 100% rename from src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/coordinate.validator.js rename to src/core_modules/capture-core/components/WidgetEnrollment/MapModal/coordinate.validator.js diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/index.js new file mode 100644 index 0000000000..41266d1ca4 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/index.js @@ -0,0 +1,3 @@ +// @flow +export { MapModal } from './MapModal.container'; + diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MiniMap.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.component.js similarity index 59% rename from src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MiniMap.component.js rename to src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.component.js index 1d6fa9013b..8e555e7836 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapCoordinates/MiniMap.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.component.js @@ -3,9 +3,10 @@ import React, { useState } from 'react'; import { Map, TileLayer, Marker, Polygon } from 'react-leaflet'; import { withStyles } from '@material-ui/core'; import { dataElementTypes } from '../../../metaData'; -import { MapCoordinatesModalComponent } from './MapCoordinatesModal.component'; -import type { MiniMapProps } from './mapCoordinates.types'; +import { MapModal } from '../MapModal'; +import type { MiniMapProps } from './MiniMap.types'; import { convertToClientCoordinates } from './converters'; +import { useUpdateEnrollment } from '../dataMutation/dataMutation'; const styles = () => ({ mapContainer: { @@ -18,12 +19,21 @@ const styles = () => ({ }, }); -const MiniMapPlain = ({ coordinates, type, classes, onSetCoordinates }: MiniMapProps) => { - const [isModalOpen, setModalOpen] = useState(false); - const clientValues = convertToClientCoordinates(coordinates, type); - const center = type === dataElementTypes.COORDINATE ? clientValues : clientValues[0]; +const MiniMapPlain = ({ + coordinates, + geometryType, + enrollment, + refetchEnrollment, + refetchTEI, + onError, + classes, +}: MiniMapProps) => { + const [isOpenMap, setOpenMap] = useState(false); + const { updateMutation } = useUpdateEnrollment(refetchEnrollment, refetchTEI, onError); + const clientValues = convertToClientCoordinates(coordinates, geometryType); + const center = geometryType === dataElementTypes.COORDINATE ? clientValues : clientValues[0]; const onMapReady = (mapRef) => { - if (mapRef?.contextValue && type === dataElementTypes.POLYGON) { + if (mapRef?.contextValue && geometryType === dataElementTypes.POLYGON) { const { map } = mapRef.contextValue; map?.fitBounds(clientValues); } @@ -43,24 +53,24 @@ const MiniMapPlain = ({ coordinates, type, classes, onSetCoordinates }: MiniMapP attributionControl={false} key="minimap" onClick={() => { - setModalOpen(true); + setOpenMap(true); }} > - {type === dataElementTypes.COORDINATE && } - {type === dataElementTypes.POLYGON && } + {geometryType === dataElementTypes.COORDINATE && } + {geometryType === dataElementTypes.POLYGON && } - ); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.types.js b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.types.js new file mode 100644 index 0000000000..b905c28783 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.types.js @@ -0,0 +1,13 @@ +// @flow +import type { QueryRefetchFunction } from '@dhis2/app-runtime'; +import { dataElementTypes } from '../../../metaData'; + +export type MiniMapProps = { + coordinates: Array>, + enrollment: any, + refetchEnrollment: QueryRefetchFunction, + refetchTEI: QueryRefetchFunction, + onError?: (message: string) => void, + geometryType: typeof dataElementTypes.COORDINATE | typeof dataElementTypes.POLYGON, + ...CssClasses +} diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/converters.js b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/converters.js new file mode 100644 index 0000000000..498738ef13 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/converters.js @@ -0,0 +1,10 @@ +// @flow +import { dataElementTypes } from '../../../metaData'; + +export const convertToClientCoordinates = (coordinates: any[], type: $Values) => { + if (type === dataElementTypes.COORDINATE) { + return [coordinates[1], coordinates[0]]; + } + + return coordinates[0].map(coord => [coord[1], coord[0]]); +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/index.js new file mode 100644 index 0000000000..c1e7fa0e6f --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/index.js @@ -0,0 +1,2 @@ +// @flow +export { MiniMap } from './MiniMap.component'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js index 65ba85d187..1e526581c8 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js @@ -20,7 +20,7 @@ import { convertValue as convertValueServerToClient } from '../../converters/ser import { convertValue as convertValueClientToView } from '../../converters/clientToView'; import { dataElementTypes } from '../../metaData'; import { Actions } from './Actions'; -import { MiniMap } from './MapCoordinates'; +import { MiniMap } from './MiniMap'; const styles = { enrollment: { @@ -58,7 +58,6 @@ export const WidgetEnrollmentPlain = ({ onDelete, onAddNew, onError, - onSetCoordinates, onSuccess, }: PlainProps) => { const [open, setOpenStatus] = useState(true); @@ -146,8 +145,11 @@ export const WidgetEnrollmentPlain = ({
)} diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js index 0d45d71751..8660f704f4 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.container.js @@ -1,5 +1,5 @@ // @flow -import React, { useCallback } from 'react'; +import React from 'react'; import { errorCreator } from 'capture-core-utils'; import log from 'loglevel'; import { WidgetEnrollment as WidgetEnrollmentComponent } from './WidgetEnrollment.component'; @@ -9,7 +9,6 @@ import { useEnrollment } from './hooks/useEnrollment'; import { useProgram } from './hooks/useProgram'; import type { Props } from './enrollment.types'; import { plainStatus } from './constants/status.const'; -import { useUpdateEnrollment } from './dataMutation/dataMutation'; export const WidgetEnrollment = ({ teiId, enrollmentId, programId, onDelete, onAddNew, onError, onSuccess }: Props) => { const { error: errorEnrollment, enrollment, refetch: refetchEnrollment } = useEnrollment(enrollmentId); @@ -26,24 +25,10 @@ export const WidgetEnrollment = ({ teiId, enrollmentId, programId, onDelete, onA .every(item => item.status !== plainStatus.ACTIVE); const error = errorEnrollment || errorProgram || errorOwnerOrgUnit || errorOrgUnit; - const { updateMutation } = useUpdateEnrollment(refetchEnrollment, refetchTEI, onError); - if (error) { log.error(errorCreator('Enrollment widget could not be loaded')({ error })); } - const handleSetCoordinates = useCallback((coordinates) => { - if (enrollment) { - if (!coordinates) { - const copyEnrollment = { ...enrollment }; - delete copyEnrollment.geometry; - updateMutation(copyEnrollment); - return; - } - updateMutation({ ...enrollment, geometry: { ...enrollment.geometry, coordinates } }); - } - }, [enrollment, updateMutation]); - return ( void, onAddNew: () => void, onError?: (message: string) => void, - onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, onSuccess?: () => void, ...CssClasses, |}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useGeometry.js b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useGeometry.js index 791cabfe42..3569c211db 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useGeometry.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useGeometry.js @@ -1,28 +1,39 @@ // @flow -import { useMemo } from 'react'; import i18n from '@dhis2/d2-i18n'; import { dataElementTypes } from '../../../metaData'; import { useProgram } from './useProgram'; -export const useGeometry = (enrollment: {program: string, geometry: string}) => { - const { program, error } = useProgram(enrollment.program); - const dataElementType = useMemo(() => { - if (!program) { - return undefined; - } - return program.featureType === 'POINT' ? dataElementTypes.COORDINATE : dataElementTypes.POLYGON; - }, [program]); +export const useGeometry = (enrollment: { program: string }) => { + const { + program: { featureType }, + } = useProgram(enrollment.program); - if (error || enrollment.geometry || !program?.featureType || program.featureType === 'NONE') { - return { geometryType: undefined, label: undefined, dataElementType }; + if (featureType === 'POINT') { + return { + geometryType: 'Point', + dataElementType: dataElementTypes.COORDINATE, + }; } - switch (dataElementType) { - case dataElementTypes.COORDINATE: - return { geometryType: 'Point', dataElementType, label: i18n.t('Add coordinates') }; - case dataElementTypes.POLYGON: - return { geometryType: 'Polygon', dataElementType, label: i18n.t('Add area') }; - default: - return { geometryType: undefined, dataElementType, label: undefined }; + return { + geometryType: 'Polygon', + dataElementType: dataElementTypes.POLYGON, + }; +}; + +export const useGeometryLabel = (enrollment: { program: string, geometry: { type: string } }) => { + const { + program: { featureType }, + error, + } = useProgram(enrollment.program); + + if (error || !featureType || !['POINT', 'POLYGON'].includes(featureType) || enrollment.geometry?.type) { + return undefined; } + + if (featureType === 'POINT') { + return i18n.t('Add coordinates'); + } + + return i18n.t('Add area'); }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useProgram.js b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useProgram.js index ea4459185f..bdeddc98ad 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useProgram.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useProgram.js @@ -18,5 +18,5 @@ export const useProgram = (programId: string) => { [programId], ), ); - return { error, program: !loading && data?.program }; + return { error, loading, program: data?.program }; }; From 575bca7467df3b3ea6216202a362259dcdc1246e Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 16 Aug 2023 11:19:26 +0200 Subject: [PATCH 40/49] chore: handle numbers with comma --- i18n/en.pot | 10 +++++----- .../WidgetEnrollment/MapModal/Coordinates.js | 4 ++-- .../components/WidgetEnrollment/MapModal/Polygon.js | 5 ++++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 535e66dd51..e300447a38 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2023-08-09T09:23:04.450Z\n" -"PO-Revision-Date: 2023-08-09T09:23:04.450Z\n" +"POT-Creation-Date: 2023-08-16T09:19:28.859Z\n" +"PO-Revision-Date: 2023-08-16T09:19:28.859Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -1176,6 +1176,9 @@ msgstr "Set coordinates" msgid "Coordinates" msgstr "Coordinates" +msgid "Delete polygon" +msgstr "Delete polygon" + msgid "Set area" msgstr "Set area" @@ -1545,9 +1548,6 @@ msgstr "To date" msgid "To time" msgstr "To time" -msgid "Delete polygon" -msgstr "Delete polygon" - msgid "Area on map saved" msgstr "Area on map saved" diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js index 7f1e2bbaca..508d5117e9 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js @@ -134,7 +134,7 @@ const CoordinatesPlain = ({ if (!longitude) { return; } - if (!isValidCoordinate({ longitude: Number(longitude), latitude })) { + if (!isValidCoordinate({ longitude: Number(longitude), latitude: Number(latitude) })) { setPosition(null); setValid(false); return; @@ -164,7 +164,7 @@ const CoordinatesPlain = ({ if (!latitude) { return; } - if (!isValidCoordinate({ longitude, latitude: Number(latitude) })) { + if (!isValidCoordinate({ longitude: Number(longitude), latitude: Number(latitude) })) { setPosition(null); setValid(false); return; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js index 4dfb2026a0..98a91da912 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js @@ -152,6 +152,9 @@ const PolygonPlain = ({ ); const onFeatureGroupReady = (reactFGref: any, featureCollection: ?FeatureCollection) => { + if (!reactFGref) { + return; + } if (featureCollection) { const leafletGeoJSON = new L.GeoJSON(featureCollection); if (reactFGref) { @@ -162,7 +165,7 @@ const PolygonPlain = ({ leafletFG.addLayer(layer); }); } - } else if (reactFGref) { + } else { const leafletFG = reactFGref.leafletElement; leafletFG.clearLayers(); } From 0ebc46460e4c27fe5222c2cb9488cca48ef5a84c Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 17 Aug 2023 09:13:41 +0200 Subject: [PATCH 41/49] fix: the map buttons are still displayed after closing the modal --- .../Actions/Actions.component.js | 3 +- .../WidgetEnrollment/MapModal/Coordinates.js | 3 +- .../MapModal/MapModal.component.js | 4 +-- .../MapModal/MapModal.container.js | 2 -- .../MapModal/MapModal.types.js | 4 --- .../WidgetEnrollment/MapModal/Polygon.js | 30 +++++-------------- .../MiniMap/MiniMap.component.js | 17 ++++++----- 7 files changed, 19 insertions(+), 44 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js index d826e19617..0c56de8cb1 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js @@ -105,11 +105,10 @@ export const ActionsPlain = ({ {i18n.t('We are processing your request.')} )} - {setOpenMap && } ); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js index 508d5117e9..d19cf18c68 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates.js @@ -43,7 +43,6 @@ const WrappedLeafletSearch = withLeaflet(ReactLeafletSearch); const CoordinatesPlain = ({ classes, center: initialCenter, - isOpen, setOpen, defaultValues, onSetCoordinates, @@ -235,7 +234,7 @@ const CoordinatesPlain = ({ ); return ( - + {i18n.t('Coordinates')} {renderMap()} diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.component.js index 20120b8b50..81719934a4 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.component.js @@ -5,12 +5,11 @@ import type { ModalProps } from './MapModal.types'; import { Coordinates } from './Coordinates'; import { Polygon } from './Polygon'; -export const MapModal = ({ type, center, isOpen, setOpen, onSetCoordinates, defaultValues }: ModalProps) => ( +export const MapModal = ({ type, center, setOpen, onSetCoordinates, defaultValues }: ModalProps) => ( <> {type === dataElementTypes.COORDINATE && ( > | ?[number, number], setOpen: (open: boolean) => void, @@ -26,7 +25,6 @@ export type ModalProps = { export type PolygonProps = { center: ?[number, number], - isOpen: boolean, setOpen: (open: boolean) => void, onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, defaultValues?: ?Array>, @@ -35,7 +33,6 @@ export type PolygonProps = { export type CoordinatesProps = { center: ?[number, number], - isOpen: boolean, setOpen: (open: boolean) => void, onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, defaultValues?: ?[number, number], @@ -45,7 +42,6 @@ export type MapModalProps = {| center?: ?[number, number], enrollment: Object, onUpdate: (arg: Object) => void, - isOpenMap: boolean, setOpenMap: (toggle: boolean) => void, defaultValues?: ?Array> | ?[number, number], |}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js index 98a91da912..044d56a8d0 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon.js @@ -1,5 +1,5 @@ // @flow -import React, { useState, useMemo } from 'react'; +import React, { useState } from 'react'; import i18n from '@dhis2/d2-i18n'; import { Modal, ModalTitle, ModalContent, ModalActions, Button, ButtonStrip } from '@dhis2/ui'; import { ReactLeafletSearch } from 'react-leaflet-search-unpolyfilled'; @@ -8,8 +8,8 @@ import { EditControl } from 'react-leaflet-draw'; import L from 'leaflet'; import { withStyles } from '@material-ui/core'; import type { PolygonProps, FeatureCollection } from './MapModal.types'; -import { isEqual } from '../../../utils/valueEqualityChecker'; import { convertPolygonToServer } from './converters'; +import { DeleteControl } from './DeleteControl.component'; const styles = () => ({ modalContent: { @@ -47,25 +47,13 @@ const WrappedLeafletSearch = withLeaflet(ReactLeafletSearch); const PolygonPlain = ({ classes, center: initialCenter, - isOpen, setOpen, defaultValues, onSetCoordinates, }: PolygonProps) => { const [polygonArea, setPolygonArea] = useState(defaultValues); - const [isEditing, setIsEditing] = useState(false); - const [isDeleting, setIsDeleting] = useState(false); - const [isDrawing, setIsDrawing] = useState(false); const [center, setCenter] = useState(initialCenter); - const disabledCancel = useMemo(() => isEditing || isDeleting || isDrawing, [isEditing, isDeleting, isDrawing]); - const disabledSetArea = useMemo(() => { - if (disabledCancel) { - return true; - } - const changed = !isEqual(polygonArea, defaultValues); - return !changed; - }, [polygonArea, defaultValues, disabledCancel]); const resetToDefaultValues = () => { setCenter(initialCenter); @@ -133,12 +121,6 @@ const PolygonPlain = ({ onEdited={onMapPolygonEdited} onCreated={onMapPolygonCreated} onDeleted={onMapPolygonDelete} - onEditStart={() => setIsEditing(true)} - onEditStop={() => setIsEditing(false)} - onDeleteStart={() => setIsDeleting(true)} - onDeleteStop={() => setIsDeleting(false)} - onDrawStart={() => setIsDrawing(true)} - onDrawStop={() => setIsDrawing(false)} draw={{ rectangle: false, polyline: false, @@ -146,7 +128,11 @@ const PolygonPlain = ({ marker: false, circlemarker: false, }} + edit={{ + remove: false, + }} /> + ); @@ -174,7 +160,6 @@ const PolygonPlain = ({ const renderActions = () => ( - + {!drawingState && ( + + )} + {drawingState && ( + <> + + + + + + )} ); From da911ba7b08e17568b1317cf2d2f5996fc8441ab Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 23 Oct 2023 10:45:20 +0100 Subject: [PATCH 46/49] feat: return to previous state when canceling --- .../MapModal/Polygon/Polygon.component.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/Polygon.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/Polygon.component.js index 501671680a..d1c2eeefe1 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/Polygon.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/Polygon.component.js @@ -1,5 +1,5 @@ // @flow -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import i18n from '@dhis2/d2-i18n'; import { Modal, ModalTitle, ModalContent, ModalActions, Button, ButtonStrip } from '@dhis2/ui'; import { ReactLeafletSearch } from 'react-leaflet-search-unpolyfilled'; @@ -63,6 +63,7 @@ const PolygonPlain = ({ const [polygonArea, setPolygonArea] = useState(defaultValues); const [center, setCenter] = useState(initialCenter); const [drawingState, setDrawingState] = useState(undefined); + const prevDrawingState = useRef(undefined); const resetToDefaultValues = () => { setCenter(initialCenter); @@ -72,11 +73,14 @@ const PolygonPlain = ({ const onMapPolygonCreated = (e: any) => { const polygonCoordinates = e.layer.toGeoJSON().geometry.coordinates[0].map(c => [c[1], c[0]]); setPolygonArea(polygonCoordinates); + setDrawingState(drawing.FINISHED); + prevDrawingState.current = drawing.FINISHED; }; const onMapPolygonDelete = () => { setPolygonArea(null); setDrawingState(drawing.FINISHED); + prevDrawingState.current = drawing.FINISHED; }; const onSearch = (searchPosition: any) => { @@ -123,7 +127,7 @@ const PolygonPlain = ({ onCreated={onMapPolygonCreated} onDeleted={onMapPolygonDelete} onDrawStart={() => setDrawingState(drawing.STARTED)} - onDrawStop={() => setDrawingState(drawing.FINISHED)} + onDrawStop={() => setDrawingState(prevDrawingState.current)} draw={{ rectangle: false, polyline: false, @@ -136,7 +140,10 @@ const PolygonPlain = ({ edit: false, }} /> - + ); From cd99514a976497e101409ff3d2ed5c1a9168448f Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 13 Nov 2023 13:58:27 +0100 Subject: [PATCH 47/49] fix: update enrollment mutation callback --- .../MapModal/MapModal.container.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js index e923ceb423..b39d141b1d 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js @@ -1,5 +1,5 @@ // @flow -import React from 'react'; +import React, { useCallback } from 'react'; import { useGeometry } from '../hooks/useGeometry'; import type { MapModalProps } from './MapModal.types'; import { MapModal as MapModalComponent } from './MapModal.component'; @@ -15,15 +15,10 @@ export const MapModal = ({ }: MapModalProps) => { const { geometryType, dataElementType } = useGeometry(enrollment); - const onSetCoordinates = (coordinates) => { - if (enrollment && !coordinates) { - const copyEnrollment = { ...enrollment }; - delete copyEnrollment.geometry; - onUpdate(copyEnrollment); - return; - } - onUpdate({ ...enrollment, geometry: { type: geometryType, coordinates } }); - }; + const onSetCoordinates = useCallback((coordinates) => { + const geometry = coordinates ? { type: geometryType, coordinates } : null; + onUpdate({ ...enrollment, geometry }); + }, [enrollment, geometryType, onUpdate]); return ( Date: Thu, 30 Nov 2023 12:33:32 +0100 Subject: [PATCH 48/49] feat: fetch the org unit only when the map is opened using useApiMetadataQuery --- .../EnrollmentDataEntry.component.js | 8 +-- ...llmentWithFirstStageDataEntry.component.js | 2 + .../DataEntry/DataEntry.component.js | 7 +-- .../components/DataEntry/index.js | 1 - .../components/DataEntry/withCenterPoint.js | 51 ----------------- .../CoordinateField.component.js | 3 +- .../PolygonField/PolygonField.component.js | 3 +- .../components/FormFields/New/HOC/index.js | 2 + .../FormFields/New/HOC/withCenterPoint.js | 56 +++++++++++++++++++ .../components/FormFields/New/index.js | 1 + .../Actions/Actions.component.js | 3 - .../MapModal/MapModal.container.js | 4 +- .../WidgetEnrollment/MapModal/hooks/index.js | 2 + .../MapModal/hooks/useCenterPoint.js | 54 ++++++++++++++++++ .../WidgetEnrollment/hooks/useCenterPoint.js | 54 ------------------ .../DataEntry/DataEntry.component.js | 6 +- .../EditEventDataEntry.component.js | 7 +-- .../DataEntry/DataEntry.component.js | 4 +- .../DataEntry/DataEntry.container.js | 5 +- .../DataEntry/dataEntry.types.js | 1 + .../WidgetProfile/DataEntry/hooks/index.js | 1 - .../DataEntry/hooks/useCenterPoint.js | 54 ------------------ .../CoordinateField.component.js | 13 ++--- .../PolygonField/PolygonField.component.js | 9 +-- 24 files changed, 148 insertions(+), 203 deletions(-) delete mode 100644 src/core_modules/capture-core/components/DataEntry/withCenterPoint.js create mode 100644 src/core_modules/capture-core/components/FormFields/New/HOC/index.js create mode 100644 src/core_modules/capture-core/components/FormFields/New/HOC/withCenterPoint.js create mode 100644 src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/index.js create mode 100644 src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/useCenterPoint.js delete mode 100644 src/core_modules/capture-core/components/WidgetEnrollment/hooks/useCenterPoint.js delete mode 100644 src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useCenterPoint.js diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js index 9f0dda9394..7fc5b5c587 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js @@ -33,7 +33,6 @@ import { getIncidentDateValidatorContainer, } from './fieldValidators'; import { sectionKeysForEnrollmentDataEntry } from './constants/sectionKeys.const'; -import { withCenterPoint } from '../../DataEntry/withCenterPoint'; import { type Enrollment, ProgramStage, RenderFoundation, getProgramThrowIfNotFound } from '../../../metaData'; import { EnrollmentWithFirstStageDataEntry } from './EnrollmentWithFirstStageDataEntry'; import { @@ -240,7 +239,7 @@ const getGeometrySettings = () => ({ dialogLabel: i18n.t('Area'), required: false, orientation: getOrientation(props.formHorizontal), - center: props.center, + orgUnit: props.orgUnit, }); } @@ -251,7 +250,7 @@ const getGeometrySettings = () => ({ required: false, orientation: getOrientation(props.formHorizontal), shrinkDisabled: props.formHorizontal, - center: props.center, + orgUnit: props.orgUnit, }); }, getPropName: () => 'geometry', @@ -379,8 +378,7 @@ const AOCFieldBuilderHOC = withAOCFieldBuilder(getAOCSettingsFn())( getCategoryOptionsSettingsFn(), )(FinalEnrollmentDataEntry)); const LocationHOC = withDataEntryFieldIfApplicable(getGeometrySettings())(AOCFieldBuilderHOC); -const CenterPointHOC = withCenterPoint()(LocationHOC); -const IncidentDateFieldHOC = withDataEntryFieldIfApplicable(getIncidentDateSettings())(CenterPointHOC); +const IncidentDateFieldHOC = withDataEntryFieldIfApplicable(getIncidentDateSettings())(LocationHOC); const EnrollmentDateFieldHOC = withDataEntryField(getEnrollmentDateSettings())(IncidentDateFieldHOC); const BrowserBackWarningHOC = withBrowserBackWarning()(EnrollmentDateFieldHOC); diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.component.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.component.js index c1b0b328b0..37477a8358 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.component.js @@ -126,6 +126,7 @@ const getStageGeometrySettings = () => ({ dialogLabel: i18n.t('Area'), required: false, orientation: getOrientation(props.formHorizontal), + orgUnit: props.orgUnit, }); } @@ -136,6 +137,7 @@ const getStageGeometrySettings = () => ({ required: false, orientation: getOrientation(props.formHorizontal), shrinkDisabled: props.formHorizontal, + orgUnit: props.orgUnit, }); }, getPropName: () => stageMainDataIds.GEOMETRY, diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/DataEntry.component.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/DataEntry.component.js index 0179eb0167..e01a9e4e95 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/DataEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/DataEntry.component.js @@ -18,7 +18,6 @@ import { withSaveHandler, placements, withCleanUp, - withCenterPoint, } from '../../../../DataEntry'; import { withInternalChangeHandler, @@ -232,7 +231,7 @@ const buildGeometrySettingsFn = () => ({ dialogLabel: i18n.t('Area'), required: false, orientation: getOrientation(props.formHorizontal), - center: props.center, + orgUnit: props.orgUnit, }); } @@ -243,7 +242,7 @@ const buildGeometrySettingsFn = () => ({ required: false, orientation: getOrientation(props.formHorizontal), shrinkDisabled: props.formHorizontal, - center: props.center, + orgUnit: props.orgUnit, }); }, getPropName: () => 'geometry', @@ -413,7 +412,7 @@ const CleanUpHOC = withCleanUp()(withFilterProps(dataEntryFilterProps)(DataEntry const AssigneeField = withDataEntryFieldIfApplicable(buildAssigneeSettingsFn())(CleanUpHOC); const RelationshipField = withDataEntryFieldIfApplicable(buildRelationshipsSettingsFn())(AssigneeField); const CommentField = withDataEntryField(buildNotesSettingsFn())(RelationshipField); -const GeometryField = withCenterPoint()(withDataEntryFieldIfApplicable(buildGeometrySettingsFn())(CommentField)); +const GeometryField = withDataEntryFieldIfApplicable(buildGeometrySettingsFn())(CommentField); const ReportDateField = withDataEntryField(buildReportDateSettingsFn())(GeometryField); const FeedbackOutput = withFeedbackOutput()(ReportDateField); const IndicatorOutput = withIndicatorOutput()(FeedbackOutput); diff --git a/src/core_modules/capture-core/components/DataEntry/index.js b/src/core_modules/capture-core/components/DataEntry/index.js index a372b082f6..97ce464d1a 100644 --- a/src/core_modules/capture-core/components/DataEntry/index.js +++ b/src/core_modules/capture-core/components/DataEntry/index.js @@ -15,7 +15,6 @@ export { withErrorOutput } from './dataEntryOutput/withErrorOutput'; export { withIndicatorOutput } from './dataEntryOutput/withIndicatorOutput'; export { withCleanUp } from './withCleanUp'; export { withAskToCreateNew } from './withAskToCreateNew'; -export { withCenterPoint } from './withCenterPoint'; // misc export { inMemoryFileStore } from './file/inMemoryFileStore'; diff --git a/src/core_modules/capture-core/components/DataEntry/withCenterPoint.js b/src/core_modules/capture-core/components/DataEntry/withCenterPoint.js deleted file mode 100644 index 01842a8ff8..0000000000 --- a/src/core_modules/capture-core/components/DataEntry/withCenterPoint.js +++ /dev/null @@ -1,51 +0,0 @@ -// @flow -import React, { type ComponentType, useMemo } from 'react'; -import { useDataQuery } from '@dhis2/app-runtime'; - -const convertToClientCoordinates = ({ coordinates, type }: { coordinates: any[], type: string }) => { - switch (type) { - case 'Point': - return [coordinates[1], coordinates[0]]; - case 'Polygon': - return coordinates[0][0]; - default: - return undefined; - } -}; - -const getCenterPoint = (InnerComponent: ComponentType) => (props: Object) => { - const { orgUnit, ...passOnProps } = props; - - const { data, refetch, called } = useDataQuery( - useMemo( - () => ({ - organisationUnits: { - resource: 'organisationUnits', - id: ({ variables: { orgUnitId: id } }) => id, - params: { fields: ['geometry,parent'] }, - }, - }), - [], - ), - { lazy: true }, - ); - if (orgUnit && !called) { - refetch({ variables: { orgUnitId: orgUnit.id } }); - } - const center = useMemo(() => { - if (data?.organisationUnits) { - const { geometry, parent } = data.organisationUnits; - if (geometry) { - return convertToClientCoordinates(geometry); - } else if (parent?.id) { - refetch({ variables: { orgUnitId: parent.id } }); - } - return undefined; - } - return undefined; - }, [data, refetch]); - - return ; -}; - -export const withCenterPoint = () => (InnerComponent: ComponentType) => getCenterPoint(InnerComponent); diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/CoordinateField/CoordinateField.component.js b/src/core_modules/capture-core/components/FormFields/New/Fields/CoordinateField/CoordinateField.component.js index 9ac8d6931c..ffbc81294a 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/CoordinateField/CoordinateField.component.js +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/CoordinateField/CoordinateField.component.js @@ -5,6 +5,7 @@ import { CoordinateField as UICoordinateField } from 'capture-ui'; import Dialog from '@material-ui/core/Dialog'; import DialogTitle from '@material-ui/core/DialogTitle'; import { typeof orientations } from '../../../New'; +import { withCenterPoint } from '../../HOC'; const getStyles = (theme: Theme) => ({ inputWrapperFocused: { @@ -93,4 +94,4 @@ class CoordinateFieldPlain extends React.Component { } } -export const CoordinateField = withStyles(getStyles)(CoordinateFieldPlain); +export const CoordinateField = withStyles(getStyles)(withCenterPoint()(CoordinateFieldPlain)); diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/PolygonField/PolygonField.component.js b/src/core_modules/capture-core/components/FormFields/New/Fields/PolygonField/PolygonField.component.js index 058d82c9c9..b689d8b569 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/PolygonField/PolygonField.component.js +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/PolygonField/PolygonField.component.js @@ -4,6 +4,7 @@ import withStyles from '@material-ui/core/styles/withStyles'; import { PolygonField as UIPolygonField } from 'capture-ui'; import { Dialog, DialogTitle } from '@material-ui/core'; import { typeof orientations } from '../../../New'; +import { withCenterPoint } from '../../HOC'; const getStyles = () => ({ dialogPaper: { @@ -56,4 +57,4 @@ class PolygonFieldPlain extends React.Component { } } -export const PolygonField = withStyles(getStyles)(PolygonFieldPlain); +export const PolygonField = withStyles(getStyles)(withCenterPoint()(PolygonFieldPlain)); diff --git a/src/core_modules/capture-core/components/FormFields/New/HOC/index.js b/src/core_modules/capture-core/components/FormFields/New/HOC/index.js new file mode 100644 index 0000000000..e92b2ccdf8 --- /dev/null +++ b/src/core_modules/capture-core/components/FormFields/New/HOC/index.js @@ -0,0 +1,2 @@ +// @flow +export { withCenterPoint } from './withCenterPoint'; diff --git a/src/core_modules/capture-core/components/FormFields/New/HOC/withCenterPoint.js b/src/core_modules/capture-core/components/FormFields/New/HOC/withCenterPoint.js new file mode 100644 index 0000000000..7fcb0ea59e --- /dev/null +++ b/src/core_modules/capture-core/components/FormFields/New/HOC/withCenterPoint.js @@ -0,0 +1,56 @@ +// @flow +import React, { type ComponentType, useMemo, useState } from 'react'; +import { useApiMetadataQuery } from 'capture-core/utils/reactQueryHelpers'; + +const DEFAULT_CENTER = [51.505, -0.09]; + +const convertToClientCoordinates = ({ coordinates, type }: { coordinates: any[], type: string }) => { + switch (type) { + case 'Point': + return [coordinates[1], coordinates[0]]; + case 'Polygon': + return coordinates[0][0]; + default: + return DEFAULT_CENTER; + } +}; + +const getCenterPoint = (InnerComponent: ComponentType) => (props: Object) => { + const { orgUnit, ...passOnProps } = props; + const [orgUnitKey, setOrgUnitKey] = useState(orgUnit.id); + const [shouldFetch, setShouldFetch] = useState(false); + const queryKey = ['organisationUnit', orgUnitKey]; + const queryFn = { + resource: 'organisationUnits', + id: () => orgUnitKey, + params: { + fields: 'geometry,parent', + }, + }; + const queryOptions = useMemo( + () => ({ enabled: Boolean(orgUnit.id) && shouldFetch }), + [shouldFetch, orgUnit.id], + ); + const { data } = useApiMetadataQuery(queryKey, queryFn, queryOptions); + + const center = useMemo(() => { + if (data) { + const { geometry, parent } = data; + if (geometry) { + return convertToClientCoordinates(geometry); + } else if (parent?.id) { + setOrgUnitKey(parent.id); + } + return DEFAULT_CENTER; + } + return undefined; + }, [data]); + + const onOpenMap = (hasValue) => { + setShouldFetch(!hasValue); + }; + + return ; +}; + +export const withCenterPoint = () => (InnerComponent: ComponentType) => getCenterPoint(InnerComponent); diff --git a/src/core_modules/capture-core/components/FormFields/New/index.js b/src/core_modules/capture-core/components/FormFields/New/index.js index 96563951ac..d1e1f4a89a 100644 --- a/src/core_modules/capture-core/components/FormFields/New/index.js +++ b/src/core_modules/capture-core/components/FormFields/New/index.js @@ -33,6 +33,7 @@ export { withLabel } from './HOC/withLabel'; export { withStyledContainer } from './HOC/withStyledContainer'; export { withOptionsIconElement } from './HOC/withOptionsIconElement'; export { withFocusSaver, withInternalChangeHandler } from 'capture-ui'; +export { withCenterPoint } from './HOC/withCenterPoint'; // OrgUnit HOCs export { diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js index 86eb70dc2d..0c56de8cb1 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js @@ -12,7 +12,6 @@ import { AddLocation } from './AddLocation'; import type { PlainProps } from './actions.types'; import { LoadingMaskForButton } from '../../LoadingMasks'; import { MapModal } from '../MapModal'; -import { useCenterPoint } from '../hooks/useCenterPoint'; const styles = { actions: { @@ -40,7 +39,6 @@ export const ActionsPlain = ({ }: PlainProps) => { const [isOpenActions, setOpenActions] = useState(false); const [isOpenMap, setOpenMap] = useState(false); - const { center } = useCenterPoint(enrollment.orgUnit); const handleOnUpdate = (arg) => { setOpenActions(false); onUpdate(arg); @@ -111,7 +109,6 @@ export const ActionsPlain = ({ enrollment={enrollment} onUpdate={handleOnUpdate} setOpenMap={setOpenMap} - center={center} />} ); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js index 1a9eb2dfef..97513540fc 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js @@ -3,15 +3,17 @@ import React, { useCallback } from 'react'; import { useGeometry } from '../hooks/useGeometry'; import type { MapModalProps } from './MapModal.types'; import { MapModal as MapModalComponent } from './MapModal.component'; +import { useCenterPoint } from './hooks'; export const MapModal = ({ enrollment, onUpdate, setOpenMap, defaultValues, - center, + center: storedCenter, }: MapModalProps) => { const { geometryType, dataElementType } = useGeometry(enrollment); + const { center } = useCenterPoint(enrollment.orgUnit, storedCenter); const onSetCoordinates = useCallback((coordinates) => { const geometry = coordinates ? { type: geometryType, coordinates } : null; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/index.js new file mode 100644 index 0000000000..d1de65205f --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/index.js @@ -0,0 +1,2 @@ +// @flow +export { useCenterPoint } from './useCenterPoint'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/useCenterPoint.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/useCenterPoint.js new file mode 100644 index 0000000000..4cbb454b93 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/useCenterPoint.js @@ -0,0 +1,54 @@ +// @flow +import { useMemo, useState } from 'react'; +import { useApiMetadataQuery } from 'capture-core/utils/reactQueryHelpers'; + +const DEFAULT_CENTER = [51.505, -0.09]; + +const convertToClientCoordinates = ({ coordinates, type }: { coordinates: any[], type: string }) => { + switch (type) { + case 'Point': + return [coordinates[1], coordinates[0]]; + case 'Polygon': + return coordinates[0][0]; + default: + return DEFAULT_CENTER; + } +}; + +export const useCenterPoint = (orgUnitId: string, storedCenter: ?[number, number]) => { + const [orgUnitKey, setOrgUnitKey] = useState(orgUnitId); + const queryKey = ['organisationUnit', orgUnitKey]; + const queryFn = { + resource: 'organisationUnits', + id: () => orgUnitKey, + params: { + fields: 'geometry,parent', + }, + }; + const queryOptions = { enabled: !storedCenter && Boolean(orgUnitId) }; + const { data, isLoading } = useApiMetadataQuery(queryKey, queryFn, queryOptions); + + const center = useMemo(() => { + if (data) { + const { geometry, parent } = data; + if (geometry) { + return convertToClientCoordinates(geometry); + } else if (parent?.id) { + setOrgUnitKey(parent.id); + } + return DEFAULT_CENTER; + } + return undefined; + }, [data]); + + if (storedCenter) { + return { + center: storedCenter, + }; + } + + return { + center, + loading: isLoading, + }; +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useCenterPoint.js b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useCenterPoint.js deleted file mode 100644 index 10f2f7193c..0000000000 --- a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useCenterPoint.js +++ /dev/null @@ -1,54 +0,0 @@ -// @flow -import { useMemo } from 'react'; -import { useDataQuery } from '@dhis2/app-runtime'; - -const DEFAULT_CENTER = [51.505, -0.09]; - -const convertToClientCoordinates = ({ coordinates, type }: { coordinates: any[], type: string }) => { - switch (type) { - case 'Point': - return [coordinates[1], coordinates[0]]; - case 'Polygon': - return coordinates[0][0]; - default: - return DEFAULT_CENTER; - } -}; - -export const useCenterPoint = (orgUnitId: string | boolean) => { - const { error, data, refetch, called, loading } = useDataQuery( - useMemo( - () => ({ - organisationUnits: { - resource: 'organisationUnits', - id: ({ variables: { orgUnitId: id } }) => id, - params: { fields: ['geometry,parent'] }, - }, - }), - [], - ), - { lazy: true }, - ); - - if (orgUnitId && !called) { - refetch({ variables: { orgUnitId } }); - } - - const center = useMemo(() => { - if (!error && data?.organisationUnits) { - const { geometry, parent } = data.organisationUnits; - if (geometry) { - return convertToClientCoordinates(geometry); - } else if (parent?.id) { - refetch({ variables: { orgUnitId: parent.id } }); - } - return DEFAULT_CENTER; - } - return undefined; - }, [data, refetch, error]); - - return { - center, - loading, - }; -}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/DataEntry.component.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/DataEntry.component.js index 77024c459c..d871330dfb 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/DataEntry.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/DataEntry.component.js @@ -12,7 +12,6 @@ import { type RenderFoundation, type ProgramStage } from '../../../metaData'; import { getNoteValidatorContainers } from './fieldValidators/note.validatorContainersGetter'; import { placements, - withCenterPoint, withCleanUp, } from '../../DataEntry'; import { @@ -224,7 +223,7 @@ const buildGeometrySettingsFn = () => ({ dialogLabel: i18n.t('Area'), required: false, orientation: getOrientation(props.formHorizontal), - center: props.center, + orgUnit: props.orgUnit, }); } @@ -235,7 +234,7 @@ const buildGeometrySettingsFn = () => ({ required: false, orientation: getOrientation(props.formHorizontal), shrinkDisabled: props.formHorizontal, - center: props.center, + orgUnit: props.orgUnit, }); }, getPropName: () => 'geometry', @@ -360,7 +359,6 @@ const WrappedDataEntry = compose( withDataEntryFields(getCategoryOptionsSettingsFn()), withDataEntryField(buildReportDateSettingsFn()), withDataEntryFieldIfApplicable(buildGeometrySettingsFn()), - withCenterPoint(), withDataEntryField(buildNotesSettingsFn()), withDataEntryFieldIfApplicable(buildAssigneeSettingsFn()), withCleanUp(), diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js index afcd027e61..1a43a9104a 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js @@ -19,7 +19,6 @@ import { placements, withCleanUp, withBrowserBackWarning, - withCenterPoint, } from '../../../components/DataEntry'; import { withInternalChangeHandler, @@ -253,7 +252,7 @@ const buildGeometrySettingsFn = () => ({ label: i18n.t('Area'), dialogLabel: i18n.t('Area'), required: false, - center: props.center, + orgUnit: props.orgUnit, }); } return createComponentProps(props, { @@ -261,7 +260,7 @@ const buildGeometrySettingsFn = () => ({ label: i18n.t('Coordinate'), dialogLabel: i18n.t('Coordinate'), required: false, - center: props.center, + orgUnit: props.orgUnit, }); }, getPropName: () => 'geometry', @@ -366,7 +365,7 @@ const saveHandlerConfig = { const AOCFieldBuilderHOC = withAOCFieldBuilder(AOCSettings)(withDataEntryFields(getCategoryOptionsSettingsFn())(DataEntry)); const CleanUpHOC = withCleanUp()(AOCFieldBuilderHOC); -const GeometryField = withCenterPoint()(withDataEntryFieldIfApplicable(buildGeometrySettingsFn())(CleanUpHOC)); +const GeometryField = withDataEntryFieldIfApplicable(buildGeometrySettingsFn())(CleanUpHOC); const ScheduleDateField = withDataEntryField(buildScheduleDateSettingsFn())(GeometryField); const ReportDateField = withDataEntryField(buildReportDateSettingsFn())(ScheduleDateField); const SaveableDataEntry = withSaveHandler(saveHandlerConfig)(withMainButton()(ReportDateField)); diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.js index 13b083c85f..b9de1f5590 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/DataEntry.component.js @@ -21,7 +21,7 @@ export const DataEntryComponent = ({ onGetValidationContext, errorsMessages, warningsMessages, - center, + orgUnit, }: PlainProps) => ( {i18n.t(`Edit ${trackedEntityName}`)} @@ -37,7 +37,7 @@ export const DataEntryComponent = ({ onUpdateFormField={onUpdateFormField} onUpdateFormFieldAsync={onUpdateFormFieldAsync} onGetValidationContext={onGetValidationContext} - center={center} + orgUnit={orgUnit} /> ) ); diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js index d0fb1a1169..b067cac252 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/dataEntry.types.js @@ -17,6 +17,7 @@ export type PlainProps = {| errorsMessages: Array<{ id: string, message: string }>, warningsMessages: Array<{ id: string, message: string }>, center?: ?Array, + orgUnit: { id: string }, |}; export type Props = {| diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/index.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/index.js index 0b93bbd5b8..32ca41ee8b 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/index.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/index.js @@ -10,4 +10,3 @@ export { useOptionSets } from './useOptionSets'; export { useProgramTrackedEntityAttributes } from './useProgramTrackedEntityAttributes'; export { useFormValidations } from './useFormValidations'; export { useGeometryValues } from './useGeometryValues'; -export { useCenterPoint } from './useCenterPoint'; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useCenterPoint.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useCenterPoint.js deleted file mode 100644 index 10f2f7193c..0000000000 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useCenterPoint.js +++ /dev/null @@ -1,54 +0,0 @@ -// @flow -import { useMemo } from 'react'; -import { useDataQuery } from '@dhis2/app-runtime'; - -const DEFAULT_CENTER = [51.505, -0.09]; - -const convertToClientCoordinates = ({ coordinates, type }: { coordinates: any[], type: string }) => { - switch (type) { - case 'Point': - return [coordinates[1], coordinates[0]]; - case 'Polygon': - return coordinates[0][0]; - default: - return DEFAULT_CENTER; - } -}; - -export const useCenterPoint = (orgUnitId: string | boolean) => { - const { error, data, refetch, called, loading } = useDataQuery( - useMemo( - () => ({ - organisationUnits: { - resource: 'organisationUnits', - id: ({ variables: { orgUnitId: id } }) => id, - params: { fields: ['geometry,parent'] }, - }, - }), - [], - ), - { lazy: true }, - ); - - if (orgUnitId && !called) { - refetch({ variables: { orgUnitId } }); - } - - const center = useMemo(() => { - if (!error && data?.organisationUnits) { - const { geometry, parent } = data.organisationUnits; - if (geometry) { - return convertToClientCoordinates(geometry); - } else if (parent?.id) { - refetch({ variables: { orgUnitId: parent.id } }); - } - return DEFAULT_CENTER; - } - return undefined; - }, [data, refetch, error]); - - return { - center, - loading, - }; -}; diff --git a/src/core_modules/capture-ui/CoordinateField/CoordinateField.component.js b/src/core_modules/capture-ui/CoordinateField/CoordinateField.component.js index 27304364ea..677b1074bd 100644 --- a/src/core_modules/capture-ui/CoordinateField/CoordinateField.component.js +++ b/src/core_modules/capture-ui/CoordinateField/CoordinateField.component.js @@ -20,8 +20,8 @@ type Coordinate = { type Props = { onBlur: (value: any) => void, + onOpenMap: (hasValue: boolean) => void, orientation: $Values, - mapCenter: Array, center?: ?Array, onChange?: ?(value: any) => void, value?: ?Coordinate, @@ -44,10 +44,6 @@ const coordinateKeys = { export class CoordinateField extends React.Component { mapInstance: ?any; - static defaultProps = { - mapCenter: [51.505, -0.09], - }; - constructor(props: Props) { super(props); @@ -90,6 +86,7 @@ export class CoordinateField extends React.Component { } openMap = () => { + this.props.onOpenMap(Boolean(this.props.value)); this.setState({ showMap: true, position: this.getPosition() }); } @@ -163,7 +160,7 @@ export class CoordinateField extends React.Component { renderMap = () => { const { position, zoom } = this.state; - const center = position || this.props.center || this.props.mapCenter; + const center = position || this.props.center; return (
{ ); renderLatitude = () => { - const { mapCenter, center, onBlur, onChange, value, orientation, shrinkDisabled, classes, mapDialog, disabled, ...passOnProps } = this.props; + const { center, onBlur, onChange, value, orientation, shrinkDisabled, classes, mapDialog, disabled, ...passOnProps } = this.props; const { mapIconContainer: mapIconContainerCustomClass, mapIcon: mapIconCustomClass, ...passOnClasses } = classes || {}; return ( // $FlowFixMe[cannot-spread-inexact] automated comment @@ -222,7 +219,7 @@ export class CoordinateField extends React.Component { } renderLongitude = () => { - const { mapCenter, center, onBlur, onChange, value, orientation, shrinkDisabled, classes, mapDialog, disabled, ...passOnProps } = this.props; + const { center, onBlur, onChange, value, orientation, shrinkDisabled, classes, mapDialog, disabled, ...passOnProps } = this.props; const { mapIconContainer: mapIconContainerCustomClass, mapIcon: mapIconCustomClass, ...passOnClasses } = classes || {}; return ( // $FlowFixMe[cannot-spread-inexact] automated comment diff --git a/src/core_modules/capture-ui/PolygonField/PolygonField.component.js b/src/core_modules/capture-ui/PolygonField/PolygonField.component.js index 530eb18a7b..24f17ea69d 100644 --- a/src/core_modules/capture-ui/PolygonField/PolygonField.component.js +++ b/src/core_modules/capture-ui/PolygonField/PolygonField.component.js @@ -14,8 +14,8 @@ const WrappedLeafletSearch = withLeaflet(ReactLeafletSearch); type Props = { onBlur: (value: any) => void, + onOpenMap: (hasValue: boolean) => void, value?: ?any, - mapCenter: Array, center?: ?Array, mapDialog?: ?React.Element, }; @@ -63,10 +63,6 @@ function coordsToFeatureCollection(coordinates): ?FeatureCollection { } export class PolygonField extends React.Component { - static defaultProps = { - mapCenter: [51.505, -0.09], - } - constructor(props: Props) { super(props); @@ -99,7 +95,7 @@ export class PolygonField extends React.Component { getCenter = (featureCollection: ?FeatureCollection) => { if (!featureCollection) { - return this.props.center || this.props.mapCenter; + return this.props.center; } const coordinates = featureCollection.features[0].geometry.coordinates[0]; const { lat, lng } = L.latLngBounds(coordinates.map(c => ([c[0], c[1]]))).getCenter(); @@ -114,6 +110,7 @@ export class PolygonField extends React.Component { } openMap = () => { + this.props.onOpenMap(Boolean(this.props.value)); this.setState({ showMap: true, mapCoordinates: this.props.value }); } From cdf150a2a7c341b6a3c2394a5a5f265e36aa85a0 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 20 Mar 2024 13:55:06 +0100 Subject: [PATCH 49/49] chore: change query key to reflect that we are just getting geometry --- i18n/en.pot | 22 ++++++++++++++----- .../FormFields/New/HOC/withCenterPoint.js | 2 +- .../MapModal/hooks/useCenterPoint.js | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 00c7be83bf..abe49b1098 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-01-26T09:31:09.168Z\n" -"PO-Revision-Date: 2024-01-26T09:31:09.168Z\n" +"POT-Creation-Date: 2024-03-20T12:51:39.157Z\n" +"PO-Revision-Date: 2024-03-20T12:51:39.157Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -932,6 +932,11 @@ msgstr "Search {{uniqueAttrName}}" msgid "Search by attributes" msgstr "Search by attributes" +msgid "Fill in at least {{count}} attribute to search" +msgid_plural "Fill in at least {{count}} attribute to search" +msgstr[0] "Fill in at least {{count}} attribute to search" +msgstr[1] "Fill in at least {{count}} attributes to search" + msgid "Could not retrieve metadata. Please try again later." msgstr "Could not retrieve metadata. Please try again later." @@ -1281,12 +1286,22 @@ msgstr "Scheduled automatically for {{suggestedScheduleDate}}" msgid "The scheduled date matches the suggested date, but can be changed if needed." msgstr "The scheduled date matches the suggested date, but can be changed if needed." +msgid "The scheduled date is {{count}} days {{position}} the suggested date." +msgid_plural "The scheduled date is {{count}} days {{position}} the suggested date." +msgstr[0] "The scheduled date is {{count}} day {{position}} the suggested date." +msgstr[1] "The scheduled date is {{count}} days {{position}} the suggested date." + msgid "after" msgstr "after" msgid "before" msgstr "before" +msgid "There are {{count}} scheduled event in {{orgUnitName}} on this day." +msgid_plural "There are {{count}} scheduled event in {{orgUnitName}} on this day." +msgstr[0] "There are {{count}} scheduled event in {{orgUnitName}} on this day." +msgstr[1] "There are {{count}} scheduled events in {{orgUnitName}} on this day." + msgid "Scheduling an event in {{stageName}} for {{programName}} in {{orgUnitName}}" msgstr "Scheduling an event in {{stageName}} for {{programName}} in {{orgUnitName}}" @@ -1365,9 +1380,6 @@ msgstr "This stage can only have one event" msgid "Events could not be retrieved. Please try again later." msgstr "Events could not be retrieved. Please try again later." -msgid "Assigned to" -msgstr "Assigned to" - msgid "{{ totalEvents }} events" msgstr "{{ totalEvents }} events" diff --git a/src/core_modules/capture-core/components/FormFields/New/HOC/withCenterPoint.js b/src/core_modules/capture-core/components/FormFields/New/HOC/withCenterPoint.js index 7fcb0ea59e..0ac9861d47 100644 --- a/src/core_modules/capture-core/components/FormFields/New/HOC/withCenterPoint.js +++ b/src/core_modules/capture-core/components/FormFields/New/HOC/withCenterPoint.js @@ -19,7 +19,7 @@ const getCenterPoint = (InnerComponent: ComponentType) => (props: Object) = const { orgUnit, ...passOnProps } = props; const [orgUnitKey, setOrgUnitKey] = useState(orgUnit.id); const [shouldFetch, setShouldFetch] = useState(false); - const queryKey = ['organisationUnit', orgUnitKey]; + const queryKey = ['organisationUnit', 'geometry', orgUnitKey]; const queryFn = { resource: 'organisationUnits', id: () => orgUnitKey, diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/useCenterPoint.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/useCenterPoint.js index 4cbb454b93..b74f3f08e5 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/useCenterPoint.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/hooks/useCenterPoint.js @@ -17,7 +17,7 @@ const convertToClientCoordinates = ({ coordinates, type }: { coordinates: any[], export const useCenterPoint = (orgUnitId: string, storedCenter: ?[number, number]) => { const [orgUnitKey, setOrgUnitKey] = useState(orgUnitId); - const queryKey = ['organisationUnit', orgUnitKey]; + const queryKey = ['organisationUnit', 'geometry', orgUnitKey]; const queryFn = { resource: 'organisationUnits', id: () => orgUnitKey,