Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [DHIS2-13237] Enrollment coordinates in enrollment widget #3141

Merged
merged 61 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
7f8ab42
feat: [DHIS2-13237] add coordinate with map
jasminenguyennn Dec 6, 2022
c9e22f4
chore: minor fix
jasminenguyennn Dec 6, 2022
29b5ee9
feat: [DHIS2-13237] handle polygon in the map
jasminenguyennn Dec 7, 2022
02b7e6d
chore: fix flow
jasminenguyennn Dec 7, 2022
6e392c7
chore: [DHIS2-13237] convert coordinates
jasminenguyennn Dec 12, 2022
10c0237
feat: [DHIS2-13237] open on click and add new location
jasminenguyennn Dec 13, 2022
d610ef6
feat: [DHIS2-13237] handle add geometry
jasminenguyennn Dec 14, 2022
562e73c
feat: [DHIS2-13237] update locations
jasminenguyennn Dec 14, 2022
6e1ea40
chore: [DHIS2-13237] minor fix
jasminenguyennn Dec 14, 2022
c3d2cc0
Merge branch 'master' into DHIS2-13237
jasminenguyennn Dec 14, 2022
2d7f6f8
chore: [DHIS2-13237] minor fix
jasminenguyennn Dec 14, 2022
261dd92
Merge branch 'master' into DHIS2-13237
jasminenguyennn Jan 5, 2023
bab56da
Merge branch 'master' into DHIS2-13237
jasminenguyennn Jan 10, 2023
89f91b0
fix: minor fix
jasminenguyennn Jan 10, 2023
58cf3cf
Merge branch 'master' into DHIS2-13237
jasminenguyennn Jan 19, 2023
7c58207
chore: [DHIS2-13237] update review feedbacks
jasminenguyennn Jan 19, 2023
d94835e
chore: [DHIS2-13237] move folder
jasminenguyennn Jan 19, 2023
1054a57
Merge branch 'master' into DHIS2-13237
jasminenguyennn Jan 20, 2023
d6586c9
chore: [DHIS2-13237] minor add default value
jasminenguyennn Jan 25, 2023
ddb709d
chore: [DHIS2-13237] update edit polygon
jasminenguyennn Jan 25, 2023
aee5480
fix: [DHIS2-13237] minor fix
jasminenguyennn Jan 26, 2023
1321d38
Merge branch 'master' into DHIS2-13237
jasminenguyennn Jan 26, 2023
a31ec08
chore: [DHIS2-13237] fit bounds
jasminenguyennn Jan 27, 2023
8b7c0fa
fix: [DHIS2-13237] fix flow
jasminenguyennn Jan 27, 2023
d9a9f2e
fix: minor fix
jasminenguyennn Jan 27, 2023
a24b951
Merge branch 'master' into DHIS2-13237
jasminenguyennn Mar 21, 2023
64e5577
feat: [DHIS2-13237] add search feature
jasminenguyennn Mar 21, 2023
67da33c
feat: [DHIS2-13237] add input fields
jasminenguyennn Mar 21, 2023
1f29f52
fix: [DHIS2-13237] fix flow
jasminenguyennn Mar 22, 2023
a3a9dac
feat: [DHIS2-13237] update view/edit state
jasminenguyennn Mar 24, 2023
7341568
fix: minor update
jasminenguyennn Mar 27, 2023
0306946
Merge branch 'master' into DHIS2-13237
jasminenguyennn Mar 31, 2023
a84c8f9
Pressing enter should trigger search unique identifier returns results
jasminenguyennn Apr 18, 2023
9ae9423
fix: [DHIS2-13237] fix onChange
jasminenguyennn Apr 18, 2023
70628a1
fix: fix flow
jasminenguyennn Apr 18, 2023
1461509
fix: [DHIS2-13237] reset
jasminenguyennn Apr 20, 2023
280f130
fix: minor fix
jasminenguyennn Apr 20, 2023
fb90cbc
Merge branch 'master' into DHIS2-13237
simonadomnisoru Jun 2, 2023
4328c72
fix: validate coordinates
simonadomnisoru Jun 8, 2023
eb276a9
fix: delete polygon error
simonadomnisoru Jun 8, 2023
12e14fc
Merge branch 'master' into DHIS2-13237
simonadomnisoru Jun 9, 2023
4f5a328
Merge branch 'master' into DHIS2-13237
simonadomnisoru Jun 9, 2023
169e2cc
fix: close enrollment actions dropdown
simonadomnisoru Jul 4, 2023
d8582d9
fix: resetToDefaultValues and adjust height
simonadomnisoru Jul 4, 2023
c44f4a3
Merge branch 'master' into DHIS2-13237
simonadomnisoru Jul 4, 2023
9940b39
Merge branch 'master' into DHIS2-13237
simonadomnisoru Jul 24, 2023
e9d829b
chore: fix typo
simonadomnisoru Aug 8, 2023
4d23376
refactor: split MapCoordinatesModalComponent
simonadomnisoru Aug 10, 2023
120fa4e
Merge branch 'master' into DHIS2-13237
simonadomnisoru Aug 10, 2023
575bca7
chore: handle numbers with comma
simonadomnisoru Aug 16, 2023
0ebc464
fix: the map buttons are still displayed after closing the modal
simonadomnisoru Aug 17, 2023
941a9dd
chore: restructure MapModal folder
simonadomnisoru Aug 17, 2023
af48be5
chore: clean reactFGref
simonadomnisoru Aug 17, 2023
ffa39ae
Merge branch 'master' into DHIS2-13237
simonadomnisoru Sep 7, 2023
e5e9f38
Merge branch 'master' into DHIS2-13237
simonadomnisoru Sep 18, 2023
5aa0fcf
Merge branch 'master' into DHIS2-13237
simonadomnisoru Oct 18, 2023
cb72b5c
chore: disable the option to edit the polygon
simonadomnisoru Oct 19, 2023
f74091b
chore: improve the logic for Close without saving and Set Area buttons
simonadomnisoru Oct 23, 2023
da911ba
feat: return to previous state when canceling
simonadomnisoru Oct 23, 2023
c50adbf
Merge branch 'master' into DHIS2-13237
simonadomnisoru Nov 13, 2023
cd99514
fix: update enrollment mutation callback
simonadomnisoru Nov 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -1043,6 +1052,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"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// @flow
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';
import type { MiniMapProps } from './mapCoordinates.types';

const styles = () => ({
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;
}
};


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];

return (
<>
<div className={classes.mapContainer}>
<Map
center={center}
className={classes.map}
zoom={11}
zoomControl={false}
attributionControl={false}
key="minimap"
onClick={() => {
setModalOpen(true);
}}
>
<TileLayer
url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"
attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
/>
{type === dataElementTypes.COORDINATE && <Marker position={clientValues} />}
{type === dataElementTypes.POLYGON && <Polygon positions={clientValues} />}
</Map>
</div>
<MapCoordinatesModal
type={type}
center={center}
isOpen={isModalOpen}
setOpen={setModalOpen}
onSetCoordinates={onSetCoordinates}
/>
</>
);
};

export const MapCoordinates = withStyles(styles)(MapCoordinatesPlain);
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// @flow
import React, { useState } from 'react';
import i18n from '@dhis2/d2-i18n';
import log from 'loglevel';
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';
import type { ModalProps } from './mapCoordinates.types';

const styles = () => ({
modalContent: {
width: '100%',
height: '75vh',
},
map: {
width: '100%',
height: '100%',
},
title: {
textTransform: 'capitalize',
},
});

const convertToServerCoordinates = (coordinates, type) => {
if (!coordinates) { return null; }
switch (type) {
case dataElementTypes.COORDINATE:
return [coordinates[1], coordinates[0]];
case dataElementTypes.POLYGON:
return [coordinates[0]];
default:
return coordinates;
}
};

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 newPosition: [number, number] = [lat, lng];
// $FlowFixMe
setPosition(newPosition);
}
};

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 = () => (<Map
center={center}
zoom={13}
ref={(ref) => {
if (ref?.leafletElement) {
setTimeout(() => { ref.leafletElement.invalidateSize(); }, 250);
}
}}
className={classes.map}
onClick={onHandleMapClicked}
>
<TileLayer
url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"
attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
/>
{type === dataElementTypes.POLYGON && <FeatureGroup>
<EditControl
position="topright"
onEdited={onMapPolygonEdited}
onCreated={onMapPolygonCreated}
onDeleted={onMapPolygonDelete}
draw={{
rectangle: false,
polyline: false,
circle: false,
marker: false,
circlemarker: false,
}}
edit={{
remove: false,
}}
/>
</FeatureGroup>}
{type === dataElementTypes.COORDINATE && position && <Marker position={position} />}
</Map>);

const getTitle = () => {
switch (type) {
case dataElementTypes.COORDINATE:
return i18n.t('coordinates');
case dataElementTypes.POLYGON:
return i18n.t('area');
default:
log.error(`${type} is not handled`);
return '';
}
};

const renderActions = () => (<ButtonStrip end>
<Button
onClick={() => {
setOpen(false);
}}
secondary
>
{i18n.t('Cancel')}
</Button>
<Button
onClick={() => {
if (position ?? coordinates) {
const convertedCoordinates = convertToServerCoordinates(position ?? coordinates, type);
// $FlowFixMe
simonadomnisoru marked this conversation as resolved.
Show resolved Hide resolved
onSetCoordinates(convertedCoordinates);
setOpen(false);
}
}}
primary
>
{`${i18n.t('Set')} ${getTitle()}`}
</Button>
</ButtonStrip>);

return (
<Modal
hide={!isOpen}
large
>
<ModalTitle>
simonadomnisoru marked this conversation as resolved.
Show resolved Hide resolved
<div className={classes.title}>{getTitle()}</div>
</ModalTitle>
<ModalContent>
<div className={classes.modalContent}>{renderMap()}</div>
</ModalContent>
<ModalActions>
{renderActions()}
</ModalActions>
</Modal>
);
};
export const MapCoordinatesModal = withStyles(styles)(MapCoordinatesModalPlain);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @flow
export { MapCoordinates } from './MapCoordinates';
export { MapCoordinatesModal } from './MapCoordinatesModal';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// @flow

export type MiniMapProps = {
coordinates: any,
type: string,
classes: Object,
simonadomnisoru marked this conversation as resolved.
Show resolved Hide resolved
onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void,
}

export type ModalProps = {
center: ?[number, number],
isOpen: boolean,
type: string,
setOpen: (open: boolean) => void,
onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void,
classes: Object
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type EnrollmentData = {|
scheduledAt: string,
trackedEntity: string,
trackedEntityType: string,
geometry?: ?{ type: string, coordinates: [number, number] | Array<[number, number]>}
|};

export type AttributeValue = {|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -72,6 +73,10 @@ export const ActionsPlain = ({
enrollment={enrollment}
onUpdate={handleOnUpdate}
/>
<AddLocation
enrollment={enrollment}
onUpdate={handleOnUpdate}
/>
<MenuDivider />
<Cancel
enrollment={enrollment}
Expand All @@ -81,6 +86,7 @@ export const ActionsPlain = ({
enrollment={enrollment}
onDelete={handleOnDelete}
/>

</FlyoutMenu>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,9 @@
// @flow
import { useDataMutation } from '@dhis2/app-runtime';
import React from 'react';
import { useDeleteEnrollment, useUpdateEnrollment } from '../dataMutation/dataMutation';
import { ActionsComponent } from './Actions.component';
import type { Props } from './actions.types';

const enrollmentUpdate = {
resource: 'tracker?async=false&importStrategy=UPDATE',
type: 'create',
data: enrollment => ({
enrollments: [enrollment],
}),
};
const enrollmentDelete = {
resource: 'tracker?async=false&importStrategy=DELETE',
type: 'create',
data: enrollment => ({
enrollments: [enrollment],
}),
};
const processErrorReports = (error) => {
// $FlowFixMe[prop-missing]
const errorReports = error?.details?.validationReport?.errorReports;
return errorReports?.length > 0
? errorReports.reduce((acc, errorReport) => `${acc} ${errorReport.message}`, '')
: error.message;
};

export const Actions = ({
enrollment = {},
refetchEnrollment,
Expand All @@ -34,27 +12,8 @@ export const Actions = ({
onError,
...passOnProps
}: Props) => {
const [updateMutation, { loading: updateLoading }] = useDataMutation(
enrollmentUpdate,
{
onComplete: () => {
refetchEnrollment();
refetchTEI();
},
onError: (e) => {
onError && onError(processErrorReports(e));
},
},
);
const [deleteMutation, { loading: deleteLoading }] = useDataMutation(
enrollmentDelete,
{
onComplete: onDelete,
onError: (e) => {
onError && onError(processErrorReports(e));
},
},
);
const { updateMutation, updateLoading } = useUpdateEnrollment(refetchEnrollment, refetchTEI, onError);
const { deleteMutation, deleteLoading } = useDeleteEnrollment(onDelete, onError);

return (
<ActionsComponent
Expand Down
Loading