From 052f33a8ff336a2d29f794ab56139adbdd4b31f4 Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Mon, 13 May 2024 16:53:02 +0200 Subject: [PATCH 01/27] working on selection --- .../maps/markers/MarkersListComponent.js | 2 + .../domains/registry/components/MapPopUp.tsx | 93 ------------------- .../components/OrgUnitChildrenMap.tsx | 79 ++++++++++------ .../registry/components/OrgUnitInstances.tsx | 17 +++- .../registry/components/OrgUnitPaper.tsx | 18 +++- .../js/apps/Iaso/domains/registry/index.tsx | 9 +- 6 files changed, 93 insertions(+), 125 deletions(-) delete mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/MapPopUp.tsx diff --git a/hat/assets/js/apps/Iaso/components/maps/markers/MarkersListComponent.js b/hat/assets/js/apps/Iaso/components/maps/markers/MarkersListComponent.js index 79d94c9489..c2bc4435e2 100644 --- a/hat/assets/js/apps/Iaso/components/maps/markers/MarkersListComponent.js +++ b/hat/assets/js/apps/Iaso/components/maps/markers/MarkersListComponent.js @@ -74,6 +74,7 @@ MarkersListComponent.defaultProps = { onContextmenu: () => {}, onMarkerClick: () => null, onDblclick: () => {}, + onClick: () => {}, }; MarkersListComponent.propTypes = { @@ -88,6 +89,7 @@ MarkersListComponent.propTypes = { isCircle: PropTypes.bool, onContextmenu: PropTypes.func, onDblclick: PropTypes.func, + onClick: PropTypes.func, }; export default MarkersListComponent; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/MapPopUp.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/MapPopUp.tsx deleted file mode 100644 index b300fb24e8..0000000000 --- a/hat/assets/js/apps/Iaso/domains/registry/components/MapPopUp.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { FunctionComponent, useRef } from 'react'; -import { Popup, useMap } from 'react-leaflet'; - -import ClearIcon from '@mui/icons-material/Clear'; -import { Box, Card, CardContent, Divider, IconButton } from '@mui/material'; -import { makeStyles } from '@mui/styles'; - -import { - commonStyles, - mapPopupStyles, - useSafeIntl, -} from 'bluesquare-components'; - -import PopupItemComponent from '../../../components/maps/popups/PopupItemComponent'; -import { LinkToOrgUnit } from '../../orgUnits/components/LinkToOrgUnit'; -import { OrgUnit } from '../../orgUnits/types/orgUnit'; -import MESSAGES from '../messages'; -import { LinkToRegistry } from './LinkToRegistry'; - -type Props = { - orgUnit: OrgUnit; -}; - -const useStyles = makeStyles(theme => ({ - ...commonStyles(theme), - ...mapPopupStyles(theme), - popupCardContent: { - ...mapPopupStyles(theme).popupCardContent, - margin: theme.spacing(2), - }, - popup: { - ...mapPopupStyles(theme).popup, - '& .leaflet-popup-content': { - ...mapPopupStyles(theme).popup['& .leaflet-popup-content'], - width: '300px !important', - }, - '& a.leaflet-popup-close-button': { - display: 'none', - }, - }, -})); - -export const MapPopUp: FunctionComponent = ({ orgUnit }) => { - const { formatMessage } = useSafeIntl(); - const classes: Record = useStyles(); - const popup: any = useRef(); - const map = useMap(); - return ( - - - - - - - - - - map.closePopup(popup.current)} - > - - - - - - - - - - - - ); -}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenMap.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenMap.tsx index 51a23da75b..86c8b1bbe6 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenMap.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenMap.tsx @@ -1,12 +1,14 @@ import { Box } from '@mui/material'; import { red } from '@mui/material/colors'; -import { makeStyles } from '@mui/styles'; +import { makeStyles, useTheme } from '@mui/styles'; import { LoadingSpinner, commonStyles } from 'bluesquare-components'; import classNames from 'classnames'; import L from 'leaflet'; import { keyBy } from 'lodash'; import React, { + Dispatch, FunctionComponent, + SetStateAction, useCallback, useEffect, useMemo, @@ -41,7 +43,6 @@ import TILES from '../../../constants/mapTiles'; import { baseUrls } from '../../../constants/urls'; import { redirectTo, redirectToReplace } from '../../../routing/actions'; import { RegistryDetailParams } from '../types'; -import { MapPopUp } from './MapPopUp'; import { MapSettings, Settings } from './MapSettings'; import { MapToolTip } from './MapTooltip'; @@ -51,6 +52,8 @@ type Props = { orgUnitChildren?: OrgUnit[]; isFetchingChildren: boolean; params: RegistryDetailParams; + setSelectedChildren: Dispatch>; + selectedChildren: OrgUnit | undefined; }; const boundsOptions = { @@ -85,6 +88,8 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ orgUnitChildren, isFetchingChildren, params, + setSelectedChildren, + selectedChildren, }) => { const classes: Record = useStyles(); const dispatch = useDispatch(); @@ -92,7 +97,7 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ showTooltip: params.showTooltip === 'true', useCluster: params.useCluster === 'true', }); - + const theme = useTheme(); const [isMapFullScreen, setIsMapFullScreen] = useState( params.isFullScreen === 'true', ); @@ -171,12 +176,22 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ }, [dispatch], ); + const handleSingleClick = useCallback( + (ou: OrgUnit, event: L.LeafletMouseEvent | undefined) => { + event?.originalEvent.stopPropagation(); + setSelectedChildren( + ou.id === selectedChildren?.id ? undefined : ou, + ); + }, + [setSelectedChildren, selectedChildren], + ); - const handleFeatureDoubleClick = useCallback( + const handleFeatureEvents = useCallback( (ou: OrgUnit) => (_, layer) => { layer.on('dblclick', event => handleDoubleClick(event, ou)); + layer.on('click', event => handleSingleClick(ou, event)); }, - [handleDoubleClick], + [handleDoubleClick, handleSingleClick], ); if (isFetchingChildren) return ( @@ -231,14 +246,15 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ {isOrgUnitActive && ( <> {orgUnit.geo_json && ( - + ({ - color: selectedOrgUnitColor, + color: !selectedChildren + ? selectedOrgUnitColor + : theme.palette.primary.main, })} > - {showTooltip && ( = ({ }} markerProps={() => ({ ...circleColorMarkerOptions( - selectedOrgUnitColor, + !selectedChildren + ? selectedOrgUnitColor + : theme.palette.primary.main, ), key: `markers-${orgUnit.id}-${showTooltip}`, })} @@ -275,7 +293,6 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ pane: 'popupPane', label: orgUnit.name, })} - PopupComponent={MapPopUp} TooltipComponent={MapToolTip} /> )} @@ -297,20 +314,23 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ {orgUnitsShapes.map(childrenOrgUnit => ( ({ - color: subType.color || '', + color: + childrenOrgUnit.id === + selectedChildren?.id + ? selectedOrgUnitColor + : subType.color || '', })} data={childrenOrgUnit.geo_json} > - {showTooltip && ( = ({ key={subType.id} > ({ + markerProps={children => ({ ...circleColorMarkerOptions( - subType.color || '', + children.id === + selectedChildren?.id + ? selectedOrgUnitColor + : subType.color || + '', ), radius: 12, })} - popupProps={location => ({ - orgUnit: location, - })} onDblclick={handleDoubleClick} + onMarkerClick={ + handleSingleClick + } tooltipProps={e => ({ permanent: showTooltip, pane: 'popupPane', label: e.name, })} - PopupComponent={MapPopUp} TooltipComponent={MapToolTip} isCircle /> @@ -375,24 +398,24 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ {!useCluster && ( <> ({ + markerProps={children => ({ ...circleColorMarkerOptions( - subType.color || '', + children.id === + selectedChildren?.id + ? selectedOrgUnitColor + : subType.color || '', ), radius: 12, })} - popupProps={location => ({ - orgUnit: location, - })} onDblclick={handleDoubleClick} + onMarkerClick={handleSingleClick} tooltipProps={e => ({ permanent: showTooltip, pane: 'popupPane', label: e.name, })} - PopupComponent={MapPopUp} TooltipComponent={MapToolTip} isCircle /> diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitInstances.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitInstances.tsx index 60cbff39aa..335f85361a 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitInstances.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitInstances.tsx @@ -36,6 +36,7 @@ import { RegistryDetailParams } from '../types'; type Props = { orgUnit: OrgUnit; params: RegistryDetailParams; + selectedChildren?: OrgUnit; }; const useStyles = makeStyles(theme => ({ @@ -79,6 +80,7 @@ const useStyles = makeStyles(theme => ({ export const OrgUnitInstances: FunctionComponent = ({ orgUnit, params, + selectedChildren, }) => { const classes: Record = useStyles(); const dispatch = useDispatch(); @@ -87,7 +89,8 @@ export const OrgUnitInstances: FunctionComponent = ({ // selected instance should be: // submission id from params OR reference instance OR first submission of the possible ones OR undefined // if undefined select should be hidden and a place holder should say no submission - + console.log('orgUnit', orgUnit); + console.log('selectedChildren', selectedChildren); const [currentInstanceId, setCurrentInstanceId] = useState< number | string | undefined >(params.submissionId || orgUnit.reference_instance?.id); @@ -123,6 +126,18 @@ export const OrgUnitInstances: FunctionComponent = ({ return ( {isFetchingCurrentInstance && } + {selectedChildren && ( + + + {selectedChildren.name} ( + {selectedChildren.org_unit_type_name}) + + + )} {instances && instances?.length === 0 && ( >; + selectedChildren: OrgUnit | undefined; }; const useStyles = makeStyles(theme => ({ @@ -90,6 +98,8 @@ export const OrgUnitPaper: FunctionComponent = ({ isFetchingListChildren, orgUnitMapChildren, isFetchingMapChildren, + setSelectedChildren, + selectedChildren, }) => { const classes: Record = useStyles(); const [tab, setTab] = useState( @@ -118,7 +128,9 @@ export const OrgUnitPaper: FunctionComponent = ({ variant="h5" className={classes.title} > - {orgUnit.name ?? ''} + {orgUnit.name + ? `${orgUnit.name} (${orgUnit.org_unit_type_name})` + : ''} = ({ subOrgUnitTypes={subOrgUnitTypes} orgUnitChildren={orgUnitMapChildren} isFetchingChildren={isFetchingMapChildren} + setSelectedChildren={setSelectedChildren} + selectedChildren={selectedChildren} /> = ({ router }) => { params, } = router; const dispatch = useDispatch(); + + const [selectedChildren, setSelectedChildren] = useState< + OrgUnit | undefined + >(); const classes: Record = useStyles(); const { formatMessage } = useSafeIntl(); @@ -134,6 +138,8 @@ export const Registry: FunctionComponent = ({ router }) => { isFetchingMapChildren={ isFetchingMapChildren } + setSelectedChildren={setSelectedChildren} + selectedChildren={selectedChildren} /> = ({ router }) => { )} From b584b6a294ca1386bd30965ea97628cce25eff86 Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Tue, 14 May 2024 11:55:08 +0200 Subject: [PATCH 02/27] splitting code --- .../components/OrgUnitChildrenMap.tsx | 431 ------------------ .../registry/components/OrgUnitPaper.tsx | 3 +- .../components/{ => map}/MapLegend.tsx | 2 +- .../components/{ => map}/MapSettings.tsx | 4 +- .../{ => map}/MapToggleFullscreen.tsx | 2 +- .../{ => map}/MapToggleTooltips.tsx | 6 +- .../components/{ => map}/MapTooltip.tsx | 0 .../map/OrgUnitChildrenLocations.tsx | 123 +++++ .../components/map/OrgUnitChildrenMap.tsx | 271 +++++++++++ .../components/map/OrgUnitChildrenShapes.tsx | 71 +++ .../components/map/OrgUnitLocation.tsx | 87 ++++ .../registry/hooks/useGetLegendOptions.ts | 2 +- 12 files changed, 561 insertions(+), 441 deletions(-) delete mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenMap.tsx rename hat/assets/js/apps/Iaso/domains/registry/components/{ => map}/MapLegend.tsx (98%) rename hat/assets/js/apps/Iaso/domains/registry/components/{ => map}/MapSettings.tsx (98%) rename hat/assets/js/apps/Iaso/domains/registry/components/{ => map}/MapToggleFullscreen.tsx (97%) rename hat/assets/js/apps/Iaso/domains/registry/components/{ => map}/MapToggleTooltips.tsx (89%) rename hat/assets/js/apps/Iaso/domains/registry/components/{ => map}/MapTooltip.tsx (100%) create mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx create mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx create mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx create mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenMap.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenMap.tsx deleted file mode 100644 index 86c8b1bbe6..0000000000 --- a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenMap.tsx +++ /dev/null @@ -1,431 +0,0 @@ -import { Box } from '@mui/material'; -import { red } from '@mui/material/colors'; -import { makeStyles, useTheme } from '@mui/styles'; -import { LoadingSpinner, commonStyles } from 'bluesquare-components'; -import classNames from 'classnames'; -import L from 'leaflet'; -import { keyBy } from 'lodash'; -import React, { - Dispatch, - FunctionComponent, - SetStateAction, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; -import { GeoJSON, MapContainer, Pane, ScaleControl } from 'react-leaflet'; -import MarkerClusterGroup from 'react-leaflet-markercluster'; -import { useDispatch } from 'react-redux'; -import { - circleColorMarkerOptions, - colorClusterCustomMarker, - getOrgUnitBounds, - getOrgUnitsBounds, - mergeBounds, -} from '../../../utils/map/mapUtils'; - -import CircleMarkerComponent from '../../../components/maps/markers/CircleMarkerComponent'; -import { Tile } from '../../../components/maps/tools/TilesSwitchControl'; -import { MapLegend } from './MapLegend'; - -import { OrgUnit } from '../../orgUnits/types/orgUnit'; -import { OrgunitTypes } from '../../orgUnits/types/orgunitTypes'; - -import { Legend, useGetlegendOptions } from '../hooks/useGetLegendOptions'; - -import { MapToggleFullscreen } from './MapToggleFullscreen'; - -import MarkersListComponent from '../../../components/maps/markers/MarkersListComponent'; -import { CustomTileLayer } from '../../../components/maps/tools/CustomTileLayer'; -import { CustomZoomControl } from '../../../components/maps/tools/CustomZoomControl'; -import TILES from '../../../constants/mapTiles'; -import { baseUrls } from '../../../constants/urls'; -import { redirectTo, redirectToReplace } from '../../../routing/actions'; -import { RegistryDetailParams } from '../types'; -import { MapSettings, Settings } from './MapSettings'; -import { MapToolTip } from './MapTooltip'; - -type Props = { - orgUnit: OrgUnit; - subOrgUnitTypes: OrgunitTypes; - orgUnitChildren?: OrgUnit[]; - isFetchingChildren: boolean; - params: RegistryDetailParams; - setSelectedChildren: Dispatch>; - selectedChildren: OrgUnit | undefined; -}; - -const boundsOptions = { - padding: [50, 50], -}; - -const useStyles = makeStyles(theme => ({ - mapContainer: { - ...commonStyles(theme).mapContainer, - height: '542px', - position: 'relative', - '& .leaflet-control-zoom': { - borderBottom: 'none', - borderBottomLeftRadius: 0, - borderBottomRightRadius: 0, - }, - }, - fullScreen: { - position: 'fixed', - top: '64px', - left: '0', - width: '100vw', - height: 'calc(100vh - 64px)', - zIndex: 10000, - }, -})); - -export const selectedOrgUnitColor = red[500]; -export const OrgUnitChildrenMap: FunctionComponent = ({ - orgUnit, - subOrgUnitTypes, - orgUnitChildren, - isFetchingChildren, - params, - setSelectedChildren, - selectedChildren, -}) => { - const classes: Record = useStyles(); - const dispatch = useDispatch(); - const [settings, setSettings] = useState({ - showTooltip: params.showTooltip === 'true', - useCluster: params.useCluster === 'true', - }); - const theme = useTheme(); - const [isMapFullScreen, setIsMapFullScreen] = useState( - params.isFullScreen === 'true', - ); - const [currentTile, setCurrentTile] = useState(TILES.osm); - const getlegendOptions = useGetlegendOptions(orgUnit); - const [legendOptions, setLegendOptions] = useState([]); - useEffect(() => { - if (legendOptions.length === 0 && subOrgUnitTypes.length > 0) { - setLegendOptions(getlegendOptions(subOrgUnitTypes)); - } - }, [getlegendOptions, legendOptions, subOrgUnitTypes]); - - const optionsObject = useMemo( - () => keyBy(legendOptions, 'value'), - [legendOptions], - ); - const activeChildren: OrgUnit[] = useMemo( - () => - orgUnitChildren?.filter( - children => - optionsObject[`${children.org_unit_type_id}`]?.active, - ) || [], - [orgUnitChildren, optionsObject], - ); - const isOrgUnitActive: boolean = - Object.keys(optionsObject).length === 0 - ? true - : optionsObject[`${orgUnit.id}`]?.active || false; - - const { showTooltip, useCluster } = settings; - const bounds = useMemo( - () => - mergeBounds( - isOrgUnitActive ? getOrgUnitBounds(orgUnit) : undefined, - getOrgUnitsBounds(activeChildren), - ), - [activeChildren, isOrgUnitActive, orgUnit], - ); - const handleChangeSettings = useCallback( - (setting: string) => { - const newSetting = !settings[setting]; - setSettings(prevSettings => { - return { - ...prevSettings, - [setting]: newSetting, - }; - }); - - dispatch( - redirectToReplace(baseUrls.registry, { - ...params, - [setting]: `${newSetting}`, - }), - ); - }, - [dispatch, params, settings], - ); - - const handleToggleFullScreen = useCallback( - (isFull: boolean) => { - setIsMapFullScreen(isFull); - dispatch( - redirectToReplace(baseUrls.registry, { - ...params, - isFullScreen: `${isFull}`, - }), - ); - }, - [dispatch, params], - ); - const handleDoubleClick = useCallback( - (event: L.LeafletMouseEvent, ou: OrgUnit) => { - event.originalEvent.stopPropagation(); - const url = `/${baseUrls.registry}/orgUnitId/${ou?.id}`; - dispatch(redirectTo(url)); - }, - [dispatch], - ); - const handleSingleClick = useCallback( - (ou: OrgUnit, event: L.LeafletMouseEvent | undefined) => { - event?.originalEvent.stopPropagation(); - setSelectedChildren( - ou.id === selectedChildren?.id ? undefined : ou, - ); - }, - [setSelectedChildren, selectedChildren], - ); - - const handleFeatureEvents = useCallback( - (ou: OrgUnit) => (_, layer) => { - layer.on('dblclick', event => handleDoubleClick(event, ou)); - layer.on('click', event => handleSingleClick(ou, event)); - }, - [handleDoubleClick, handleSingleClick], - ); - if (isFetchingChildren) - return ( - - - - ); - return ( - - - - - - - - - - - {isOrgUnitActive && ( - <> - {orgUnit.geo_json && ( - - ({ - color: !selectedChildren - ? selectedOrgUnitColor - : theme.palette.primary.main, - })} - > - {showTooltip && ( - - )} - {!showTooltip && ( - - )} - - - )} - {orgUnit.latitude && orgUnit.longitude && ( - ({ - ...circleColorMarkerOptions( - !selectedChildren - ? selectedOrgUnitColor - : theme.palette.primary.main, - ), - key: `markers-${orgUnit.id}-${showTooltip}`, - })} - popupProps={() => ({ - orgUnit, - })} - tooltipProps={() => ({ - permanent: showTooltip, - pane: 'popupPane', - label: orgUnit.name, - })} - TooltipComponent={MapToolTip} - /> - )} - - )} - {subOrgUnitTypes.map((subType, index) => { - const orgUnitsShapes = activeChildren?.filter( - childrenOrgUnit => - Boolean(childrenOrgUnit.geo_json) && - childrenOrgUnit.org_unit_type_id === subType.id, - ); - const orgUnitsMarkers = activeChildren?.filter( - childrenOrgUnit => - childrenOrgUnit.latitude && - childrenOrgUnit.longitude && - childrenOrgUnit.org_unit_type_id === subType.id, - ); - return ( - - - {orgUnitsShapes.map(childrenOrgUnit => ( - ({ - color: - childrenOrgUnit.id === - selectedChildren?.id - ? selectedOrgUnitColor - : subType.color || '', - })} - data={childrenOrgUnit.geo_json} - > - {showTooltip && ( - - )} - {!showTooltip && ( - - )} - - ))} - - - - {useCluster && ( - <> - - colorClusterCustomMarker( - cluster, - subType.color || '', - ) - } - polygonOptions={{ - fillColor: subType.color || '', - color: subType.color || '', - }} - key={subType.id} - > - ({ - ...circleColorMarkerOptions( - children.id === - selectedChildren?.id - ? selectedOrgUnitColor - : subType.color || - '', - ), - radius: 12, - })} - onDblclick={handleDoubleClick} - onMarkerClick={ - handleSingleClick - } - tooltipProps={e => ({ - permanent: showTooltip, - pane: 'popupPane', - label: e.name, - })} - TooltipComponent={MapToolTip} - isCircle - /> - - - )} - {!useCluster && ( - <> - ({ - ...circleColorMarkerOptions( - children.id === - selectedChildren?.id - ? selectedOrgUnitColor - : subType.color || '', - ), - radius: 12, - })} - onDblclick={handleDoubleClick} - onMarkerClick={handleSingleClick} - tooltipProps={e => ({ - permanent: showTooltip, - pane: 'popupPane', - label: e.name, - })} - TooltipComponent={MapToolTip} - isCircle - /> - - )} - - - ); - })} - - - ); -}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx index 4e8a74e55e..05266f6c7d 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx @@ -24,9 +24,8 @@ import MESSAGES from '../messages'; import { baseUrls } from '../../../constants/urls'; -import { OrgUnitChildrenMap } from './OrgUnitChildrenMap'; - import { redirectToReplace } from '../../../routing/actions'; +import { OrgUnitChildrenMap } from './map/OrgUnitChildrenMap'; import { OrgUnit } from '../../orgUnits/types/orgUnit'; import { OrgUnitChildrenList } from './OrgUnitChildrenList'; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/MapLegend.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapLegend.tsx similarity index 98% rename from hat/assets/js/apps/Iaso/domains/registry/components/MapLegend.tsx rename to hat/assets/js/apps/Iaso/domains/registry/components/map/MapLegend.tsx index 410875dacd..e329ef919c 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/MapLegend.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapLegend.tsx @@ -8,7 +8,7 @@ import React, { useCallback, } from 'react'; -import { Legend } from '../hooks/useGetLegendOptions'; +import { Legend } from '../../hooks/useGetLegendOptions'; const useStyles = makeStyles(theme => ({ root: { diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/MapSettings.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapSettings.tsx similarity index 98% rename from hat/assets/js/apps/Iaso/domains/registry/components/MapSettings.tsx rename to hat/assets/js/apps/Iaso/domains/registry/components/map/MapSettings.tsx index 634b06e578..88e48cab97 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/MapSettings.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapSettings.tsx @@ -3,8 +3,8 @@ import { Box, FormControlLabel, Switch } from '@mui/material'; import { IconButton, useSafeIntl } from 'bluesquare-components'; import React, { FunctionComponent, useState } from 'react'; -import { SxStyles } from '../../../types/general'; -import MESSAGES from '../messages'; +import { SxStyles } from '../../../../types/general'; +import MESSAGES from '../../messages'; const styles: SxStyles = { root: { diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/MapToggleFullscreen.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapToggleFullscreen.tsx similarity index 97% rename from hat/assets/js/apps/Iaso/domains/registry/components/MapToggleFullscreen.tsx rename to hat/assets/js/apps/Iaso/domains/registry/components/map/MapToggleFullscreen.tsx index e8ec94b97f..fe06a10256 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/MapToggleFullscreen.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapToggleFullscreen.tsx @@ -8,7 +8,7 @@ import React, { useEffect, } from 'react'; import { useMap } from 'react-leaflet'; -import { SxStyles } from '../../../types/general'; +import { SxStyles } from '../../../../types/general'; const styles: SxStyles = { root: { diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/MapToggleTooltips.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapToggleTooltips.tsx similarity index 89% rename from hat/assets/js/apps/Iaso/domains/registry/components/MapToggleTooltips.tsx rename to hat/assets/js/apps/Iaso/domains/registry/components/map/MapToggleTooltips.tsx index d6ca483eb1..647b54360b 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/MapToggleTooltips.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapToggleTooltips.tsx @@ -1,8 +1,8 @@ -import React, { FunctionComponent, Dispatch, SetStateAction } from 'react'; -import { Paper, Box, Tooltip, Switch } from '@mui/material'; +import { Box, Paper, Switch, Tooltip } from '@mui/material'; import { makeStyles } from '@mui/styles'; import { useSafeIntl } from 'bluesquare-components'; -import MESSAGES from '../messages'; +import React, { Dispatch, FunctionComponent, SetStateAction } from 'react'; +import MESSAGES from '../../messages'; const useStyles = makeStyles(theme => ({ root: { diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/MapTooltip.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapTooltip.tsx similarity index 100% rename from hat/assets/js/apps/Iaso/domains/registry/components/MapTooltip.tsx rename to hat/assets/js/apps/Iaso/domains/registry/components/map/MapTooltip.tsx diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx new file mode 100644 index 0000000000..2e75138429 --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx @@ -0,0 +1,123 @@ +import L from 'leaflet'; +import React, { FunctionComponent, useMemo } from 'react'; +import { Pane } from 'react-leaflet'; +import MarkerClusterGroup from 'react-leaflet-markercluster'; +import { + circleColorMarkerOptions, + colorClusterCustomMarker, +} from '../../../../utils/map/mapUtils'; + +import { OrgUnit } from '../../../orgUnits/types/orgUnit'; +import { OrgunitType } from '../../../orgUnits/types/orgunitTypes'; + +import MarkersListComponent from '../../../../components/maps/markers/MarkersListComponent'; +import { MapToolTip } from './MapTooltip'; +import { selectedOrgUnitColor } from './OrgUnitChildrenMap'; + +type Props = { + selectedChildren: OrgUnit | undefined; + activeChildren: OrgUnit[]; + handleSingleClick: ( + // eslint-disable-next-line no-unused-vars + ou: OrgUnit, + // eslint-disable-next-line no-unused-vars + event: L.LeafletMouseEvent | undefined, + ) => void; + // eslint-disable-next-line no-unused-vars + handleDoubleClick: (event: L.LeafletMouseEvent, ou: OrgUnit) => void; + showTooltip: boolean; + useCluster: boolean; + index: number; + subType: OrgunitType; +}; + +export const OrgUnitChildrenLocations: FunctionComponent = ({ + handleSingleClick, + handleDoubleClick, + activeChildren, + showTooltip, + useCluster, + index, + subType, + selectedChildren, +}) => { + const orgUnitsMarkers = useMemo(() => { + return activeChildren?.filter( + childrenOrgUnit => + childrenOrgUnit.latitude && + childrenOrgUnit.longitude && + childrenOrgUnit.org_unit_type_id === subType.id, + ); + }, [activeChildren, subType]); + return ( + + {useCluster && ( + <> + + colorClusterCustomMarker( + cluster, + subType.color || '', + ) + } + polygonOptions={{ + fillColor: subType.color || '', + color: subType.color || '', + }} + key={subType.id} + > + ({ + ...circleColorMarkerOptions( + children.id === selectedChildren?.id + ? selectedOrgUnitColor + : subType.color || '', + ), + radius: 12, + })} + onDblclick={handleDoubleClick} + onMarkerClick={handleSingleClick} + tooltipProps={e => ({ + permanent: showTooltip, + pane: 'popupPane', + label: e.name, + })} + TooltipComponent={MapToolTip} + isCircle + /> + + + )} + {!useCluster && ( + <> + ({ + ...circleColorMarkerOptions( + children.id === selectedChildren?.id + ? selectedOrgUnitColor + : subType.color || '', + ), + radius: 12, + })} + onDblclick={handleDoubleClick} + onMarkerClick={handleSingleClick} + tooltipProps={e => ({ + permanent: showTooltip, + pane: 'popupPane', + label: e.name, + })} + TooltipComponent={MapToolTip} + isCircle + /> + + )} + + ); +}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx new file mode 100644 index 0000000000..3153cd9e61 --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx @@ -0,0 +1,271 @@ +import { Box } from '@mui/material'; +import { red } from '@mui/material/colors'; +import { makeStyles } from '@mui/styles'; +import { LoadingSpinner, commonStyles } from 'bluesquare-components'; +import classNames from 'classnames'; +import L from 'leaflet'; +import { keyBy } from 'lodash'; +import React, { + Dispatch, + FunctionComponent, + SetStateAction, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; +import { MapContainer, ScaleControl } from 'react-leaflet'; +import { useDispatch } from 'react-redux'; +import { + getOrgUnitBounds, + getOrgUnitsBounds, + mergeBounds, +} from '../../../../utils/map/mapUtils'; + +import { Tile } from '../../../../components/maps/tools/TilesSwitchControl'; +import { MapLegend } from './MapLegend'; + +import { OrgUnit } from '../../../orgUnits/types/orgUnit'; +import { OrgunitTypes } from '../../../orgUnits/types/orgunitTypes'; +import { Legend, useGetlegendOptions } from '../../hooks/useGetLegendOptions'; + +import { MapToggleFullscreen } from './MapToggleFullscreen'; + +import { CustomTileLayer } from '../../../../components/maps/tools/CustomTileLayer'; +import { CustomZoomControl } from '../../../../components/maps/tools/CustomZoomControl'; +import TILES from '../../../../constants/mapTiles'; +import { baseUrls } from '../../../../constants/urls'; +import { redirectTo, redirectToReplace } from '../../../../routing/actions'; +import { RegistryDetailParams } from '../../types'; +import { MapSettings, Settings } from './MapSettings'; +import { OrgUnitChildrenLocations } from './OrgUnitChildrenLocations'; +import { OrgUnitChildrenShapes } from './OrgUnitChildrenShapes'; +import { OrgUnitLocation } from './OrgUnitLocation'; + +type Props = { + orgUnit: OrgUnit; + subOrgUnitTypes: OrgunitTypes; + orgUnitChildren?: OrgUnit[]; + isFetchingChildren: boolean; + params: RegistryDetailParams; + setSelectedChildren: Dispatch>; + selectedChildren: OrgUnit | undefined; +}; + +const boundsOptions = { + padding: [50, 50], +}; + +const useStyles = makeStyles(theme => ({ + mapContainer: { + ...commonStyles(theme).mapContainer, + height: '542px', + position: 'relative', + '& .leaflet-control-zoom': { + borderBottom: 'none', + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + }, + }, + fullScreen: { + position: 'fixed', + top: '64px', + left: '0', + width: '100vw', + height: 'calc(100vh - 64px)', + zIndex: 10000, + }, +})); + +export const selectedOrgUnitColor = red[500]; +export const OrgUnitChildrenMap: FunctionComponent = ({ + orgUnit, + subOrgUnitTypes, + orgUnitChildren, + isFetchingChildren, + params, + setSelectedChildren, + selectedChildren, +}) => { + const classes: Record = useStyles(); + const dispatch = useDispatch(); + const [settings, setSettings] = useState({ + showTooltip: params.showTooltip === 'true', + useCluster: params.useCluster === 'true', + }); + const [isMapFullScreen, setIsMapFullScreen] = useState( + params.isFullScreen === 'true', + ); + const [currentTile, setCurrentTile] = useState(TILES.osm); + const getlegendOptions = useGetlegendOptions(orgUnit); + const [legendOptions, setLegendOptions] = useState([]); + useEffect(() => { + if (legendOptions.length === 0 && subOrgUnitTypes.length > 0) { + setLegendOptions(getlegendOptions(subOrgUnitTypes)); + } + }, [getlegendOptions, legendOptions, subOrgUnitTypes]); + + const optionsObject = useMemo( + () => keyBy(legendOptions, 'value'), + [legendOptions], + ); + const activeChildren: OrgUnit[] = useMemo( + () => + orgUnitChildren?.filter( + children => + optionsObject[`${children.org_unit_type_id}`]?.active, + ) || [], + [orgUnitChildren, optionsObject], + ); + const isOrgUnitActive: boolean = + Object.keys(optionsObject).length === 0 + ? true + : optionsObject[`${orgUnit.id}`]?.active || false; + + const { showTooltip, useCluster } = settings; + const bounds = useMemo( + () => + mergeBounds( + isOrgUnitActive ? getOrgUnitBounds(orgUnit) : undefined, + getOrgUnitsBounds(activeChildren), + ), + [activeChildren, isOrgUnitActive, orgUnit], + ); + const handleChangeSettings = useCallback( + (setting: string) => { + const newSetting = !settings[setting]; + setSettings(prevSettings => { + return { + ...prevSettings, + [setting]: newSetting, + }; + }); + + dispatch( + redirectToReplace(baseUrls.registry, { + ...params, + [setting]: `${newSetting}`, + }), + ); + }, + [dispatch, params, settings], + ); + + const handleToggleFullScreen = useCallback( + (isFull: boolean) => { + setIsMapFullScreen(isFull); + dispatch( + redirectToReplace(baseUrls.registry, { + ...params, + isFullScreen: `${isFull}`, + }), + ); + }, + [dispatch, params], + ); + const handleDoubleClick = useCallback( + (event: L.LeafletMouseEvent, ou: OrgUnit) => { + event.originalEvent.stopPropagation(); + const url = `/${baseUrls.registry}/orgUnitId/${ou?.id}`; + dispatch(redirectTo(url)); + }, + [dispatch], + ); + const handleSingleClick = useCallback( + (ou: OrgUnit, event: L.LeafletMouseEvent | undefined) => { + event?.originalEvent.stopPropagation(); + setSelectedChildren( + ou.id === selectedChildren?.id ? undefined : ou, + ); + }, + [setSelectedChildren, selectedChildren], + ); + + const handleFeatureEvents = useCallback( + (ou: OrgUnit) => (_, layer) => { + layer.on('dblclick', event => handleDoubleClick(event, ou)); + layer.on('click', event => handleSingleClick(ou, event)); + }, + [handleDoubleClick, handleSingleClick], + ); + if (isFetchingChildren) + return ( + + + + ); + return ( + + + + + + + + + + + {subOrgUnitTypes.map((subType, index) => ( + + + + + ))} + + + ); +}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx new file mode 100644 index 0000000000..612c341fd6 --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx @@ -0,0 +1,71 @@ +import L from 'leaflet'; +import React, { FunctionComponent } from 'react'; +import { GeoJSON, Pane } from 'react-leaflet'; + +import { OrgUnit } from '../../../orgUnits/types/orgUnit'; +import { OrgunitType } from '../../../orgUnits/types/orgunitTypes'; + +import { MapToolTip } from './MapTooltip'; +import { selectedOrgUnitColor } from './OrgUnitChildrenMap'; + +type Props = { + selectedChildren: OrgUnit | undefined; + activeChildren: OrgUnit[]; + handleFeatureEvents: ( + // eslint-disable-next-line no-unused-vars + ou: OrgUnit, + // eslint-disable-next-line no-unused-vars + ) => (feature: any, layer: L.Layer) => void; + showTooltip: boolean; + index: number; + subType: OrgunitType; +}; + +export const OrgUnitChildrenShapes: FunctionComponent = ({ + handleFeatureEvents, + activeChildren, + showTooltip, + index, + subType, + selectedChildren, +}) => { + const orgUnitsShapes = activeChildren?.filter( + childrenOrgUnit => + Boolean(childrenOrgUnit.geo_json) && + childrenOrgUnit.org_unit_type_id === subType.id, + ); + return ( + + {orgUnitsShapes.map(childrenOrgUnit => ( + ({ + color: + childrenOrgUnit.id === selectedChildren?.id + ? selectedOrgUnitColor + : subType.color || '', + })} + data={childrenOrgUnit.geo_json} + > + {showTooltip && ( + + )} + {!showTooltip && ( + + )} + + ))} + + ); +}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx new file mode 100644 index 0000000000..71a08169b2 --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx @@ -0,0 +1,87 @@ +import { red } from '@mui/material/colors'; +import { useTheme } from '@mui/styles'; +import React, { FunctionComponent } from 'react'; +import { GeoJSON, Pane } from 'react-leaflet'; +import { circleColorMarkerOptions } from '../../../../utils/map/mapUtils'; + +import CircleMarkerComponent from '../../../../components/maps/markers/CircleMarkerComponent'; + +import { OrgUnit } from '../../../orgUnits/types/orgUnit'; + +import { MapToolTip } from './MapTooltip'; + +type Props = { + orgUnit: OrgUnit; + showTooltip: boolean; + isOrgUnitActive: boolean; + selectedChildren: OrgUnit | undefined; +}; + +export const selectedOrgUnitColor = red[500]; +export const OrgUnitLocation: FunctionComponent = ({ + orgUnit, + showTooltip, + isOrgUnitActive, + selectedChildren, +}) => { + const theme = useTheme(); + return ( + <> + {isOrgUnitActive && ( + <> + {orgUnit.geo_json && ( + + ({ + color: !selectedChildren + ? selectedOrgUnitColor + : theme.palette.primary.main, + })} + > + {showTooltip && ( + + )} + {!showTooltip && ( + + )} + + + )} + {orgUnit.latitude && orgUnit.longitude && ( + ({ + ...circleColorMarkerOptions( + !selectedChildren + ? selectedOrgUnitColor + : theme.palette.primary.main, + ), + key: `markers-${orgUnit.id}-${showTooltip}`, + })} + popupProps={() => ({ + orgUnit, + })} + tooltipProps={() => ({ + permanent: showTooltip, + pane: 'popupPane', + label: orgUnit.name, + })} + TooltipComponent={MapToolTip} + /> + )} + + )} + + ); +}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts index f99e4c748b..571bf76015 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts @@ -3,7 +3,7 @@ import { useSafeIntl } from 'bluesquare-components'; import { OrgUnit } from '../../orgUnits/types/orgUnit'; import { OrgunitTypes } from '../../orgUnits/types/orgunitTypes'; -import { selectedOrgUnitColor } from '../components/OrgUnitChildrenMap'; +import { selectedOrgUnitColor } from '../components/map/OrgUnitChildrenMap'; import MESSAGES from '../messages'; export type Legend = { From 2b6232b6e259ed4dd4d7b41b821573d3b6394ac9 Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Wed, 15 May 2024 11:31:39 +0200 Subject: [PATCH 03/27] fixinf marker props, allow sub ou selection --- .../maps/markers/MarkersListComponent.js | 2 - .../registry/components/OrgUnitInstances.tsx | 4 +- .../registry/components/map/MapTooltip.tsx | 6 +- .../map/OrgUnitChildrenLocations.tsx | 90 +++++++++---------- .../components/map/OrgUnitChildrenShapes.tsx | 35 ++++---- .../components/map/OrgUnitLocation.tsx | 33 +++---- hat/assets/js/apps/Iaso/utils/map/mapUtils.ts | 29 +++--- 7 files changed, 91 insertions(+), 108 deletions(-) diff --git a/hat/assets/js/apps/Iaso/components/maps/markers/MarkersListComponent.js b/hat/assets/js/apps/Iaso/components/maps/markers/MarkersListComponent.js index c2bc4435e2..79d94c9489 100644 --- a/hat/assets/js/apps/Iaso/components/maps/markers/MarkersListComponent.js +++ b/hat/assets/js/apps/Iaso/components/maps/markers/MarkersListComponent.js @@ -74,7 +74,6 @@ MarkersListComponent.defaultProps = { onContextmenu: () => {}, onMarkerClick: () => null, onDblclick: () => {}, - onClick: () => {}, }; MarkersListComponent.propTypes = { @@ -89,7 +88,6 @@ MarkersListComponent.propTypes = { isCircle: PropTypes.bool, onContextmenu: PropTypes.func, onDblclick: PropTypes.func, - onClick: PropTypes.func, }; export default MarkersListComponent; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitInstances.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitInstances.tsx index 335f85361a..64520694ed 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitInstances.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitInstances.tsx @@ -89,8 +89,8 @@ export const OrgUnitInstances: FunctionComponent = ({ // selected instance should be: // submission id from params OR reference instance OR first submission of the possible ones OR undefined // if undefined select should be hidden and a place holder should say no submission - console.log('orgUnit', orgUnit); - console.log('selectedChildren', selectedChildren); + // console.log('orgUnit', orgUnit); + // console.log('selectedChildren', selectedChildren); const [currentInstanceId, setCurrentInstanceId] = useState< number | string | undefined >(params.submissionId || orgUnit.reference_instance?.id); diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/MapTooltip.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapTooltip.tsx index ab005e3008..bc316b8428 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/MapTooltip.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapTooltip.tsx @@ -13,7 +13,11 @@ export const MapToolTip: FunctionComponent = ({ pane, }) => { return ( - + {label} ); diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx index 2e75138429..40f7b95ed1 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx @@ -1,5 +1,5 @@ import L from 'leaflet'; -import React, { FunctionComponent, useMemo } from 'react'; +import React, { FunctionComponent, useCallback, useMemo } from 'react'; import { Pane } from 'react-leaflet'; import MarkerClusterGroup from 'react-leaflet-markercluster'; import { @@ -49,62 +49,36 @@ export const OrgUnitChildrenLocations: FunctionComponent = ({ childrenOrgUnit.org_unit_type_id === subType.id, ); }, [activeChildren, subType]); + + const getColor = useCallback( + (children: OrgUnit) => { + return children.id === selectedChildren?.id + ? selectedOrgUnitColor + : subType.color || ''; + }, + [selectedChildren, subType], + ); return ( {useCluster && ( - <> - - colorClusterCustomMarker( - cluster, - subType.color || '', - ) - } - polygonOptions={{ - fillColor: subType.color || '', - color: subType.color || '', - }} - key={subType.id} - > - ({ - ...circleColorMarkerOptions( - children.id === selectedChildren?.id - ? selectedOrgUnitColor - : subType.color || '', - ), - radius: 12, - })} - onDblclick={handleDoubleClick} - onMarkerClick={handleSingleClick} - tooltipProps={e => ({ - permanent: showTooltip, - pane: 'popupPane', - label: e.name, - })} - TooltipComponent={MapToolTip} - isCircle - /> - - - )} - {!useCluster && ( - <> + + colorClusterCustomMarker(cluster, subType.color || '') + } + polygonOptions={{ + fillColor: subType.color || '', + color: subType.color || '', + }} + key={subType.id} + > ({ - ...circleColorMarkerOptions( - children.id === selectedChildren?.id - ? selectedOrgUnitColor - : subType.color || '', - ), - radius: 12, + ...circleColorMarkerOptions(getColor(children), 12), })} onDblclick={handleDoubleClick} onMarkerClick={handleSingleClick} @@ -116,7 +90,25 @@ export const OrgUnitChildrenLocations: FunctionComponent = ({ TooltipComponent={MapToolTip} isCircle /> - + + )} + {!useCluster && ( + ({ + ...circleColorMarkerOptions(getColor(children), 12), + })} + onDblclick={handleDoubleClick} + onMarkerClick={handleSingleClick} + tooltipProps={e => ({ + permanent: showTooltip, + pane: 'popupPane', + label: e.name, + })} + TooltipComponent={MapToolTip} + isCircle + /> )} ); diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx index 612c341fd6..53083ffedc 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx @@ -1,5 +1,5 @@ import L from 'leaflet'; -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useMemo } from 'react'; import { GeoJSON, Pane } from 'react-leaflet'; import { OrgUnit } from '../../../orgUnits/types/orgUnit'; @@ -29,14 +29,19 @@ export const OrgUnitChildrenShapes: FunctionComponent = ({ subType, selectedChildren, }) => { - const orgUnitsShapes = activeChildren?.filter( - childrenOrgUnit => - Boolean(childrenOrgUnit.geo_json) && - childrenOrgUnit.org_unit_type_id === subType.id, + const orgUnitsShapes = useMemo( + () => + activeChildren?.filter( + childrenOrgUnit => + Boolean(childrenOrgUnit.geo_json) && + childrenOrgUnit.org_unit_type_id === subType.id, + ), + [activeChildren, subType.id], ); + return ( {orgUnitsShapes.map(childrenOrgUnit => ( @@ -51,19 +56,11 @@ export const OrgUnitChildrenShapes: FunctionComponent = ({ })} data={childrenOrgUnit.geo_json} > - {showTooltip && ( - - )} - {!showTooltip && ( - - )} + ))} diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx index 71a08169b2..9ac6a06682 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx @@ -25,33 +25,26 @@ export const OrgUnitLocation: FunctionComponent = ({ selectedChildren, }) => { const theme = useTheme(); + const color = selectedChildren + ? theme.palette.primary.main + : selectedOrgUnitColor; return ( <> {isOrgUnitActive && ( <> {orgUnit.geo_json && ( - + ({ - color: !selectedChildren - ? selectedOrgUnitColor - : theme.palette.primary.main, + color, })} > - {showTooltip && ( - - )} - {!showTooltip && ( - - )} + )} @@ -62,11 +55,7 @@ export const OrgUnitLocation: FunctionComponent = ({ longitude: orgUnit.longitude, }} markerProps={() => ({ - ...circleColorMarkerOptions( - !selectedChildren - ? selectedOrgUnitColor - : theme.palette.primary.main, - ), + ...circleColorMarkerOptions(color), key: `markers-${orgUnit.id}-${showTooltip}`, })} popupProps={() => ({ diff --git a/hat/assets/js/apps/Iaso/utils/map/mapUtils.ts b/hat/assets/js/apps/Iaso/utils/map/mapUtils.ts index 66989650d3..c25724beb7 100644 --- a/hat/assets/js/apps/Iaso/utils/map/mapUtils.ts +++ b/hat/assets/js/apps/Iaso/utils/map/mapUtils.ts @@ -1,18 +1,18 @@ -import L from 'leaflet'; -import Color from 'color'; -import orderBy from 'lodash/orderBy'; -import isNumber from 'lodash/isNumber'; import { Theme } from '@mui/material/styles'; +import Color from 'color'; +import L from 'leaflet'; import { isEqual } from 'lodash'; +import isNumber from 'lodash/isNumber'; +import orderBy from 'lodash/orderBy'; import { ScaleThreshold } from '../../components/LegendBuilder/types'; -import { OrgUnit } from '../../domains/orgUnits/types/orgUnit'; -import { OrgunitTypes } from '../../domains/orgUnits/types/orgunitTypes'; +import { CompletenessMapStats } from '../../domains/completenessStats/types'; import { AssociatedOrgUnit, MappedOrgUnit, } from '../../domains/orgUnits/components/orgUnitMap/OrgUnitMap/types'; -import { CompletenessMapStats } from '../../domains/completenessStats/types'; +import { OrgUnit } from '../../domains/orgUnits/types/orgUnit'; +import { OrgunitTypes } from '../../domains/orgUnits/types/orgunitTypes'; export const defaultCenter = [5, 20]; export const defaultZoom = 4; @@ -115,13 +115,16 @@ export const customMarker = L.divIcon(customMarkerOptions); export const circleColorMarkerOptions = ( color: string, -): Record => ({ + radius = 8, +): Record => ({ className: 'marker-custom color circle-marker', - fillColor: color, - fillOpacity: 1, - weight: 2, - color: Color(color).darken(0.5), - radius: 8, + pathOptions: { + fillColor: color, + fillOpacity: 1, + weight: 2, + color: Color(color).darken(0.5), + radius, + }, }); // Takes the value of the .orgUnit field of each org unit and copy it in either shape or location field From 330b60181ab72805ddc2bcb21f0f151f1bf37cac Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Wed, 15 May 2024 13:58:39 +0200 Subject: [PATCH 04/27] progressing on nav --- .../registry/components/OrgUnitPaper.tsx | 61 +--------------- ...gUnitInstances.tsx => SelectedOrgUnit.tsx} | 69 ++++++++++++------- .../components/map/OrgUnitChildrenMap.tsx | 30 ++++++-- .../components/map/OrgUnitLocation.tsx | 17 +++++ .../registry/hooks/useGetLegendOptions.ts | 22 +++--- .../js/apps/Iaso/domains/registry/index.tsx | 36 ++++++---- hat/assets/js/apps/Iaso/hooks/usePrevious.ts | 9 +++ 7 files changed, 131 insertions(+), 113 deletions(-) rename hat/assets/js/apps/Iaso/domains/registry/components/{OrgUnitInstances.tsx => SelectedOrgUnit.tsx} (79%) create mode 100644 hat/assets/js/apps/Iaso/hooks/usePrevious.ts diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx index 05266f6c7d..228b7570b1 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx @@ -1,15 +1,6 @@ -import AddIcon from '@mui/icons-material/Add'; -import { - Box, - Divider, - Grid, - Paper, - Tab, - Tabs, - Typography, -} from '@mui/material'; +import { Box, Paper, Tab, Tabs } from '@mui/material'; import { makeStyles } from '@mui/styles'; -import { IconButton, commonStyles, useSafeIntl } from 'bluesquare-components'; +import { commonStyles, useSafeIntl } from 'bluesquare-components'; import classnames from 'classnames'; import React, { Dispatch, @@ -62,18 +53,6 @@ const useStyles = makeStyles(theme => ({ fontSize: '1.4rem', }, }, - paperTitle: { - padding: theme.spacing(2), - display: 'flex', - }, - paperTitleButtonContainer: { - position: 'relative', - }, - paperTitleButton: { - position: 'absolute', - right: -theme.spacing(1), - top: -theme.spacing(1), - }, hiddenOpacity: { position: 'absolute', top: 0, @@ -120,42 +99,6 @@ export const OrgUnitPaper: FunctionComponent = ({ ); return ( - - - - {orgUnit.name - ? `${orgUnit.name} (${orgUnit.org_unit_type_name})` - : ''} - - - - - - - - - - ({ @@ -77,10 +77,9 @@ const useStyles = makeStyles(theme => ({ }, })); -export const OrgUnitInstances: FunctionComponent = ({ +export const SelectedOrgUnit: FunctionComponent = ({ orgUnit, params, - selectedChildren, }) => { const classes: Record = useStyles(); const dispatch = useDispatch(); @@ -90,10 +89,9 @@ export const OrgUnitInstances: FunctionComponent = ({ // submission id from params OR reference instance OR first submission of the possible ones OR undefined // if undefined select should be hidden and a place holder should say no submission // console.log('orgUnit', orgUnit); - // console.log('selectedChildren', selectedChildren); const [currentInstanceId, setCurrentInstanceId] = useState< number | string | undefined - >(params.submissionId || orgUnit.reference_instance?.id); + >(params.submissionId || orgUnit.reference_instances?.[0]?.id); const { data: currentInstance, isFetching: isFetchingCurrentInstance } = useGetInstance(currentInstanceId); @@ -118,6 +116,12 @@ export const OrgUnitInstances: FunctionComponent = ({ }; dispatch(redirectToReplace(baseUrls.registry, newParams)); }; + const isReferenceInstance = orgUnit.reference_instances?.some( + ref => ref.id === currentInstance?.id, + ); + const referenceInstanceMessage = isReferenceInstance + ? ` (${formatMessage(MESSAGES.referenceInstance)})` + : ''; useEffect(() => { if (!currentInstanceId && instances && instances?.length > 0) { setCurrentInstanceId(instances[0].id); @@ -126,18 +130,42 @@ export const OrgUnitInstances: FunctionComponent = ({ return ( {isFetchingCurrentInstance && } - {selectedChildren && ( - - + + + + {orgUnit.name} ({orgUnit.org_unit_type_name}) + + + - {selectedChildren.name} ( - {selectedChildren.org_unit_type_name}) - - - )} + + + + + + + {instances && instances?.length === 0 && ( = ({ variant="h5" className={classes.title} > - {`${currentInstance.form_name}${ - currentInstance.id === - orgUnit.reference_instance?.id - ? ` (${formatMessage( - MESSAGES.referenceInstance, - )})` - : '' - }`} + {`${currentInstance.form_name}${referenceInstanceMessage}`} = ({ ); const [currentTile, setCurrentTile] = useState(TILES.osm); const getlegendOptions = useGetlegendOptions(orgUnit); + const prevSelectedChildren = usePrevious( + selectedChildren, + ); + const [legendOptions, setLegendOptions] = useState([]); useEffect(() => { - if (legendOptions.length === 0 && subOrgUnitTypes.length > 0) { - setLegendOptions(getlegendOptions(subOrgUnitTypes)); + if ( + (legendOptions.length === 0 && subOrgUnitTypes.length > 0) || + selectedChildren?.id !== prevSelectedChildren?.id + ) { + setLegendOptions( + getlegendOptions(subOrgUnitTypes, selectedChildren), + ); } - }, [getlegendOptions, legendOptions, subOrgUnitTypes]); + }, [ + getlegendOptions, + legendOptions, + selectedChildren, + subOrgUnitTypes, + prevSelectedChildren, + ]); const optionsObject = useMemo( () => keyBy(legendOptions, 'value'), @@ -174,11 +190,9 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ const handleSingleClick = useCallback( (ou: OrgUnit, event: L.LeafletMouseEvent | undefined) => { event?.originalEvent.stopPropagation(); - setSelectedChildren( - ou.id === selectedChildren?.id ? undefined : ou, - ); + setSelectedChildren(ou.id === orgUnit.id ? undefined : ou); }, - [setSelectedChildren, selectedChildren], + [orgUnit, setSelectedChildren], ); const handleFeatureEvents = useCallback( @@ -242,6 +256,8 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ orgUnit={orgUnit} isOrgUnitActive={isOrgUnitActive} selectedChildren={selectedChildren} + handleSingleClick={handleSingleClick} + handleFeatureEvents={handleFeatureEvents} /> {subOrgUnitTypes.map((subType, index) => ( diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx index 9ac6a06682..0db3a51d65 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx @@ -1,5 +1,6 @@ import { red } from '@mui/material/colors'; import { useTheme } from '@mui/styles'; +import L from 'leaflet'; import React, { FunctionComponent } from 'react'; import { GeoJSON, Pane } from 'react-leaflet'; import { circleColorMarkerOptions } from '../../../../utils/map/mapUtils'; @@ -15,6 +16,18 @@ type Props = { showTooltip: boolean; isOrgUnitActive: boolean; selectedChildren: OrgUnit | undefined; + handleFeatureEvents: ( + // eslint-disable-next-line no-unused-vars + ou: OrgUnit, + // eslint-disable-next-line no-unused-vars + ) => (feature: any, layer: L.Layer) => void; + + handleSingleClick: ( + // eslint-disable-next-line no-unused-vars + ou: OrgUnit, + // eslint-disable-next-line no-unused-vars + event: L.LeafletMouseEvent | undefined, + ) => void; }; export const selectedOrgUnitColor = red[500]; @@ -23,6 +36,8 @@ export const OrgUnitLocation: FunctionComponent = ({ showTooltip, isOrgUnitActive, selectedChildren, + handleSingleClick, + handleFeatureEvents, }) => { const theme = useTheme(); const color = selectedChildren @@ -36,6 +51,7 @@ export const OrgUnitLocation: FunctionComponent = ({ ({ color, })} @@ -61,6 +77,7 @@ export const OrgUnitLocation: FunctionComponent = ({ popupProps={() => ({ orgUnit, })} + onClick={handleSingleClick} tooltipProps={() => ({ permanent: showTooltip, pane: 'popupPane', diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts index 571bf76015..2a23c60c10 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts @@ -1,10 +1,8 @@ -import { useSafeIntl } from 'bluesquare-components'; - +import { useTheme } from '@mui/styles'; import { OrgUnit } from '../../orgUnits/types/orgUnit'; import { OrgunitTypes } from '../../orgUnits/types/orgunitTypes'; import { selectedOrgUnitColor } from '../components/map/OrgUnitChildrenMap'; -import MESSAGES from '../messages'; export type Legend = { value: string; @@ -14,10 +12,14 @@ export type Legend = { }; export const useGetlegendOptions = ( orgUnit: OrgUnit, +): (( + // eslint-disable-next-line no-unused-vars + subOrgUnitTypes: OrgunitTypes, // eslint-disable-next-line no-unused-vars -): ((subOrgUnitTypes: OrgunitTypes) => Legend[]) => { - const { formatMessage } = useSafeIntl(); - const getLegendOptions = (subOrgUnitTypes: OrgunitTypes): Legend[] => { + selectedChildren?: OrgUnit, +) => Legend[]) => { + const theme = useTheme(); + const getLegendOptions = (subOrgUnitTypes, selectedChildren?): Legend[] => { const options = subOrgUnitTypes.map(subOuType => ({ value: `${subOuType.id}`, label: `${subOuType.name} (${subOuType.orgUnits?.length})`, @@ -25,10 +27,14 @@ export const useGetlegendOptions = ( active: true, })); if (orgUnit) { + // console.log('selectedChildren', selectedChildren); + const color = selectedChildren + ? theme.palette.primary.main + : selectedOrgUnitColor; options.unshift({ value: `${orgUnit.id}`, - label: formatMessage(MESSAGES.selectedOrgUnit), - color: selectedOrgUnitColor, + label: orgUnit.name, + color, active: true, }); } diff --git a/hat/assets/js/apps/Iaso/domains/registry/index.tsx b/hat/assets/js/apps/Iaso/domains/registry/index.tsx index ed427a972d..095a4a5bfa 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/index.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/index.tsx @@ -6,7 +6,7 @@ import { useSafeIntl, } from 'bluesquare-components'; import { orderBy } from 'lodash'; -import React, { FunctionComponent, useMemo, useState } from 'react'; +import React, { FunctionComponent, useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import TopBar from '../../components/nav/TopBarComponent'; import MESSAGES from './messages'; @@ -21,8 +21,8 @@ import { } from './hooks/useGetOrgUnit'; import { Instances } from './components/Instances'; -import { OrgUnitInstances } from './components/OrgUnitInstances'; import { OrgUnitPaper } from './components/OrgUnitPaper'; +import { SelectedOrgUnit } from './components/SelectedOrgUnit'; import { OrgunitTypeRegistry } from './types/orgunitTypes'; import { RegistryDetailParams } from './types'; @@ -93,23 +93,23 @@ export const Registry: FunctionComponent = ({ router }) => { dispatch(redirectTo(`/${baseUrls.registry}`, newParams)); } }; + useEffect(() => { + setSelectedChildren(undefined); + }, [orgUnitId]); return ( <> - + {isFetching && } - {!isFetching && orgUnit && ( - - - - )} @@ -125,6 +125,13 @@ export const Registry: FunctionComponent = ({ router }) => { {!isFetching && orgUnit && ( <> + + + = ({ router }) => { container > {orgUnit && ( - )} diff --git a/hat/assets/js/apps/Iaso/hooks/usePrevious.ts b/hat/assets/js/apps/Iaso/hooks/usePrevious.ts new file mode 100644 index 0000000000..747d12e08a --- /dev/null +++ b/hat/assets/js/apps/Iaso/hooks/usePrevious.ts @@ -0,0 +1,9 @@ +import { useEffect, useRef } from 'react'; + +export const usePrevious = (value: T): T | undefined => { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }); + return ref.current; +}; From 703e750a02fe2706c34deb3068d9b6e8aeeabd1e Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Wed, 15 May 2024 15:44:14 +0200 Subject: [PATCH 05/27] working nav --- hat/assets/js/apps/Iaso/constants/routes.js | 4 ++ .../domains/registry/components/Instances.tsx | 4 +- .../components/MissingInstanceButton.tsx | 4 +- .../components/MissingInstanceDialog.tsx | 4 +- .../components/OrgUnitChildrenList.tsx | 4 +- .../registry/components/OrgUnitPaper.tsx | 10 +-- .../registry/components/SelectedOrgUnit.tsx | 23 +++--- .../map/OrgUnitChildrenLocations.tsx | 8 +-- .../components/map/OrgUnitChildrenMap.tsx | 71 ++++++------------- .../components/map/OrgUnitChildrenShapes.tsx | 6 +- .../components/map/OrgUnitLocation.tsx | 6 +- .../hooks/useGetEmptyInstanceOrgUnits.ts | 6 +- .../registry/hooks/useGetInstances.tsx | 12 ++-- .../registry/hooks/useGetLegendOptions.ts | 68 ++++++++++-------- .../domains/registry/hooks/useGetOrgUnit.ts | 6 +- .../js/apps/Iaso/domains/registry/index.tsx | 52 ++++++++++---- .../apps/Iaso/domains/registry/types/index.ts | 3 +- hat/assets/js/apps/Iaso/hooks/usePrevious.ts | 9 --- 18 files changed, 155 insertions(+), 145 deletions(-) delete mode 100644 hat/assets/js/apps/Iaso/hooks/usePrevious.ts diff --git a/hat/assets/js/apps/Iaso/constants/routes.js b/hat/assets/js/apps/Iaso/constants/routes.js index ef8103d1ed..d4804379ad 100644 --- a/hat/assets/js/apps/Iaso/constants/routes.js +++ b/hat/assets/js/apps/Iaso/constants/routes.js @@ -541,6 +541,10 @@ export const registryPath = { isRequired: false, key: 'orgUnitId', }, + { + isRequired: false, + key: 'orgUnitChildrenId', + }, { isRequired: false, key: 'formIds', diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/Instances.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/Instances.tsx index 6463a77bb0..53db9e8632 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/Instances.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/Instances.tsx @@ -19,7 +19,7 @@ import { redirectToReplace } from '../../../routing/actions'; import { Form } from '../../forms/types/forms'; import { OrgunitType } from '../../orgUnits/types/orgunitTypes'; -import { RegistryDetailParams } from '../types'; +import { RegistryParams } from '../types'; import { OrgunitTypeRegistry } from '../types/orgunitTypes'; import { useGetForms } from '../hooks/useGetForms'; @@ -36,7 +36,7 @@ import MESSAGES from '../messages'; type Props = { isLoading: boolean; subOrgUnitTypes: OrgunitTypeRegistry[]; - params: RegistryDetailParams; + params: RegistryParams; }; export const Instances: FunctionComponent = ({ diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/MissingInstanceButton.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/MissingInstanceButton.tsx index 43bd0de844..e8c824867d 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/MissingInstanceButton.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/MissingInstanceButton.tsx @@ -5,14 +5,14 @@ import { useSafeIntl } from 'bluesquare-components'; import React, { FunctionComponent, useCallback } from 'react'; import { useDispatch } from 'react-redux'; -import { RegistryDetailParams } from '../types'; +import { RegistryParams } from '../types'; import { baseUrls } from '../../../constants/urls'; import { redirectToReplace } from '../../../routing/actions'; import MESSAGES from '../messages'; type Props = { - params: RegistryDetailParams; + params: RegistryParams; count: number; onClick: () => void; }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/MissingInstanceDialog.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/MissingInstanceDialog.tsx index 900507cf17..808817fad5 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/MissingInstanceDialog.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/MissingInstanceDialog.tsx @@ -25,7 +25,7 @@ import { baseUrls } from '../../../constants/urls'; import { redirectToReplace } from '../../../routing/actions'; import { CompletenessApiResponse } from '../../completenessStats/types'; import MESSAGES from '../messages'; -import { RegistryDetailParams } from '../types'; +import { RegistryParams } from '../types'; import { defaultSorted } from '../hooks/useGetEmptyInstanceOrgUnits'; @@ -33,7 +33,7 @@ type Props = { missingOrgUnitsData: CompletenessApiResponse; isOpen: boolean; closeDialog: () => void; - params: RegistryDetailParams; + params: RegistryParams; formId?: string; isFetching: boolean; }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenList.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenList.tsx index 485f7af8f2..76e2ac2323 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenList.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitChildrenList.tsx @@ -9,10 +9,10 @@ import { redirectToReplace } from '../../../routing/actions'; import { useGetOrgUnitsListColumns } from '../config'; import { OrgUnitListChildren } from '../hooks/useGetOrgUnit'; -import { RegistryDetailParams } from '../types'; +import { RegistryParams } from '../types'; type Props = { - params: RegistryDetailParams; + params: RegistryParams; orgUnitChildren?: OrgUnitListChildren; isFetchingChildren: boolean; }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx index 228b7570b1..1ae558b3db 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/OrgUnitPaper.tsx @@ -23,18 +23,18 @@ import { OrgUnitChildrenList } from './OrgUnitChildrenList'; import { OrgunitTypes } from '../../orgUnits/types/orgunitTypes'; import { OrgUnitListChildren } from '../hooks/useGetOrgUnit'; -import { OrgUnitListTab, RegistryDetailParams } from '../types'; +import { OrgUnitListTab, RegistryParams } from '../types'; type Props = { orgUnit: OrgUnit; subOrgUnitTypes: OrgunitTypes; - params: RegistryDetailParams; + params: RegistryParams; orgUnitListChildren?: OrgUnitListChildren; isFetchingListChildren: boolean; orgUnitMapChildren?: OrgUnit[]; isFetchingMapChildren: boolean; setSelectedChildren: Dispatch>; - selectedChildren: OrgUnit | undefined; + selectedChildrenId: string | undefined; }; const useStyles = makeStyles(theme => ({ @@ -77,7 +77,7 @@ export const OrgUnitPaper: FunctionComponent = ({ orgUnitMapChildren, isFetchingMapChildren, setSelectedChildren, - selectedChildren, + selectedChildrenId, }) => { const classes: Record = useStyles(); const [tab, setTab] = useState( @@ -123,7 +123,7 @@ export const OrgUnitPaper: FunctionComponent = ({ orgUnitChildren={orgUnitMapChildren} isFetchingChildren={isFetchingMapChildren} setSelectedChildren={setSelectedChildren} - selectedChildren={selectedChildren} + selectedChildrenId={selectedChildrenId} /> ({ @@ -80,6 +81,7 @@ const useStyles = makeStyles(theme => ({ export const SelectedOrgUnit: FunctionComponent = ({ orgUnit, params, + isFetching: isFetchingOrgUnit, }) => { const classes: Record = useStyles(); const dispatch = useDispatch(); @@ -91,12 +93,12 @@ export const SelectedOrgUnit: FunctionComponent = ({ // console.log('orgUnit', orgUnit); const [currentInstanceId, setCurrentInstanceId] = useState< number | string | undefined - >(params.submissionId || orgUnit.reference_instances?.[0]?.id); + >(params.submissionId || orgUnit?.reference_instances?.[0]?.id); const { data: currentInstance, isFetching: isFetchingCurrentInstance } = useGetInstance(currentInstanceId); - const { data: instances, isFetching } = useGetOrgUnitInstances(orgUnit.id); + const { data: instances, isFetching } = useGetOrgUnitInstances(orgUnit?.id); const instancesOptions = useMemo(() => { return (instances || []).map(instance => ({ label: `${instance.form_name} (${moment @@ -110,13 +112,13 @@ export const SelectedOrgUnit: FunctionComponent = ({ const handleChange = (_, submissionId) => { setCurrentInstanceId(submissionId); - const newParams: RegistryDetailParams = { + const newParams: RegistryParams = { ...params, submissionId, }; dispatch(redirectToReplace(baseUrls.registry, newParams)); }; - const isReferenceInstance = orgUnit.reference_instances?.some( + const isReferenceInstance = orgUnit?.reference_instances?.some( ref => ref.id === currentInstance?.id, ); const referenceInstanceMessage = isReferenceInstance @@ -127,9 +129,14 @@ export const SelectedOrgUnit: FunctionComponent = ({ setCurrentInstanceId(instances[0].id); } }, [currentInstanceId, instances]); + if (!orgUnit) { + return null; + } return ( - {isFetchingCurrentInstance && } + {(isFetchingCurrentInstance || isFetchingOrgUnit) && ( + + )} diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx index 40f7b95ed1..49d5831d15 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx @@ -15,7 +15,7 @@ import { MapToolTip } from './MapTooltip'; import { selectedOrgUnitColor } from './OrgUnitChildrenMap'; type Props = { - selectedChildren: OrgUnit | undefined; + selectedChildrenId: string | undefined; activeChildren: OrgUnit[]; handleSingleClick: ( // eslint-disable-next-line no-unused-vars @@ -39,7 +39,7 @@ export const OrgUnitChildrenLocations: FunctionComponent = ({ useCluster, index, subType, - selectedChildren, + selectedChildrenId, }) => { const orgUnitsMarkers = useMemo(() => { return activeChildren?.filter( @@ -52,11 +52,11 @@ export const OrgUnitChildrenLocations: FunctionComponent = ({ const getColor = useCallback( (children: OrgUnit) => { - return children.id === selectedChildren?.id + return `${children.id}` === selectedChildrenId ? selectedOrgUnitColor : subType.color || ''; }, - [selectedChildren, subType], + [selectedChildrenId, subType], ); return ( >; - selectedChildren: OrgUnit | undefined; + selectedChildrenId: string | undefined; }; const boundsOptions = { @@ -86,7 +84,7 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ isFetchingChildren, params, setSelectedChildren, - selectedChildren, + selectedChildrenId, }) => { const classes: Record = useStyles(); const dispatch = useDispatch(); @@ -98,46 +96,20 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ params.isFullScreen === 'true', ); const [currentTile, setCurrentTile] = useState(TILES.osm); - const getlegendOptions = useGetlegendOptions(orgUnit); - const prevSelectedChildren = usePrevious( - selectedChildren, - ); - - const [legendOptions, setLegendOptions] = useState([]); - useEffect(() => { - if ( - (legendOptions.length === 0 && subOrgUnitTypes.length > 0) || - selectedChildren?.id !== prevSelectedChildren?.id - ) { - setLegendOptions( - getlegendOptions(subOrgUnitTypes, selectedChildren), - ); - } - }, [ - getlegendOptions, - legendOptions, - selectedChildren, - subOrgUnitTypes, - prevSelectedChildren, - ]); + const { getLegendOptions, setLegendOptions } = useGetLegendOptions(orgUnit); + const legendOptions = useMemo(() => { + return getLegendOptions(subOrgUnitTypes, selectedChildrenId); + }, [getLegendOptions, selectedChildrenId, subOrgUnitTypes]); - const optionsObject = useMemo( - () => keyBy(legendOptions, 'value'), - [legendOptions], - ); - const activeChildren: OrgUnit[] = useMemo( - () => + const legendOptionsMap = keyBy(legendOptions, 'value'); + const activeChildren: OrgUnit[] = useMemo(() => { + return ( orgUnitChildren?.filter( - children => - optionsObject[`${children.org_unit_type_id}`]?.active, - ) || [], - [orgUnitChildren, optionsObject], - ); - const isOrgUnitActive: boolean = - Object.keys(optionsObject).length === 0 - ? true - : optionsObject[`${orgUnit.id}`]?.active || false; - + child => legendOptionsMap[child.org_unit_type_id]?.active, + ) || [] + ); + }, [orgUnitChildren, legendOptionsMap]); + const isOrgUnitActive = Boolean(legendOptions[0]?.active); const { showTooltip, useCluster } = settings; const bounds = useMemo( () => @@ -183,9 +155,10 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ (event: L.LeafletMouseEvent, ou: OrgUnit) => { event.originalEvent.stopPropagation(); const url = `/${baseUrls.registry}/orgUnitId/${ou?.id}`; + setSelectedChildren(undefined); dispatch(redirectTo(url)); }, - [dispatch], + [dispatch, setSelectedChildren], ); const handleSingleClick = useCallback( (ou: OrgUnit, event: L.LeafletMouseEvent | undefined) => { @@ -255,7 +228,7 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ showTooltip={showTooltip} orgUnit={orgUnit} isOrgUnitActive={isOrgUnitActive} - selectedChildren={selectedChildren} + selectedChildrenId={selectedChildrenId} handleSingleClick={handleSingleClick} handleFeatureEvents={handleFeatureEvents} /> @@ -267,7 +240,7 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ index={index} subType={subType} handleFeatureEvents={handleFeatureEvents} - selectedChildren={selectedChildren} + selectedChildrenId={selectedChildrenId} /> = ({ subType={subType} handleSingleClick={handleSingleClick} handleDoubleClick={handleDoubleClick} - selectedChildren={selectedChildren} + selectedChildrenId={selectedChildrenId} /> ))} diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx index 53083ffedc..ef40993d98 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenShapes.tsx @@ -9,7 +9,7 @@ import { MapToolTip } from './MapTooltip'; import { selectedOrgUnitColor } from './OrgUnitChildrenMap'; type Props = { - selectedChildren: OrgUnit | undefined; + selectedChildrenId: string | undefined; activeChildren: OrgUnit[]; handleFeatureEvents: ( // eslint-disable-next-line no-unused-vars @@ -27,7 +27,7 @@ export const OrgUnitChildrenShapes: FunctionComponent = ({ showTooltip, index, subType, - selectedChildren, + selectedChildrenId, }) => { const orgUnitsShapes = useMemo( () => @@ -50,7 +50,7 @@ export const OrgUnitChildrenShapes: FunctionComponent = ({ onEachFeature={handleFeatureEvents(childrenOrgUnit)} style={() => ({ color: - childrenOrgUnit.id === selectedChildren?.id + `${childrenOrgUnit.id}` === selectedChildrenId ? selectedOrgUnitColor : subType.color || '', })} diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx index 0db3a51d65..94869e345e 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitLocation.tsx @@ -15,7 +15,7 @@ type Props = { orgUnit: OrgUnit; showTooltip: boolean; isOrgUnitActive: boolean; - selectedChildren: OrgUnit | undefined; + selectedChildrenId: string | undefined; handleFeatureEvents: ( // eslint-disable-next-line no-unused-vars ou: OrgUnit, @@ -35,12 +35,12 @@ export const OrgUnitLocation: FunctionComponent = ({ orgUnit, showTooltip, isOrgUnitActive, - selectedChildren, + selectedChildrenId, handleSingleClick, handleFeatureEvents, }) => { const theme = useTheme(); - const color = selectedChildren + const color = selectedChildrenId ? theme.palette.primary.main : selectedOrgUnitColor; return ( diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetEmptyInstanceOrgUnits.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetEmptyInstanceOrgUnits.ts index ba7e6926ee..6abfe35528 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetEmptyInstanceOrgUnits.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetEmptyInstanceOrgUnits.ts @@ -3,13 +3,13 @@ import { UseQueryResult } from 'react-query'; // @ts-ignore import { getSort } from 'bluesquare-components'; -import { useSnackQuery } from '../../../libs/apiHooks'; import { getRequest } from '../../../libs/Api'; +import { useSnackQuery } from '../../../libs/apiHooks'; import { makeUrlWithParams } from '../../../libs/utils'; -import { RegistryDetailParams } from '../types'; import { CompletenessApiResponse } from '../../completenessStats/types'; +import { RegistryParams } from '../types'; type ApiParams = { org_unit_type_ids?: number; @@ -23,7 +23,7 @@ type ApiParams = { export const defaultSorted = [{ id: 'name', desc: false }]; export const useGetEmptyInstanceOrgUnits = ( - params: RegistryDetailParams, + params: RegistryParams, orgUnitTypeId?: number, ): UseQueryResult => { const apiParams: ApiParams = { diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetInstances.tsx b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetInstances.tsx index be127e2885..7aa6a7f179 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetInstances.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetInstances.tsx @@ -3,15 +3,15 @@ import { UseQueryResult } from 'react-query'; // @ts-ignore import { getSort } from 'bluesquare-components'; -import { useSnackQuery } from '../../../libs/apiHooks'; import { getRequest } from '../../../libs/Api'; +import { useSnackQuery } from '../../../libs/apiHooks'; -import { PaginatedInstances, Instance } from '../../instances/types/instance'; import { makeUrlWithParams } from '../../../libs/utils'; +import { Instance, PaginatedInstances } from '../../instances/types/instance'; -import { RegistryDetailParams } from '../types'; -import { defaultSorted } from '../config'; import { OrgUnitStatus } from '../../orgUnits/types/orgUnit'; +import { defaultSorted } from '../config'; +import { RegistryParams } from '../types'; type ApiParams = { orgUnitTypeId?: number; @@ -31,7 +31,7 @@ type InstanceApi = { }; export const useGetInstanceApi = ( - params: RegistryDetailParams, + params: RegistryParams, orgUnitTypeId?: number, orgUnitStatus?: OrgUnitStatus, ): InstanceApi => { @@ -57,7 +57,7 @@ export const useGetInstanceApi = ( }; export const useGetInstances = ( - params: RegistryDetailParams, + params: RegistryParams, orgUnitTypeId?: number, ): UseQueryResult => { const { apiParams, url } = useGetInstanceApi( diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts index 2a23c60c10..3bd593de9c 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts @@ -1,4 +1,5 @@ import { useTheme } from '@mui/styles'; +import { Dispatch, SetStateAction, useCallback, useState } from 'react'; import { OrgUnit } from '../../orgUnits/types/orgUnit'; import { OrgunitTypes } from '../../orgUnits/types/orgunitTypes'; @@ -10,35 +11,46 @@ export type Legend = { color: string; // has to be an hexa color active?: boolean; }; -export const useGetlegendOptions = ( +export const useGetLegendOptions = ( orgUnit: OrgUnit, -): (( - // eslint-disable-next-line no-unused-vars - subOrgUnitTypes: OrgunitTypes, - // eslint-disable-next-line no-unused-vars - selectedChildren?: OrgUnit, -) => Legend[]) => { +): { + getLegendOptions: ( + // eslint-disable-next-line no-unused-vars + subOrgUnitTypes: OrgunitTypes, + // eslint-disable-next-line no-unused-vars + selectedChildrenId?: string, + ) => Legend[]; + setLegendOptions: Dispatch>; + legendOptions: Legend[]; +} => { const theme = useTheme(); - const getLegendOptions = (subOrgUnitTypes, selectedChildren?): Legend[] => { - const options = subOrgUnitTypes.map(subOuType => ({ - value: `${subOuType.id}`, - label: `${subOuType.name} (${subOuType.orgUnits?.length})`, - color: subOuType.color || '', - active: true, - })); - if (orgUnit) { - // console.log('selectedChildren', selectedChildren); - const color = selectedChildren - ? theme.palette.primary.main - : selectedOrgUnitColor; - options.unshift({ - value: `${orgUnit.id}`, - label: orgUnit.name, - color, + const [legendOptions, setLegendOptions] = useState([]); + + const getLegendOptions = useCallback( + (subOrgUnitTypes: OrgunitTypes, selectedChildrenId?: string) => { + const options = subOrgUnitTypes.map(subOuType => ({ + value: `${subOuType.id}`, + label: `${subOuType.name} (${subOuType.orgUnits?.length})`, + color: subOuType.color || '', active: true, - }); - } - return options; - }; - return getLegendOptions; + })); + + if (orgUnit) { + const color = selectedChildrenId + ? theme.palette.primary.main + : selectedOrgUnitColor; + options.unshift({ + value: `${orgUnit.id}`, + label: orgUnit.name, + color, + active: true, + }); + } + setLegendOptions(options); + return options; + }, + [orgUnit, theme], + ); + + return { getLegendOptions, setLegendOptions, legendOptions }; }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts index a0cc808b13..9daa9a6001 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts @@ -7,10 +7,10 @@ import { useSnackQuery } from '../../../libs/apiHooks'; import { makeUrlWithParams } from '../../../libs/utils'; import { OrgUnit } from '../../orgUnits/types/orgUnit'; import { OrgunitTypes } from '../../orgUnits/types/orgunitTypes'; -import { RegistryDetailParams } from '../types'; +import { RegistryParams } from '../types'; export const useGetOrgUnit = ( - orgUnitId: string, + orgUnitId?: string, ): UseQueryResult => { const queryKey: any[] = ['orgUnit', orgUnitId]; return useSnackQuery({ @@ -29,7 +29,7 @@ export type OrgUnitListChildren = Pagination & { export const useGetOrgUnitListChildren = ( orgUnitParentId: string, - params: RegistryDetailParams, + params: RegistryParams, orgUnitTypes?: OrgunitTypes, ): UseQueryResult => { let order = '-name'; diff --git a/hat/assets/js/apps/Iaso/domains/registry/index.tsx b/hat/assets/js/apps/Iaso/domains/registry/index.tsx index 095a4a5bfa..8b50891415 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/index.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/index.tsx @@ -6,7 +6,7 @@ import { useSafeIntl, } from 'bluesquare-components'; import { orderBy } from 'lodash'; -import React, { FunctionComponent, useEffect, useMemo, useState } from 'react'; +import React, { FunctionComponent, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import TopBar from '../../components/nav/TopBarComponent'; import MESSAGES from './messages'; @@ -25,16 +25,16 @@ import { OrgUnitPaper } from './components/OrgUnitPaper'; import { SelectedOrgUnit } from './components/SelectedOrgUnit'; import { OrgunitTypeRegistry } from './types/orgunitTypes'; -import { RegistryDetailParams } from './types'; +import { RegistryParams } from './types'; -import { redirectTo } from '../../routing/actions'; +import { redirectTo, redirectToReplace } from '../../routing/actions'; import { OrgUnitTreeviewModal } from '../orgUnits/components/TreeView/OrgUnitTreeviewModal'; import { OrgUnitBreadcrumbs } from '../orgUnits/components/breadcrumbs/OrgUnitBreadcrumbs'; import { OrgUnit } from '../orgUnits/types/orgUnit'; type Router = { goBack: () => void; - params: RegistryDetailParams; + params: RegistryParams; }; type Props = { router: Router; @@ -46,18 +46,20 @@ const useStyles = makeStyles(theme => ({ export const Registry: FunctionComponent = ({ router }) => { const { - params: { orgUnitId }, + params: { orgUnitId, orgUnitChildrenId }, params, } = router; const dispatch = useDispatch(); - const [selectedChildren, setSelectedChildren] = useState< - OrgUnit | undefined - >(); const classes: Record = useStyles(); const { formatMessage } = useSafeIntl(); const { data: orgUnit, isFetching } = useGetOrgUnit(orgUnitId); + const [selectedChildrenId, setSelectedChildrenId] = useState< + string | undefined + >(orgUnitChildrenId); + const { data: selectedChildren, isFetching: isFetchingSelectedChildren } = + useGetOrgUnit(selectedChildrenId); const { data: orgUnitListChildren, isFetching: isFetchingListChildren } = useGetOrgUnitListChildren( orgUnitId, @@ -90,13 +92,24 @@ export const Registry: FunctionComponent = ({ router }) => { ...params, orgUnitId: `${newOrgUnit.id}`, }; + delete newParams.orgUnitChildrenId; + setSelectedChildrenId(undefined); dispatch(redirectTo(`/${baseUrls.registry}`, newParams)); } }; - useEffect(() => { - setSelectedChildren(undefined); - }, [orgUnitId]); - + const handleChildrenChange = (newChildren: OrgUnit) => { + const newParams = { + ...params, + }; + if (newChildren) { + setSelectedChildrenId(`${newChildren.id}`); + newParams.orgUnitChildrenId = `${newChildren.id}`; + } else { + setSelectedChildrenId(undefined); + delete newParams.orgUnitChildrenId; + } + dispatch(redirectToReplace(`/${baseUrls.registry}`, newParams)); + }; return ( <> = ({ router }) => { isFetchingMapChildren={ isFetchingMapChildren } - setSelectedChildren={setSelectedChildren} - selectedChildren={selectedChildren} + setSelectedChildren={handleChildrenChange} + selectedChildrenId={selectedChildrenId} /> = ({ router }) => { > {orgUnit && ( )} diff --git a/hat/assets/js/apps/Iaso/domains/registry/types/index.ts b/hat/assets/js/apps/Iaso/domains/registry/types/index.ts index 0b46531da5..7f3d724f93 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/types/index.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/types/index.ts @@ -2,8 +2,9 @@ import { UrlParams } from 'bluesquare-components'; export type OrgUnitListTab = 'map' | 'list'; -export type RegistryDetailParams = UrlParams & { +export type RegistryParams = UrlParams & { orgUnitId: string; + orgUnitChildrenId?: string; accountId: string; formIds?: string; planningIds?: string; diff --git a/hat/assets/js/apps/Iaso/hooks/usePrevious.ts b/hat/assets/js/apps/Iaso/hooks/usePrevious.ts deleted file mode 100644 index 747d12e08a..0000000000 --- a/hat/assets/js/apps/Iaso/hooks/usePrevious.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useEffect, useRef } from 'react'; - -export const usePrevious = (value: T): T | undefined => { - const ref = useRef(); - useEffect(() => { - ref.current = value; - }); - return ref.current; -}; From f5af079483d308bc89b17a5cd7362db780b0bcb9 Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Wed, 15 May 2024 16:59:25 +0200 Subject: [PATCH 06/27] fix height --- .../instances/components/LinkToInstance.tsx | 4 +- .../registry/components/SelectedOrgUnit.tsx | 181 ++++++++++-------- .../components/map/OrgUnitChildrenMap.tsx | 13 +- .../js/apps/Iaso/domains/registry/config.tsx | 10 +- .../js/apps/Iaso/domains/registry/index.tsx | 4 +- 5 files changed, 117 insertions(+), 95 deletions(-) diff --git a/hat/assets/js/apps/Iaso/domains/instances/components/LinkToInstance.tsx b/hat/assets/js/apps/Iaso/domains/instances/components/LinkToInstance.tsx index 6698b8f524..275fa5787a 100644 --- a/hat/assets/js/apps/Iaso/domains/instances/components/LinkToInstance.tsx +++ b/hat/assets/js/apps/Iaso/domains/instances/components/LinkToInstance.tsx @@ -2,11 +2,11 @@ import React, { FunctionComponent } from 'react'; import { IconButton as IconButtonComponent } from 'bluesquare-components'; import { Link } from 'react-router'; -import { userHasPermission } from '../../users/utils'; import { baseUrls } from '../../../constants/urls'; +import * as Permission from '../../../utils/permissions'; import { useCurrentUser } from '../../../utils/usersUtils'; import MESSAGES from '../../assignments/messages'; -import * as Permission from '../../../utils/permissions'; +import { userHasPermission } from '../../users/utils'; type Props = { instanceId: string; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx index d84a07073a..9d926d3cf1 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx @@ -32,6 +32,7 @@ import { import * as Permission from '../../../utils/permissions'; import { LinkToInstance } from '../../instances/components/LinkToInstance'; import { OrgUnit } from '../../orgUnits/types/orgUnit'; +import { HEIGHT } from '../config'; import { RegistryParams } from '../types'; type Props = { @@ -44,13 +45,17 @@ const useStyles = makeStyles(theme => ({ ...commonStyles(theme), paper: { width: '100%', + height: HEIGHT, + }, + subTitle: { + fontSize: '1.15rem', }, formContents: { - maxHeight: '485px', + maxHeight: `calc(${HEIGHT} - 222px)`, overflow: 'auto', }, emptyPaper: { - height: '636px', + height: '527px', display: 'flex', justifyContent: 'center', alignItems: 'center', @@ -133,7 +138,7 @@ export const SelectedOrgUnit: FunctionComponent = ({ return null; } return ( - + {(isFetchingCurrentInstance || isFetchingOrgUnit) && ( )} @@ -166,96 +171,108 @@ export const SelectedOrgUnit: FunctionComponent = ({ - - {instances && instances?.length === 0 && ( - - + {instances && instances?.length === 0 && ( + + + + {formatMessage(MESSAGES.noInstance)} + + + )} + {instances && instances?.length > 0 && ( + - - {formatMessage(MESSAGES.noInstance)} - - - )} - {instances && instances?.length > 0 && ( - - - + + + - - )} - {currentInstance && ( - - - - 0 && currentInstance && ( + + )} + {currentInstance && ( + <> + + + + {`${currentInstance.form_name}${referenceInstanceMessage}`} + + + - {`${currentInstance.form_name}${referenceInstanceMessage}`} - - - - - {userHasPermission( - Permission.SUBMISSIONS_UPDATE, - currentUser, - ) && ( - getEnketoUrl()} - overrideIcon={EnketoIcon} + + {userHasPermission( + Permission.SUBMISSIONS_UPDATE, + currentUser, + ) && ( + getEnketoUrl()} + overrideIcon={EnketoIcon} + color="secondary" + tooltipMessage={ + MESSAGES.editOnEnketo + } + /> + )} + - )} - - + + - - - - - - - )} + + + + + + )} + ); }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx index 42d1ffc09e..7fc3ee8940 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx @@ -35,6 +35,7 @@ import { CustomZoomControl } from '../../../../components/maps/tools/CustomZoomC import TILES from '../../../../constants/mapTiles'; import { baseUrls } from '../../../../constants/urls'; import { redirectTo, redirectToReplace } from '../../../../routing/actions'; +import { HEIGHT } from '../../config'; import { RegistryParams } from '../../types'; import { MapSettings, Settings } from './MapSettings'; import { OrgUnitChildrenLocations } from './OrgUnitChildrenLocations'; @@ -54,11 +55,13 @@ type Props = { const boundsOptions = { padding: [50, 50], }; - +const tabsHeight = '50px'; +const mapHeight = `calc(${HEIGHT} - ${tabsHeight})`; const useStyles = makeStyles(theme => ({ mapContainer: { ...commonStyles(theme).mapContainer, - height: '542px', + height: mapHeight, + marginBottom: 0, position: 'relative', '& .leaflet-control-zoom': { borderBottom: 'none', @@ -71,7 +74,7 @@ const useStyles = makeStyles(theme => ({ top: '64px', left: '0', width: '100vw', - height: 'calc(100vh - 64px)', + height: '100vh', zIndex: 10000, }, })); @@ -177,7 +180,7 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ ); if (isFetchingChildren) return ( - + ); @@ -194,7 +197,7 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ doubleClickZoom={false} maxZoom={currentTile.maxZoom} style={{ - minHeight: '542px', + minHeight: mapHeight, height: '100%', }} center={[1, 20]} diff --git a/hat/assets/js/apps/Iaso/domains/registry/config.tsx b/hat/assets/js/apps/Iaso/domains/registry/config.tsx index ebc334308a..9375d6ed31 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/config.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/config.tsx @@ -1,19 +1,21 @@ -import React, { ReactElement } from 'react'; -import { useSafeIntl, IntlFormatMessage, Column } from 'bluesquare-components'; -import { Box } from '@mui/material'; import MapIcon from '@mui/icons-material/Map'; +import { Box } from '@mui/material'; +import { Column, IntlFormatMessage, useSafeIntl } from 'bluesquare-components'; +import React, { ReactElement } from 'react'; import { InstanceMetasField } from '../instances/components/ColumnSelect'; import { Instance } from '../instances/types/instance'; import { LinkToRegistry } from './components/LinkToRegistry'; -import MESSAGES from './messages'; import { LinkToOrgUnit } from '../orgUnits/components/LinkToOrgUnit'; import { OrgUnitLocationIcon } from '../orgUnits/components/OrgUnitLocationIcon'; +import MESSAGES from './messages'; export const defaultSorted = [{ id: 'org_unit__name', desc: false }]; +export const HEIGHT = '62vh'; + export const INSTANCE_METAS_FIELDS: InstanceMetasField[] = [ { key: 'uuid', diff --git a/hat/assets/js/apps/Iaso/domains/registry/index.tsx b/hat/assets/js/apps/Iaso/domains/registry/index.tsx index 8b50891415..e6502d5d46 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/index.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/index.tsx @@ -142,7 +142,7 @@ export const Registry: FunctionComponent = ({ router }) => { @@ -188,7 +188,7 @@ export const Registry: FunctionComponent = ({ router }) => { )} - + Date: Wed, 15 May 2024 17:18:14 +0200 Subject: [PATCH 07/27] lay-out --- .../js/apps/Iaso/domains/registry/index.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/hat/assets/js/apps/Iaso/domains/registry/index.tsx b/hat/assets/js/apps/Iaso/domains/registry/index.tsx index e6502d5d46..1b48b58b42 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/index.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/index.tsx @@ -117,14 +117,25 @@ export const Registry: FunctionComponent = ({ router }) => { orgUnit ? `: ${orgUnit.name} (${orgUnit.org_unit_type_name})` : '' - }}`} + }`} /> {isFetching && } - + {orgUnitId && ( + + {!isFetching && orgUnit && ( + + )} + + )} + = ({ router }) => { {!isFetching && orgUnit && ( <> - - - Date: Thu, 16 May 2024 11:01:28 +0200 Subject: [PATCH 08/27] small changes on org unit count and icons --- .../instances/components/LinkToInstance.tsx | 6 ++++ .../registry/components/LinkToRegistry.tsx | 3 ++ .../registry/components/SelectedOrgUnit.tsx | 33 +++++++++++++++---- .../registry/hooks/useGetLegendOptions.ts | 23 +++++++++---- hat/assets/js/apps/Iaso/utils/map/mapUtils.ts | 7 ++++ 5 files changed, 60 insertions(+), 12 deletions(-) diff --git a/hat/assets/js/apps/Iaso/domains/instances/components/LinkToInstance.tsx b/hat/assets/js/apps/Iaso/domains/instances/components/LinkToInstance.tsx index 275fa5787a..7db273c171 100644 --- a/hat/assets/js/apps/Iaso/domains/instances/components/LinkToInstance.tsx +++ b/hat/assets/js/apps/Iaso/domains/instances/components/LinkToInstance.tsx @@ -12,11 +12,15 @@ type Props = { instanceId: string; useIcon?: boolean; color?: string; + iconSize?: 'small' | 'medium' | 'large' | 'default' | 'inherit'; + size?: 'small' | 'medium' | 'large' | 'default' | 'inherit'; }; export const LinkToInstance: FunctionComponent = ({ instanceId, useIcon = false, color = 'inherit', + iconSize = 'medium', + size = 'medium', }) => { const user = useCurrentUser(); if (userHasPermission(Permission.SUBMISSIONS, user)) { @@ -28,6 +32,8 @@ export const LinkToInstance: FunctionComponent = ({ url={formUrl} tooltipMessage={MESSAGES.details} color={color} + iconSize={iconSize} + size={size} /> ); } diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/LinkToRegistry.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/LinkToRegistry.tsx index 2c282d5b00..4ce9fc25a6 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/LinkToRegistry.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/LinkToRegistry.tsx @@ -26,6 +26,7 @@ type Props = { replace?: boolean; iconSize?: 'small' | 'medium' | 'large' | 'default' | 'inherit'; size?: 'small' | 'medium' | 'large' | 'default' | 'inherit'; + color?: string; }; const useStyles = makeStyles(() => ({ @@ -41,6 +42,7 @@ export const LinkToRegistry: FunctionComponent = ({ replace = false, iconSize = 'medium', size = 'medium', + color = 'inherit', }) => { const user = useCurrentUser(); @@ -66,6 +68,7 @@ export const LinkToRegistry: FunctionComponent = ({ tooltipMessage={MESSAGES.seeRegistry} iconSize={iconSize} size={size} + color={color} /> ); } diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx index 9d926d3cf1..27a8947090 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx @@ -34,6 +34,7 @@ import { LinkToInstance } from '../../instances/components/LinkToInstance'; import { OrgUnit } from '../../orgUnits/types/orgUnit'; import { HEIGHT } from '../config'; import { RegistryParams } from '../types'; +import { LinkToRegistry } from './LinkToRegistry'; type Props = { orgUnit?: OrgUnit; @@ -112,6 +113,7 @@ export const SelectedOrgUnit: FunctionComponent = ({ value: instance.id, })); }, [instances]); + const isRootOrgUnit = params.orgUnitId === `${orgUnit?.id}`; const getEnketoUrl = useGetEnketoUrl(window.location.href, currentInstance); const currentUser = useCurrentUser(); @@ -162,18 +164,33 @@ export const SelectedOrgUnit: FunctionComponent = ({ className={classes.paperTitleButtonContainer} > - + {isRootOrgUnit && ( + + )} + {!isRootOrgUnit && ( + + )} @@ -250,6 +267,8 @@ export const SelectedOrgUnit: FunctionComponent = ({ onClick={() => getEnketoUrl()} overrideIcon={EnketoIcon} color="secondary" + iconSize="small" + size="small" tooltipMessage={ MESSAGES.editOnEnketo } @@ -259,6 +278,8 @@ export const SelectedOrgUnit: FunctionComponent = ({ instanceId={`${currentInstance.id}`} useIcon color="secondary" + iconSize="small" + size="small" /> diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts index 3bd593de9c..9b81da48bd 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts @@ -3,6 +3,7 @@ import { Dispatch, SetStateAction, useCallback, useState } from 'react'; import { OrgUnit } from '../../orgUnits/types/orgUnit'; import { OrgunitTypes } from '../../orgUnits/types/orgunitTypes'; +import { hasLocation } from '../../../utils/map/mapUtils'; import { selectedOrgUnitColor } from '../components/map/OrgUnitChildrenMap'; export type Legend = { @@ -28,12 +29,22 @@ export const useGetLegendOptions = ( const getLegendOptions = useCallback( (subOrgUnitTypes: OrgunitTypes, selectedChildrenId?: string) => { - const options = subOrgUnitTypes.map(subOuType => ({ - value: `${subOuType.id}`, - label: `${subOuType.name} (${subOuType.orgUnits?.length})`, - color: subOuType.color || '', - active: true, - })); + const options = subOrgUnitTypes.map(subOuType => { + console.log('subOuType.orgUnits?', subOuType.orgUnits); + const orgUnitWithLocationsCount = + subOuType.orgUnits?.filter(hasLocation).length || 0; + const orgUnitCount = subOuType.orgUnits?.length || 0; + const labelCount = + orgUnitCount === orgUnitWithLocationsCount + ? `${orgUnitCount}` + : `${orgUnitWithLocationsCount}/${orgUnitCount}`; + return { + value: `${subOuType.id}`, + label: `${subOuType.name} (${labelCount})`, + color: subOuType.color || '', + active: true, + }; + }); if (orgUnit) { const color = selectedChildrenId diff --git a/hat/assets/js/apps/Iaso/utils/map/mapUtils.ts b/hat/assets/js/apps/Iaso/utils/map/mapUtils.ts index c25724beb7..77b0b49d91 100644 --- a/hat/assets/js/apps/Iaso/utils/map/mapUtils.ts +++ b/hat/assets/js/apps/Iaso/utils/map/mapUtils.ts @@ -252,3 +252,10 @@ export const getEffectiveThreshold = ( threshold?: ScaleThreshold, ): ScaleThreshold => !threshold || isEqual(threshold, {}) ? defaultScaleThreshold : threshold; + +export const hasLocation = (orgUnit: OrgUnit): boolean => + orgUnit.has_geo_json || + (orgUnit.latitude !== undefined && + orgUnit.longitude !== undefined && + orgUnit.latitude !== null && + orgUnit.longitude !== null); From 5f3215a8e77f37d460a933e4c8c761a90d6b8275 Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Thu, 16 May 2024 11:46:31 +0200 Subject: [PATCH 09/27] splitting code --- .../registry/components/SelectedOrgUnit.tsx | 299 ------------------ .../selectedOrgUnit/EmptyInstances.tsx | 36 +++ .../selectedOrgUnit/InstanceTitle.tsx | 101 ++++++ .../selectedOrgUnit/OrgUnitTitle.tsx | 89 ++++++ .../components/selectedOrgUnit/index.tsx | 150 +++++++++ .../registry/hooks/useGetLegendOptions.ts | 1 - .../js/apps/Iaso/domains/registry/index.tsx | 2 +- 7 files changed, 377 insertions(+), 301 deletions(-) delete mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx create mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/EmptyInstances.tsx create mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/InstanceTitle.tsx create mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/OrgUnitTitle.tsx create mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx deleted file mode 100644 index 27a8947090..0000000000 --- a/hat/assets/js/apps/Iaso/domains/registry/components/SelectedOrgUnit.tsx +++ /dev/null @@ -1,299 +0,0 @@ -import AddIcon from '@mui/icons-material/Add'; -import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; -import { Box, Divider, Grid, Paper, Typography } from '@mui/material'; -import { makeStyles } from '@mui/styles'; -import { - IconButton, - LoadingSpinner, - commonStyles, - useSafeIntl, -} from 'bluesquare-components'; -import moment from 'moment'; -import React, { FunctionComponent, useEffect, useMemo, useState } from 'react'; -import { useDispatch } from 'react-redux'; - -import { baseUrls } from '../../../constants/urls'; -import MESSAGES from '../messages'; - -import { redirectToReplace } from '../../../routing/actions'; -import { useCurrentUser } from '../../../utils/usersUtils'; -import { useGetEnketoUrl } from '../hooks/useGetEnketoUrl'; - -import InputComponent from '../../../components/forms/InputComponent'; -import EnketoIcon from '../../instances/components/EnketoIcon'; -import InstanceFileContent from '../../instances/components/InstanceFileContent'; - -import { userHasPermission } from '../../users/utils'; -import { - useGetInstance, - useGetOrgUnitInstances, -} from '../hooks/useGetInstances'; - -import * as Permission from '../../../utils/permissions'; -import { LinkToInstance } from '../../instances/components/LinkToInstance'; -import { OrgUnit } from '../../orgUnits/types/orgUnit'; -import { HEIGHT } from '../config'; -import { RegistryParams } from '../types'; -import { LinkToRegistry } from './LinkToRegistry'; - -type Props = { - orgUnit?: OrgUnit; - params: RegistryParams; - isFetching: boolean; -}; - -const useStyles = makeStyles(theme => ({ - ...commonStyles(theme), - paper: { - width: '100%', - height: HEIGHT, - }, - subTitle: { - fontSize: '1.15rem', - }, - formContents: { - maxHeight: `calc(${HEIGHT} - 222px)`, - overflow: 'auto', - }, - emptyPaper: { - height: '527px', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }, - emptyPaperTypo: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }, - emptyPaperIcon: { - display: 'inline-block', - marginRight: theme.spacing(1), - }, - paperTitle: { - padding: theme.spacing(2), - display: 'flex', - }, - paperTitleButtonContainer: { - position: 'relative', - }, - paperTitleButton: { - position: 'absolute', - right: -theme.spacing(1), - top: -theme.spacing(1), - }, -})); - -export const SelectedOrgUnit: FunctionComponent = ({ - orgUnit, - params, - isFetching: isFetchingOrgUnit, -}) => { - const classes: Record = useStyles(); - const dispatch = useDispatch(); - const { formatMessage } = useSafeIntl(); - - // selected instance should be: - // submission id from params OR reference instance OR first submission of the possible ones OR undefined - // if undefined select should be hidden and a place holder should say no submission - // console.log('orgUnit', orgUnit); - const [currentInstanceId, setCurrentInstanceId] = useState< - number | string | undefined - >(params.submissionId || orgUnit?.reference_instances?.[0]?.id); - - const { data: currentInstance, isFetching: isFetchingCurrentInstance } = - useGetInstance(currentInstanceId); - - const { data: instances, isFetching } = useGetOrgUnitInstances(orgUnit?.id); - const instancesOptions = useMemo(() => { - return (instances || []).map(instance => ({ - label: `${instance.form_name} (${moment - .unix(instance.created_at) - .format('L')})`, - value: instance.id, - })); - }, [instances]); - const isRootOrgUnit = params.orgUnitId === `${orgUnit?.id}`; - const getEnketoUrl = useGetEnketoUrl(window.location.href, currentInstance); - const currentUser = useCurrentUser(); - - const handleChange = (_, submissionId) => { - setCurrentInstanceId(submissionId); - const newParams: RegistryParams = { - ...params, - submissionId, - }; - dispatch(redirectToReplace(baseUrls.registry, newParams)); - }; - const isReferenceInstance = orgUnit?.reference_instances?.some( - ref => ref.id === currentInstance?.id, - ); - const referenceInstanceMessage = isReferenceInstance - ? ` (${formatMessage(MESSAGES.referenceInstance)})` - : ''; - useEffect(() => { - if (!currentInstanceId && instances && instances?.length > 0) { - setCurrentInstanceId(instances[0].id); - } - }, [currentInstanceId, instances]); - if (!orgUnit) { - return null; - } - return ( - - {(isFetchingCurrentInstance || isFetchingOrgUnit) && ( - - )} - - - - - - {orgUnit.name} ({orgUnit.org_unit_type_name}) - - - - - {isRootOrgUnit && ( - - )} - - {!isRootOrgUnit && ( - - )} - - - - - {instances && instances?.length === 0 && ( - - - - {formatMessage(MESSAGES.noInstance)} - - - )} - {instances && instances?.length > 0 && ( - - - - - - )} - - {instances && instances?.length > 0 && currentInstance && ( - - )} - {currentInstance && ( - <> - - - - {`${currentInstance.form_name}${referenceInstanceMessage}`} - - - - - {userHasPermission( - Permission.SUBMISSIONS_UPDATE, - currentUser, - ) && ( - getEnketoUrl()} - overrideIcon={EnketoIcon} - color="secondary" - iconSize="small" - size="small" - tooltipMessage={ - MESSAGES.editOnEnketo - } - /> - )} - - - - - - - - - - )} - - - ); -}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/EmptyInstances.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/EmptyInstances.tsx new file mode 100644 index 0000000000..e62c105c1d --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/EmptyInstances.tsx @@ -0,0 +1,36 @@ +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import { Box, Typography } from '@mui/material'; +import { makeStyles } from '@mui/styles'; +import { useSafeIntl } from 'bluesquare-components'; +import React, { FunctionComponent } from 'react'; + +import MESSAGES from '../../messages'; + + +const useStyles = makeStyles(() => ({ + emptyPaper: { + height: '527px', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + emptyPaperTypo: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, +})); + +export const EmptyInstances: FunctionComponent = () => { + const classes: Record = useStyles(); + const { formatMessage } = useSafeIntl(); + + return ( + + + + {formatMessage(MESSAGES.noInstance)} + + + ); +}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/InstanceTitle.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/InstanceTitle.tsx new file mode 100644 index 0000000000..d4bbf12145 --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/InstanceTitle.tsx @@ -0,0 +1,101 @@ +import { Box, Grid, Typography } from '@mui/material'; +import { makeStyles } from '@mui/styles'; +import { IconButton, commonStyles, useSafeIntl } from 'bluesquare-components'; +import React, { FunctionComponent } from 'react'; + +import MESSAGES from '../../messages'; + +import { useCurrentUser } from '../../../../utils/usersUtils'; +import { useGetEnketoUrl } from '../../hooks/useGetEnketoUrl'; + +import EnketoIcon from '../../../instances/components/EnketoIcon'; + +import { userHasPermission } from '../../../users/utils'; + +import * as Permission from '../../../../utils/permissions'; +import { LinkToInstance } from '../../../instances/components/LinkToInstance'; +import { Instance } from '../../../instances/types/instance'; +import { OrgUnit } from '../../../orgUnits/types/orgUnit'; + +type Props = { + orgUnit?: OrgUnit; + currentInstance: Instance; +}; + +const useStyles = makeStyles(theme => ({ + ...commonStyles(theme), + subTitle: { + fontSize: '1.15rem', + }, + paperTitle: { + padding: theme.spacing(2), + display: 'flex', + }, + paperTitleButtonContainer: { + position: 'relative', + }, + paperTitleButton: { + position: 'absolute', + right: -theme.spacing(1), + top: -theme.spacing(1), + }, +})); + +export const InstanceTitle: FunctionComponent = ({ + orgUnit, + currentInstance, +}) => { + const classes: Record = useStyles(); + const { formatMessage } = useSafeIntl(); + const getEnketoUrl = useGetEnketoUrl(window.location.href, currentInstance); + const currentUser = useCurrentUser(); + const isReferenceInstance = orgUnit?.reference_instances?.some( + ref => ref.id === currentInstance?.id, + ); + const referenceInstanceMessage = isReferenceInstance + ? ` (${formatMessage(MESSAGES.referenceInstance)})` + : ''; + return ( + + + + {`${currentInstance.form_name}${referenceInstanceMessage}`} + + + + + {userHasPermission( + Permission.SUBMISSIONS_UPDATE, + currentUser, + ) && ( + getEnketoUrl()} + overrideIcon={EnketoIcon} + color="secondary" + iconSize="small" + size="small" + tooltipMessage={MESSAGES.editOnEnketo} + /> + )} + + + + + ); +}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/OrgUnitTitle.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/OrgUnitTitle.tsx new file mode 100644 index 0000000000..cd8d1dff81 --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/OrgUnitTitle.tsx @@ -0,0 +1,89 @@ +import AddIcon from '@mui/icons-material/Add'; +import { Box, Grid, Typography } from '@mui/material'; +import { makeStyles } from '@mui/styles'; +import { IconButton, commonStyles } from 'bluesquare-components'; +import React, { FunctionComponent } from 'react'; + +import { baseUrls } from '../../../../constants/urls'; +import MESSAGES from '../../messages'; + +import { OrgUnit } from '../../../orgUnits/types/orgUnit'; +import { RegistryParams } from '../../types'; +import { LinkToRegistry } from '../LinkToRegistry'; + +type Props = { + orgUnit: OrgUnit; + params: RegistryParams; +}; + +const useStyles = makeStyles(theme => ({ + ...commonStyles(theme), + paperTitle: { + padding: theme.spacing(2), + display: 'flex', + }, + paperTitleButtonContainer: { + position: 'relative', + }, + paperTitleButton: { + position: 'absolute', + right: -theme.spacing(1), + top: -theme.spacing(1), + }, +})); + +export const OrgUnitTitle: FunctionComponent = ({ orgUnit, params }) => { + const classes: Record = useStyles(); + + const isRootOrgUnit = params.orgUnitId === `${orgUnit?.id}`; + return ( + + + + {orgUnit.name} ({orgUnit.org_unit_type_name}) + + + + + {isRootOrgUnit && ( + + )} + + {!isRootOrgUnit && ( + + )} + + + + ); +}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx new file mode 100644 index 0000000000..e68ae412c0 --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx @@ -0,0 +1,150 @@ +import { Box, Divider, Paper } from '@mui/material'; +import { makeStyles } from '@mui/styles'; +import { LoadingSpinner, commonStyles } from 'bluesquare-components'; +import moment from 'moment'; +import React, { FunctionComponent, useCallback, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; + +import { baseUrls } from '../../../../constants/urls'; +import MESSAGES from '../../messages'; + +import { redirectToReplace } from '../../../../routing/actions'; + +import InputComponent from '../../../../components/forms/InputComponent'; +import InstanceFileContent from '../../../instances/components/InstanceFileContent'; + +import { + useGetInstance, + useGetOrgUnitInstances, +} from '../../hooks/useGetInstances'; + +import { OrgUnit } from '../../../orgUnits/types/orgUnit'; +import { HEIGHT } from '../../config'; +import { RegistryParams } from '../../types'; +import { EmptyInstances } from './EmptyInstances'; +import { InstanceTitle } from './InstanceTitle'; +import { OrgUnitTitle } from './OrgUnitTitle'; + +type Props = { + orgUnit?: OrgUnit; + params: RegistryParams; + isFetching: boolean; +}; + +const useStyles = makeStyles(theme => ({ + ...commonStyles(theme), + paper: { + width: '100%', + height: HEIGHT, + }, + formContents: { + maxHeight: `calc(${HEIGHT} - 222px)`, + overflow: 'auto', + }, +})); + +export const SelectedOrgUnit: FunctionComponent = ({ + orgUnit, + params, + isFetching: isFetchingOrgUnit, +}) => { + const classes: Record = useStyles(); + const dispatch = useDispatch(); + + // selected instance should be: + // submission id from params OR reference instance OR first submission of the possible ones OR undefined + // if undefined select should be hidden and a place holder should say no submission + + const currentInstanceId = useMemo(() => { + return params.submissionId || orgUnit?.reference_instances?.[0]?.id; + }, [params.submissionId, orgUnit]); + + const { data: currentInstance, isFetching: isFetchingCurrentInstance } = + useGetInstance(currentInstanceId); + const { data: instances, isFetching } = useGetOrgUnitInstances(orgUnit?.id); + const instancesOptions = useMemo(() => { + return (instances || []).map(instance => ({ + label: `${instance.form_name} (${moment + .unix(instance.created_at) + .format('LTS')})`, + value: instance.id, + })); + }, [instances]); + + const handleChange = useCallback( + (_, submissionId) => { + const newParams: RegistryParams = { + ...params, + submissionId, + }; + dispatch(redirectToReplace(baseUrls.registry, newParams)); + }, + [params, dispatch], + ); + if (!orgUnit) { + return null; + } + + // console.log('currentInstance', currentInstance); + // console.log('currentInstanceId', currentInstanceId); + // console.log('instances', instances); + return ( + + {(isFetchingCurrentInstance || isFetchingOrgUnit) && ( + + )} + + + + + {instances && instances?.length === 0 && } + {instances && instances?.length > 0 && ( + + + + + + )} + + {instances && instances?.length > 0 && currentInstance && ( + + )} + {currentInstance && ( + <> + + + + + + + )} + + + ); +}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts index 9b81da48bd..6b2d339fbc 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts @@ -30,7 +30,6 @@ export const useGetLegendOptions = ( const getLegendOptions = useCallback( (subOrgUnitTypes: OrgunitTypes, selectedChildrenId?: string) => { const options = subOrgUnitTypes.map(subOuType => { - console.log('subOuType.orgUnits?', subOuType.orgUnits); const orgUnitWithLocationsCount = subOuType.orgUnits?.filter(hasLocation).length || 0; const orgUnitCount = subOuType.orgUnits?.length || 0; diff --git a/hat/assets/js/apps/Iaso/domains/registry/index.tsx b/hat/assets/js/apps/Iaso/domains/registry/index.tsx index 1b48b58b42..c25002a466 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/index.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/index.tsx @@ -22,7 +22,6 @@ import { import { Instances } from './components/Instances'; import { OrgUnitPaper } from './components/OrgUnitPaper'; -import { SelectedOrgUnit } from './components/SelectedOrgUnit'; import { OrgunitTypeRegistry } from './types/orgunitTypes'; import { RegistryParams } from './types'; @@ -31,6 +30,7 @@ import { redirectTo, redirectToReplace } from '../../routing/actions'; import { OrgUnitTreeviewModal } from '../orgUnits/components/TreeView/OrgUnitTreeviewModal'; import { OrgUnitBreadcrumbs } from '../orgUnits/components/breadcrumbs/OrgUnitBreadcrumbs'; import { OrgUnit } from '../orgUnits/types/orgUnit'; +import { SelectedOrgUnit } from './components/selectedOrgUnit'; type Router = { goBack: () => void; From a8e8f691e879634addd3699781a5367ae387e7f1 Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Thu, 16 May 2024 11:51:43 +0200 Subject: [PATCH 10/27] splittin filters too --- .../components/selectedOrgUnit/Filters.tsx | 79 +++++++++++++++++++ .../components/selectedOrgUnit/index.tsx | 61 ++------------ 2 files changed, 87 insertions(+), 53 deletions(-) create mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/Filters.tsx diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/Filters.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/Filters.tsx new file mode 100644 index 0000000000..c79696765d --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/Filters.tsx @@ -0,0 +1,79 @@ +import { Box } from '@mui/material'; +import moment from 'moment'; +import React, { FunctionComponent, useCallback, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; + +import { baseUrls } from '../../../../constants/urls'; +import MESSAGES from '../../messages'; + +import { redirectToReplace } from '../../../../routing/actions'; + +import InputComponent from '../../../../components/forms/InputComponent'; + +import { Instance } from '../../../instances/types/instance'; +import { OrgUnit } from '../../../orgUnits/types/orgUnit'; +import { RegistryParams } from '../../types'; + +type Props = { + orgUnit?: OrgUnit; + params: RegistryParams; + instances: Instance[]; + isFetching: boolean; +}; + +export const Filters: FunctionComponent = ({ + orgUnit, + params, + instances, + isFetching, +}) => { + const dispatch = useDispatch(); + + const currentInstanceId = useMemo(() => { + return params.submissionId || orgUnit?.reference_instances?.[0]?.id; + }, [params.submissionId, orgUnit]); + + const instancesOptions = useMemo(() => { + return (instances || []).map(instance => ({ + label: `${instance.form_name} (${moment + .unix(instance.created_at) + .format('LTS')})`, + value: instance.id, + })); + }, [instances]); + + const handleChange = useCallback( + (_, submissionId) => { + const newParams: RegistryParams = { + ...params, + submissionId, + }; + dispatch(redirectToReplace(baseUrls.registry, newParams)); + }, + [params, dispatch], + ); + + return ( + + + + + + ); +}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx index e68ae412c0..10e641e17c 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx @@ -1,16 +1,8 @@ import { Box, Divider, Paper } from '@mui/material'; import { makeStyles } from '@mui/styles'; import { LoadingSpinner, commonStyles } from 'bluesquare-components'; -import moment from 'moment'; -import React, { FunctionComponent, useCallback, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; +import React, { FunctionComponent, useMemo } from 'react'; -import { baseUrls } from '../../../../constants/urls'; -import MESSAGES from '../../messages'; - -import { redirectToReplace } from '../../../../routing/actions'; - -import InputComponent from '../../../../components/forms/InputComponent'; import InstanceFileContent from '../../../instances/components/InstanceFileContent'; import { @@ -22,6 +14,7 @@ import { OrgUnit } from '../../../orgUnits/types/orgUnit'; import { HEIGHT } from '../../config'; import { RegistryParams } from '../../types'; import { EmptyInstances } from './EmptyInstances'; +import { Filters } from './Filters'; import { InstanceTitle } from './InstanceTitle'; import { OrgUnitTitle } from './OrgUnitTitle'; @@ -49,7 +42,6 @@ export const SelectedOrgUnit: FunctionComponent = ({ isFetching: isFetchingOrgUnit, }) => { const classes: Record = useStyles(); - const dispatch = useDispatch(); // selected instance should be: // submission id from params OR reference instance OR first submission of the possible ones OR undefined @@ -62,25 +54,7 @@ export const SelectedOrgUnit: FunctionComponent = ({ const { data: currentInstance, isFetching: isFetchingCurrentInstance } = useGetInstance(currentInstanceId); const { data: instances, isFetching } = useGetOrgUnitInstances(orgUnit?.id); - const instancesOptions = useMemo(() => { - return (instances || []).map(instance => ({ - label: `${instance.form_name} (${moment - .unix(instance.created_at) - .format('LTS')})`, - value: instance.id, - })); - }, [instances]); - const handleChange = useCallback( - (_, submissionId) => { - const newParams: RegistryParams = { - ...params, - submissionId, - }; - dispatch(redirectToReplace(baseUrls.registry, newParams)); - }, - [params, dispatch], - ); if (!orgUnit) { return null; } @@ -99,31 +73,12 @@ export const SelectedOrgUnit: FunctionComponent = ({ {instances && instances?.length === 0 && } {instances && instances?.length > 0 && ( - - - - - + )} {instances && instances?.length > 0 && currentInstance && ( From 85755866f33146054a13a6b5e734700a5705de1b Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Thu, 16 May 2024 16:53:23 +0200 Subject: [PATCH 11/27] feed back Son and Laure --- .../TreeView/OrgUnitTreeviewModal.js | 50 +++++-- .../domains/registry/components/Instances.tsx | 1 - .../components/map/OrgUnitChildrenMap.tsx | 10 +- .../selectedOrgUnit/EmptyInstances.tsx | 8 +- .../components/selectedOrgUnit/Filters.tsx | 79 ----------- .../selectedOrgUnit/InstanceTitle.tsx | 129 ++++++++++++------ .../components/selectedOrgUnit/index.tsx | 26 +--- .../registry/hooks/useGetInstances.tsx | 3 +- .../registry/hooks/useGetLegendOptions.ts | 91 ++++++------ .../domains/registry/hooks/useGetOrgUnit.ts | 1 + .../js/apps/Iaso/domains/registry/index.tsx | 72 ++++++---- 11 files changed, 231 insertions(+), 239 deletions(-) delete mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/Filters.tsx diff --git a/hat/assets/js/apps/Iaso/domains/orgUnits/components/TreeView/OrgUnitTreeviewModal.js b/hat/assets/js/apps/Iaso/domains/orgUnits/components/TreeView/OrgUnitTreeviewModal.js index ea59a5f72b..3a53c5199f 100644 --- a/hat/assets/js/apps/Iaso/domains/orgUnits/components/TreeView/OrgUnitTreeviewModal.js +++ b/hat/assets/js/apps/Iaso/domains/orgUnits/components/TreeView/OrgUnitTreeviewModal.js @@ -1,6 +1,6 @@ import { Box, useTheme } from '@mui/material'; import { makeStyles } from '@mui/styles'; -import { TreeViewWithSearch } from 'bluesquare-components'; +import { IconButton, TreeViewWithSearch } from 'bluesquare-components'; import { isEqual } from 'lodash'; import { array, @@ -47,6 +47,7 @@ const OrgUnitTreeviewModal = ({ allowedTypes, errors, defaultOpen, + useIcon, }) => { const theme = useTheme(); const classes = useStyles(); @@ -181,20 +182,37 @@ const OrgUnitTreeviewModal = ({ }, [resetTrigger, hardReset, resetSelection]); return ( ( - - )} + renderTrigger={({ openDialog }) => + useIcon ? ( + + ) : ( + + ) + } titleMessage={titleMessage} onConfirm={onModalConfirm} onCancel={onModalCancel} @@ -269,6 +287,7 @@ OrgUnitTreeviewModal.propTypes = { allowedTypes: array, errors: arrayOf(string), defaultOpen: bool, + useIcon: bool, }; OrgUnitTreeviewModal.defaultProps = { @@ -288,6 +307,7 @@ OrgUnitTreeviewModal.defaultProps = { allowedTypes: [], errors: [], defaultOpen: false, + useIcon: false, }; export { OrgUnitTreeviewModal }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/Instances.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/Instances.tsx index 53db9e8632..71ebaf5aa1 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/Instances.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/Instances.tsx @@ -122,7 +122,6 @@ export const Instances: FunctionComponent = ({ = ({ params.isFullScreen === 'true', ); const [currentTile, setCurrentTile] = useState(TILES.osm); - const { getLegendOptions, setLegendOptions } = useGetLegendOptions(orgUnit); - const legendOptions = useMemo(() => { - return getLegendOptions(subOrgUnitTypes, selectedChildrenId); - }, [getLegendOptions, selectedChildrenId, subOrgUnitTypes]); - + const { legendOptions, setLegendOptions } = useGetLegendOptions( + orgUnit, + subOrgUnitTypes, + selectedChildrenId, + ); const legendOptionsMap = keyBy(legendOptions, 'value'); const activeChildren: OrgUnit[] = useMemo(() => { return ( diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/EmptyInstances.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/EmptyInstances.tsx index e62c105c1d..19dc83dedf 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/EmptyInstances.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/EmptyInstances.tsx @@ -4,12 +4,12 @@ import { makeStyles } from '@mui/styles'; import { useSafeIntl } from 'bluesquare-components'; import React, { FunctionComponent } from 'react'; +import { HEIGHT } from '../../config'; import MESSAGES from '../../messages'; - const useStyles = makeStyles(() => ({ emptyPaper: { - height: '527px', + height: `calc(${HEIGHT} - 65px)`, display: 'flex', justifyContent: 'center', alignItems: 'center', @@ -29,7 +29,9 @@ export const EmptyInstances: FunctionComponent = () => { - {formatMessage(MESSAGES.noInstance)} + + {formatMessage(MESSAGES.noInstance)} + ); diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/Filters.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/Filters.tsx deleted file mode 100644 index c79696765d..0000000000 --- a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/Filters.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Box } from '@mui/material'; -import moment from 'moment'; -import React, { FunctionComponent, useCallback, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; - -import { baseUrls } from '../../../../constants/urls'; -import MESSAGES from '../../messages'; - -import { redirectToReplace } from '../../../../routing/actions'; - -import InputComponent from '../../../../components/forms/InputComponent'; - -import { Instance } from '../../../instances/types/instance'; -import { OrgUnit } from '../../../orgUnits/types/orgUnit'; -import { RegistryParams } from '../../types'; - -type Props = { - orgUnit?: OrgUnit; - params: RegistryParams; - instances: Instance[]; - isFetching: boolean; -}; - -export const Filters: FunctionComponent = ({ - orgUnit, - params, - instances, - isFetching, -}) => { - const dispatch = useDispatch(); - - const currentInstanceId = useMemo(() => { - return params.submissionId || orgUnit?.reference_instances?.[0]?.id; - }, [params.submissionId, orgUnit]); - - const instancesOptions = useMemo(() => { - return (instances || []).map(instance => ({ - label: `${instance.form_name} (${moment - .unix(instance.created_at) - .format('LTS')})`, - value: instance.id, - })); - }, [instances]); - - const handleChange = useCallback( - (_, submissionId) => { - const newParams: RegistryParams = { - ...params, - submissionId, - }; - dispatch(redirectToReplace(baseUrls.registry, newParams)); - }, - [params, dispatch], - ); - - return ( - - - - - - ); -}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/InstanceTitle.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/InstanceTitle.tsx index d4bbf12145..0254b6e18e 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/InstanceTitle.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/InstanceTitle.tsx @@ -1,8 +1,10 @@ -import { Box, Grid, Typography } from '@mui/material'; +import { Box, Grid } from '@mui/material'; import { makeStyles } from '@mui/styles'; import { IconButton, commonStyles, useSafeIntl } from 'bluesquare-components'; -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useCallback, useMemo } from 'react'; +import moment from 'moment'; +import { useDispatch } from 'react-redux'; import MESSAGES from '../../messages'; import { useCurrentUser } from '../../../../utils/usersUtils'; @@ -12,14 +14,21 @@ import EnketoIcon from '../../../instances/components/EnketoIcon'; import { userHasPermission } from '../../../users/utils'; +import InputComponent from '../../../../components/forms/InputComponent'; +import { baseUrls } from '../../../../constants/urls'; +import { redirectToReplace } from '../../../../routing/actions'; import * as Permission from '../../../../utils/permissions'; import { LinkToInstance } from '../../../instances/components/LinkToInstance'; import { Instance } from '../../../instances/types/instance'; import { OrgUnit } from '../../../orgUnits/types/orgUnit'; +import { RegistryParams } from '../../types'; type Props = { orgUnit?: OrgUnit; - currentInstance: Instance; + currentInstance?: Instance; + params: RegistryParams; + instances: Instance[]; + isFetching: boolean; }; const useStyles = makeStyles(theme => ({ @@ -44,58 +53,96 @@ const useStyles = makeStyles(theme => ({ export const InstanceTitle: FunctionComponent = ({ orgUnit, currentInstance, + params, + instances, + isFetching, }) => { + const dispatch = useDispatch(); const classes: Record = useStyles(); const { formatMessage } = useSafeIntl(); const getEnketoUrl = useGetEnketoUrl(window.location.href, currentInstance); const currentUser = useCurrentUser(); - const isReferenceInstance = orgUnit?.reference_instances?.some( - ref => ref.id === currentInstance?.id, + + const currentInstanceId = useMemo(() => { + return params.submissionId || orgUnit?.reference_instances?.[0]?.id; + }, [params.submissionId, orgUnit]); + + const instancesOptions = useMemo(() => { + return (instances || []).map(instance => { + const isReferenceInstance = orgUnit?.reference_instances?.some( + ref => ref.id === instance.id, + ); + const label = `${instance.form_name} (${ + isReferenceInstance + ? `${formatMessage(MESSAGES.referenceInstance)} - ` + : '' + }${moment.unix(instance.created_at).format('LTS')})`; + return { + label, + value: instance.id, + }; + }); + }, [formatMessage, instances, orgUnit?.reference_instances]); + + const handleChange = useCallback( + (_, submissionId) => { + const newParams: RegistryParams = { + ...params, + submissionId, + }; + dispatch(redirectToReplace(baseUrls.registry, newParams)); + }, + [params, dispatch], ); - const referenceInstanceMessage = isReferenceInstance - ? ` (${formatMessage(MESSAGES.referenceInstance)})` - : ''; return ( - - {`${currentInstance.form_name}${referenceInstanceMessage}`} - + - - - {userHasPermission( - Permission.SUBMISSIONS_UPDATE, - currentUser, - ) && ( - getEnketoUrl()} - overrideIcon={EnketoIcon} + {currentInstance && ( + + + {userHasPermission( + Permission.SUBMISSIONS_UPDATE, + currentUser, + ) && ( + getEnketoUrl()} + overrideIcon={EnketoIcon} + color="secondary" + iconSize="small" + size="small" + tooltipMessage={MESSAGES.editOnEnketo} + /> + )} + - )} - - - + + + )} ); }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx index 10e641e17c..070296a0fd 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/index.tsx @@ -14,7 +14,6 @@ import { OrgUnit } from '../../../orgUnits/types/orgUnit'; import { HEIGHT } from '../../config'; import { RegistryParams } from '../../types'; import { EmptyInstances } from './EmptyInstances'; -import { Filters } from './Filters'; import { InstanceTitle } from './InstanceTitle'; import { OrgUnitTitle } from './OrgUnitTitle'; @@ -29,9 +28,10 @@ const useStyles = makeStyles(theme => ({ paper: { width: '100%', height: HEIGHT, + overflow: 'hidden', }, formContents: { - maxHeight: `calc(${HEIGHT} - 222px)`, + maxHeight: `calc(${HEIGHT} - 155px)`, overflow: 'auto', }, })); @@ -42,26 +42,17 @@ export const SelectedOrgUnit: FunctionComponent = ({ isFetching: isFetchingOrgUnit, }) => { const classes: Record = useStyles(); - - // selected instance should be: - // submission id from params OR reference instance OR first submission of the possible ones OR undefined - // if undefined select should be hidden and a place holder should say no submission - const currentInstanceId = useMemo(() => { return params.submissionId || orgUnit?.reference_instances?.[0]?.id; }, [params.submissionId, orgUnit]); const { data: currentInstance, isFetching: isFetchingCurrentInstance } = - useGetInstance(currentInstanceId); + useGetInstance(currentInstanceId, false); const { data: instances, isFetching } = useGetOrgUnitInstances(orgUnit?.id); if (!orgUnit) { return null; } - - // console.log('currentInstance', currentInstance); - // console.log('currentInstanceId', currentInstanceId); - // console.log('instances', instances); return ( {(isFetchingCurrentInstance || isFetchingOrgUnit) && ( @@ -73,23 +64,16 @@ export const SelectedOrgUnit: FunctionComponent = ({ {instances && instances?.length === 0 && } {instances && instances?.length > 0 && ( - )} - - {instances && instances?.length > 0 && currentInstance && ( - - )} {currentInstance && ( <> - => { return useSnackQuery({ queryKey: ['instance', instanceId], @@ -105,7 +106,7 @@ export const useGetInstance = ( options: { enabled: Boolean(instanceId), retry: false, - keepPreviousData: true, + keepPreviousData, }, }); }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts index 6b2d339fbc..6cb6386a09 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts @@ -1,9 +1,8 @@ import { useTheme } from '@mui/styles'; -import { Dispatch, SetStateAction, useCallback, useState } from 'react'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { hasLocation } from '../../../utils/map/mapUtils'; import { OrgUnit } from '../../orgUnits/types/orgUnit'; import { OrgunitTypes } from '../../orgUnits/types/orgunitTypes'; - -import { hasLocation } from '../../../utils/map/mapUtils'; import { selectedOrgUnitColor } from '../components/map/OrgUnitChildrenMap'; export type Legend = { @@ -12,55 +11,59 @@ export type Legend = { color: string; // has to be an hexa color active?: boolean; }; + export const useGetLegendOptions = ( orgUnit: OrgUnit, + subOrgUnitTypes: OrgunitTypes, + selectedChildrenId?: string, ): { - getLegendOptions: ( - // eslint-disable-next-line no-unused-vars - subOrgUnitTypes: OrgunitTypes, - // eslint-disable-next-line no-unused-vars - selectedChildrenId?: string, - ) => Legend[]; - setLegendOptions: Dispatch>; legendOptions: Legend[]; + setLegendOptions: Dispatch>; } => { - const theme = useTheme(); const [legendOptions, setLegendOptions] = useState([]); + const theme = useTheme(); + + // Generate initial legend options based on orgUnit and subOrgUnitTypes + useEffect(() => { + const options = subOrgUnitTypes.map(subOuType => { + const orgUnitWithLocationsCount = + subOuType.orgUnits?.filter(hasLocation).length || 0; + const orgUnitCount = subOuType.orgUnits?.length || 0; + const labelCount = + orgUnitCount === orgUnitWithLocationsCount + ? `${orgUnitCount}` + : `${orgUnitWithLocationsCount}/${orgUnitCount}`; + return { + value: `${subOuType.id}`, + label: `${subOuType.name} (${labelCount})`, + color: subOuType.color || '', + active: true, + }; + }); - const getLegendOptions = useCallback( - (subOrgUnitTypes: OrgunitTypes, selectedChildrenId?: string) => { - const options = subOrgUnitTypes.map(subOuType => { - const orgUnitWithLocationsCount = - subOuType.orgUnits?.filter(hasLocation).length || 0; - const orgUnitCount = subOuType.orgUnits?.length || 0; - const labelCount = - orgUnitCount === orgUnitWithLocationsCount - ? `${orgUnitCount}` - : `${orgUnitWithLocationsCount}/${orgUnitCount}`; - return { - value: `${subOuType.id}`, - label: `${subOuType.name} (${labelCount})`, - color: subOuType.color || '', - active: true, - }; + if (orgUnit) { + options.unshift({ + value: `${orgUnit.id}`, + label: orgUnit.name, + color: selectedOrgUnitColor, // Default color + active: true, }); + } + setLegendOptions(options); + }, [orgUnit, subOrgUnitTypes]); - if (orgUnit) { - const color = selectedChildrenId - ? theme.palette.primary.main - : selectedOrgUnitColor; - options.unshift({ - value: `${orgUnit.id}`, - label: orgUnit.name, - color, - active: true, - }); - } - setLegendOptions(options); - return options; - }, - [orgUnit, theme], - ); + // Adjust the color of the first legend option based on selectedChildrenId + useEffect(() => { + if (legendOptions.length > 0 && selectedChildrenId) { + const adjustedOptions = [...legendOptions]; + adjustedOptions[0] = { + ...adjustedOptions[0], + color: theme.palette.primary.main, + }; + setLegendOptions(adjustedOptions); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedChildrenId, theme.palette.primary.main]); - return { getLegendOptions, setLegendOptions, legendOptions }; + return { legendOptions, setLegendOptions }; }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts index 9daa9a6001..27204eac34 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts @@ -19,6 +19,7 @@ export const useGetOrgUnit = ( options: { retry: false, enabled: Boolean(orgUnitId), + keepPreviousData: true, }, }); }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/index.tsx b/hat/assets/js/apps/Iaso/domains/registry/index.tsx index c25002a466..51aba84a53 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/index.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/index.tsx @@ -27,6 +27,7 @@ import { OrgunitTypeRegistry } from './types/orgunitTypes'; import { RegistryParams } from './types'; import { redirectTo, redirectToReplace } from '../../routing/actions'; +import { SxStyles } from '../../types/general'; import { OrgUnitTreeviewModal } from '../orgUnits/components/TreeView/OrgUnitTreeviewModal'; import { OrgUnitBreadcrumbs } from '../orgUnits/components/breadcrumbs/OrgUnitBreadcrumbs'; import { OrgUnit } from '../orgUnits/types/orgUnit'; @@ -40,6 +41,19 @@ type Props = { router: Router; }; +const styles: SxStyles = { + breadCrumbContainer: { + '& nav': { + display: 'inline-block', + }, + }, + treeContainer: { + top: '-2px', + position: 'relative', + display: 'inline-block', + }, +}; + const useStyles = makeStyles(theme => ({ ...commonStyles(theme), })); @@ -93,6 +107,7 @@ export const Registry: FunctionComponent = ({ router }) => { orgUnitId: `${newOrgUnit.id}`, }; delete newParams.orgUnitChildrenId; + delete newParams.submissionId; setSelectedChildrenId(undefined); dispatch(redirectTo(`/${baseUrls.registry}`, newParams)); } @@ -108,6 +123,7 @@ export const Registry: FunctionComponent = ({ router }) => { setSelectedChildrenId(undefined); delete newParams.orgUnitChildrenId; } + delete newParams.submissionId; dispatch(redirectToReplace(`/${baseUrls.registry}`, newParams)); }; return ( @@ -123,29 +139,29 @@ export const Registry: FunctionComponent = ({ router }) => { {isFetching && } - - {orgUnitId && ( - - {!isFetching && orgUnit && ( - - )} - - )} - - - + + + + {orgUnitId && !isFetching && orgUnit && ( + <> + + {`>`} + + - - + + )} {!isFetching && orgUnit && ( <> @@ -192,13 +208,11 @@ export const Registry: FunctionComponent = ({ router }) => { )} - - - + ); From 59b6b3b1fd0b0eb230b698ccf6b0c38328afd74f Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Thu, 16 May 2024 17:40:43 +0200 Subject: [PATCH 12/27] placeholder, last fixes --- .../breadcrumbs/OrgUnitBreadcrumbs.tsx | 29 +++++++++++----- .../registry/components/Placeholder.tsx | 34 +++++++++++++++++++ .../components/map/OrgUnitChildrenMap.tsx | 2 +- .../registry/hooks/useGetLegendOptions.ts | 8 +++-- .../js/apps/Iaso/domains/registry/index.tsx | 21 ++++++------ 5 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 hat/assets/js/apps/Iaso/domains/registry/components/Placeholder.tsx diff --git a/hat/assets/js/apps/Iaso/domains/orgUnits/components/breadcrumbs/OrgUnitBreadcrumbs.tsx b/hat/assets/js/apps/Iaso/domains/orgUnits/components/breadcrumbs/OrgUnitBreadcrumbs.tsx index e282da68eb..6cdeab7ede 100644 --- a/hat/assets/js/apps/Iaso/domains/orgUnits/components/breadcrumbs/OrgUnitBreadcrumbs.tsx +++ b/hat/assets/js/apps/Iaso/domains/orgUnits/components/breadcrumbs/OrgUnitBreadcrumbs.tsx @@ -1,9 +1,9 @@ -import React, { FunctionComponent, useMemo } from 'react'; -import { Breadcrumbs } from '@mui/material'; +import { Breadcrumbs, Typography } from '@mui/material'; import { makeStyles } from '@mui/styles'; +import React, { FunctionComponent, useMemo } from 'react'; +import { LinkToRegistry } from '../../../registry/components/LinkToRegistry'; import { OrgUnit } from '../../types/orgUnit'; import { LinkToOrgUnit } from '../LinkToOrgUnit'; -import { LinkToRegistry } from '../../../registry/components/LinkToRegistry'; type BreadCrumbsArgs = { orgUnit?: OrgUnit; @@ -53,6 +53,7 @@ type Props = { orgUnit: OrgUnit; showOnlyParents?: boolean; showRegistry?: boolean; + color?: string; }; export const OrgUnitBreadcrumbs: FunctionComponent = ({ @@ -60,13 +61,25 @@ export const OrgUnitBreadcrumbs: FunctionComponent = ({ orgUnit, showOnlyParents, showRegistry = false, + color = 'inherit', }) => { const { link } = useStyles(); const breadcrumbs = useOrgUnitBreadCrumbs({ orgUnit, showOnlyParents }); return ( - - {breadcrumbs.map(ou => - showRegistry ? ( + + {breadcrumbs.map((ou, index) => { + if (index === breadcrumbs.length - 1 && !showOnlyParents) { + return ( + + {ou.name} + + ); + } + return showRegistry ? ( = ({ className={link} replace /> - ), - )} + ); + })} ); }; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/Placeholder.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/Placeholder.tsx new file mode 100644 index 0000000000..cb74fbb2b6 --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/registry/components/Placeholder.tsx @@ -0,0 +1,34 @@ +import React, { FunctionComponent } from 'react'; + +import { Box, Grid, Skeleton } from '@mui/material'; +import { HEIGHT } from '../config'; + +export const Placeholder: FunctionComponent = () => { + return ( + + + + + + + + + + + + + + ); +}; diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx index 3f22655949..8291e2008b 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx @@ -74,7 +74,7 @@ const useStyles = makeStyles(theme => ({ top: '64px', left: '0', width: '100vw', - height: '100vh', + height: 'calc(100vh - 64px)', zIndex: 10000, }, })); diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts index 6cb6386a09..6fce0a0f35 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetLegendOptions.ts @@ -54,11 +54,15 @@ export const useGetLegendOptions = ( // Adjust the color of the first legend option based on selectedChildrenId useEffect(() => { - if (legendOptions.length > 0 && selectedChildrenId) { + if (legendOptions.length > 0) { const adjustedOptions = [...legendOptions]; + + const color = selectedChildrenId + ? theme.palette.primary.main + : selectedOrgUnitColor; adjustedOptions[0] = { ...adjustedOptions[0], - color: theme.palette.primary.main, + color, }; setLegendOptions(adjustedOptions); } diff --git a/hat/assets/js/apps/Iaso/domains/registry/index.tsx b/hat/assets/js/apps/Iaso/domains/registry/index.tsx index 51aba84a53..72727aac8a 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/index.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/index.tsx @@ -31,6 +31,7 @@ import { SxStyles } from '../../types/general'; import { OrgUnitTreeviewModal } from '../orgUnits/components/TreeView/OrgUnitTreeviewModal'; import { OrgUnitBreadcrumbs } from '../orgUnits/components/breadcrumbs/OrgUnitBreadcrumbs'; import { OrgUnit } from '../orgUnits/types/orgUnit'; +import { Placeholder } from './components/Placeholder'; import { SelectedOrgUnit } from './components/selectedOrgUnit'; type Router = { @@ -150,17 +151,16 @@ export const Registry: FunctionComponent = ({ router }) => { useIcon /> + + {`>`} + + {!orgUnitId && '...'} {orgUnitId && !isFetching && orgUnit && ( - <> - - {`>`} - - - + )} {!isFetching && orgUnit && ( @@ -213,6 +213,7 @@ export const Registry: FunctionComponent = ({ router }) => { subOrgUnitTypes={subOrgUnitTypes} params={params} /> + {!orgUnitId && } ); From 905cfcaeef47b2dbee1d9840be7880036f2eeae3 Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Fri, 17 May 2024 15:26:20 +0200 Subject: [PATCH 13/27] fix conflict in hat/menupermissions/constants.py --- hat/menupermissions/constants.py | 66 ++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/hat/menupermissions/constants.py b/hat/menupermissions/constants.py index 563bb2011f..a27b520445 100644 --- a/hat/menupermissions/constants.py +++ b/hat/menupermissions/constants.py @@ -61,3 +61,69 @@ {"name": "Registry", "codename": "REGISTRY"}, {"name": "Payments", "codename": "PAYMENTS"}, ] + +FEATUREFLAGES_TO_EXCLUDE = { + "PLANNING": ["PLANNING"], + "ENTITIES": [ + "REPORTS", + "ENTITY", + "MOBILE_ENTITY_WARN_WHEN_FOUND", + "MOBILE_ENTITY_LIMITED_SEARCH", + "MOBILE_ENTITY_NO_CREATION", + "WRITE_ON_NFC_CARDS", + ], +} + +PERMISSIONS_PRESENTATION = { + "forms": [ + "iaso_forms", + "iaso_update_submission", + "iaso_submissions", + "iaso_completeness", + "iaso_completeness_stats", + ], + "org_units": [ + "iaso_org_units", + "iaso_org_unit_types", + "iaso_org_unit_groups", + "iaso_sources", + "iaso_write_sources", + "iaso_links", + "iaso_registry", + "iaso_org_unit_change_request_review", + ], + "entities": [ + "iaso_entities", + "iaso_workflows", + "iaso_entity_duplicates_write", + "iaso_entity_duplicates_read", + "iaso_entity_type_write", + ], + "payments": ["iaso_payments"], + "dhis2_mapping": ["iaso_mappings"], + "external_storage": ["iaso_storages"], + "planning": ["iaso_assignments", "iaso_planning"], + "embedded_links": ["iaso_pages", "iaso_page_write"], + "polio": [ + "iaso_polio_config", + "iaso_polio", + "iaso_polio_budget_admin", + "iaso_polio_budget", + "iaso_polio_vaccine_supply_chain_read", + "iaso_polio_vaccine_supply_chain_write", + "iaso_polio_vaccine_stock_management_read", + "iaso_polio_vaccine_stock_management_write", + "iaso_polio_notifications", + "iaso_polio_vaccine_authorizations_read_only", + "iaso_polio_vaccine_authorizations_admin", + ], + "admin": [ + "iaso_data_tasks", + "iaso_reports", + "iaso_projects", + "iaso_users", + "iaso_user_roles", + "iaso_teams", + "iaso_modules", + ], +} From 4acce70fcaecec11cce819e3fdfdfb69e06d88bd Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Fri, 17 May 2024 16:59:51 +0200 Subject: [PATCH 14/27] groupe permissions --- .../components/PermissionsSwitches.tsx | 102 ++++++++++-------- .../Iaso/domains/users/components/Filters.js | 4 +- .../users/components/PermissionsSwitches.tsx | 24 +++-- .../users/hooks/useGetUserPermissions.tsx | 46 ++++---- iaso/api/permissions.py | 31 ++++-- 5 files changed, 123 insertions(+), 84 deletions(-) diff --git a/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx b/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx index 83d5fa5418..19a7f9e89c 100644 --- a/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx +++ b/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx @@ -37,7 +37,7 @@ export const PermissionsSwitches: React.FunctionComponent = ({ const classes = useStyles(); const { data, isLoading } = useSnackQuery<{ permissions: Permission[] }>( ['permissions'], - () => getRequest('/api/permissions/'), + () => getRequest('/api/permissions/grouped_permissions/'), MESSAGES.fetchPermissionsError, ); @@ -85,60 +85,70 @@ export const PermissionsSwitches: React.FunctionComponent = ({ } return ''; }; + const permissions = useMemo( () => data?.permissions ?? [], [data?.permissions], ); + const DisplayPermissions = ({ group_permissions }) => { + return group_permissions + .sort((a, b) => + getPermissionLabel(a.codename).localeCompare( + getPermissionLabel(b.codename), + undefined, + { + sensitivity: 'accent', + }, + ), + ) + .map(p => ( + + +
+ + up.codename === p.codename, + ), + )} + onChange={e => + setPermissions(p, e.target.checked) + } + name={p.codename} + color="primary" + /> + } + label={getPermissionLabel(p.codename)} + /> +
+
+ + {getPermissionToolTip(p.codename)} + +
+ )); + }; + return ( {isLoading && } - {permissions - .sort((a, b) => - getPermissionLabel(a.codename).localeCompare( - getPermissionLabel(b.codename), - undefined, - { - sensitivity: 'accent', - }, - ), - ) - .map(p => ( - - -
- - up.codename === - p.codename, - ), - )} - onChange={e => - setPermissions( - p, - e.target.checked, - ) - } - name={p.codename} - color="primary" - /> - } - label={getPermissionLabel(p.codename)} - /> -
-
- - {getPermissionToolTip(p.codename)} - -
- ))} + {Object.keys(permissions).map(group => { + return ( +
+ {group} + +
+ ); + })}
); }; diff --git a/hat/assets/js/apps/Iaso/domains/users/components/Filters.js b/hat/assets/js/apps/Iaso/domains/users/components/Filters.js index 1f94f4854c..d46cad0bca 100644 --- a/hat/assets/js/apps/Iaso/domains/users/components/Filters.js +++ b/hat/assets/js/apps/Iaso/domains/users/components/Filters.js @@ -8,7 +8,7 @@ import SearchIcon from '@mui/icons-material/Search'; import { commonStyles, useSafeIntl } from 'bluesquare-components'; -import InputComponent from 'Iaso/components/forms/InputComponent'; +import InputComponent from 'Iaso/components/forms/InputComponent.tsx'; import { redirectTo } from '../../../routing/actions.ts'; import MESSAGES from '../messages'; import { useGetPermissionsDropDown } from '../hooks/useGetPermissionsDropdown.ts'; @@ -19,7 +19,7 @@ import { stringToBoolean } from '../../../utils/dataManipulation.ts'; import { useGetUserRolesDropDown } from '../hooks/useGetUserRolesDropDown.ts'; import { useGetProjectsDropdownOptions } from '../../projects/hooks/requests.ts'; -import { useGetTeamsDropdown } from '../../teams/hooks/requests/useGetTeams'; +import { useGetTeamsDropdown } from '../../teams/hooks/requests/useGetTeams.ts'; const useStyles = makeStyles(theme => ({ ...commonStyles(theme), diff --git a/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx b/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx index 28f3a04582..650df800b1 100644 --- a/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx +++ b/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx @@ -71,8 +71,8 @@ const PermissionsSwitches: React.FunctionComponent = ({ const { formatMessage } = useSafeIntl(); const classes = useStyles(); const { data, isLoading } = useSnackQuery({ - queryKey: ['permissions'], - queryFn: () => getRequest('/api/permissions/'), + queryKey: ['grouped_permissions'], + queryFn: () => getRequest('/api/permissions/grouped_permissions'), snackErrorMsg: MESSAGES.fetchPermissionsError, // Permission list is not displayed for superuser, no need to fetch it from server options: { enabled: !isSuperUser }, @@ -89,16 +89,20 @@ const PermissionsSwitches: React.FunctionComponent = ({ handleChange(newUserPerms); }; const loggedInUser = useCurrentUser(); - const allPermissions = - data?.permissions?.filter(permission => - canAssignPermission(loggedInUser, permission), - ) ?? []; + const groups = data?.permissions ? Object.keys(data?.permissions) : []; + + const allPermissions = {}; + groups.forEach(group => { + allPermissions[group] = + data?.permissions[group]?.filter(permission => + canAssignPermission(loggedInUser, permission), + ) ?? []; + }); + const userPermissions = currentUser.user_permissions.value; const { data: userRoles, isFetching } = useGetUserRolesDropDown(); - const permissionsData = useGetUserPermissions( - allPermissions, - userPermissions, - ); + const permissionsData = useGetUserPermissions([], userPermissions); + // console.log(permissionsData); // This is a problem with the type definition of Column is bluesquare-components // @ts-ignore const columns: Column[] = useUserPermissionColumns({ diff --git a/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx b/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx index 705d0f1998..e4fa7d0acb 100644 --- a/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx +++ b/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx @@ -9,7 +9,8 @@ type Row = { }; export const useGetUserPermissions = ( - allPermissions: Permission[], + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + allPermissions: any, userPermissions: string[], ): any => { const { formatMessage } = useSafeIntl(); @@ -21,20 +22,25 @@ export const useGetUserPermissions = ( }, [formatMessage], ); + const sortedPermissions = useGetSortedPermissions({ allPermissions, permissionLabel, }); + return useMemo(() => { - const data: Row[] = []; + const data: any = {}; - sortedPermissions.forEach(p => { - const row: any = {}; - row.permission = permissionLabel(p.codename); - row.userPermissions = userPermissions; - row.permissionCodeName = p.codename; + Object.keys(sortedPermissions).forEach(group => { + data[group] = []; + sortedPermissions[group].forEach(p => { + const row: any = {}; + row.permission = permissionLabel(p.codename); + row.userPermissions = userPermissions; + row.permissionCodeName = p.codename; - data.push(row); + data[group].push(row); + }); }); return data; }, [permissionLabel, sortedPermissions, userPermissions]); @@ -49,18 +55,20 @@ type SortProps = { const useGetSortedPermissions = ({ allPermissions, permissionLabel, -}: SortProps): Permission[] => { +}: SortProps): any => { return useMemo(() => { - let sortedPermissions: Permission[] = []; - sortedPermissions = allPermissions.sort((a, b) => - permissionLabel(a.codename).localeCompare( - permissionLabel(b.codename), - undefined, - { - sensitivity: 'accent', - }, - ), - ); + const sortedPermissions = {}; + Object.keys(allPermissions).forEach(group => { + sortedPermissions[group] = allPermissions[group].sort((a, b) => + permissionLabel(a.codename).localeCompare( + permissionLabel(b.codename), + undefined, + { + sensitivity: 'accent', + }, + ), + ); + }); return sortedPermissions; }, [allPermissions, permissionLabel]); }; diff --git a/iaso/api/permissions.py b/iaso/api/permissions.py index 336547f9a7..15b56fbdd1 100644 --- a/iaso/api/permissions.py +++ b/iaso/api/permissions.py @@ -3,9 +3,11 @@ from django.conf import settings from django.contrib.auth.models import Permission from django.utils.translation import gettext as _ +from hat.menupermissions.constants import PERMISSIONS_PRESENTATION from iaso.utils.module_permissions import account_module_permissions from rest_framework import viewsets, permissions from rest_framework.response import Response +from rest_framework.decorators import action from hat.menupermissions.models import CustomPermissionSupport from hat.menupermissions import models as p @@ -24,6 +26,27 @@ class PermissionsViewSet(viewsets.ViewSet): @staticmethod def list(request): + perms = PermissionsViewSet.queryset(request) + + result = [] + for permission in perms: + result.append({"id": permission.id, "name": _(permission.name), "codename": permission.codename}) + + return Response({"permissions": sorted(result, key=itemgetter("name"))}) + + @staticmethod + @action(methods=["GET"], detail=False) + def grouped_permissions(request): + perms = PermissionsViewSet.queryset(request) + result = {} + for group in PERMISSIONS_PRESENTATION.keys(): + result[group] = [] + for permission in perms.filter(codename__in=PERMISSIONS_PRESENTATION[group]): + result[group].append({"id": permission.id, "name": _(permission.name), "codename": permission.codename}) + return Response({"permissions": result}) + + @staticmethod + def queryset(request): if request.user.has_perm(p.USERS_ADMIN) or request.user.has_perm(p.USERS_MANAGED): perms = Permission.objects else: @@ -35,10 +58,4 @@ def list(request): # Get all permissions linked to the modules modules_permissions = account_module_permissions(account_modules) - perms = CustomPermissionSupport.filter_permissions(perms, modules_permissions, settings) - - result = [] - for permission in perms: - result.append({"id": permission.id, "name": _(permission.name), "codename": permission.codename}) - - return Response({"permissions": sorted(result, key=itemgetter("name"))}) + return CustomPermissionSupport.filter_permissions(perms, modules_permissions, settings) From 9176708466562c30ccc320283546eb88da1bb139 Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Mon, 20 May 2024 13:48:26 +0200 Subject: [PATCH 15/27] fix the group of permissions displaying --- .../components/PermissionsSwitches.tsx | 2 +- .../users/components/PermissionsSwitches.tsx | 7 +- .../js/apps/Iaso/domains/users/config.js | 65 ++++++++++--------- .../users/hooks/useGetUserPermissions.tsx | 11 ++-- 4 files changed, 49 insertions(+), 36 deletions(-) diff --git a/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx b/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx index 19a7f9e89c..716bc526f4 100644 --- a/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx +++ b/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx @@ -36,7 +36,7 @@ export const PermissionsSwitches: React.FunctionComponent = ({ const { formatMessage } = useSafeIntl(); const classes = useStyles(); const { data, isLoading } = useSnackQuery<{ permissions: Permission[] }>( - ['permissions'], + ['grouped_permissions'], () => getRequest('/api/permissions/grouped_permissions/'), MESSAGES.fetchPermissionsError, ); diff --git a/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx b/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx index 650df800b1..26a3f603e3 100644 --- a/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx +++ b/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx @@ -72,7 +72,7 @@ const PermissionsSwitches: React.FunctionComponent = ({ const classes = useStyles(); const { data, isLoading } = useSnackQuery({ queryKey: ['grouped_permissions'], - queryFn: () => getRequest('/api/permissions/grouped_permissions'), + queryFn: () => getRequest('/api/permissions/grouped_permissions/'), snackErrorMsg: MESSAGES.fetchPermissionsError, // Permission list is not displayed for superuser, no need to fetch it from server options: { enabled: !isSuperUser }, @@ -101,7 +101,10 @@ const PermissionsSwitches: React.FunctionComponent = ({ const userPermissions = currentUser.user_permissions.value; const { data: userRoles, isFetching } = useGetUserRolesDropDown(); - const permissionsData = useGetUserPermissions([], userPermissions); + const permissionsData = useGetUserPermissions( + allPermissions, + userPermissions, + ); // console.log(permissionsData); // This is a problem with the type definition of Column is bluesquare-components // @ts-ignore diff --git a/hat/assets/js/apps/Iaso/domains/users/config.js b/hat/assets/js/apps/Iaso/domains/users/config.js index 871041531a..b26771102d 100644 --- a/hat/assets/js/apps/Iaso/domains/users/config.js +++ b/hat/assets/js/apps/Iaso/domains/users/config.js @@ -118,28 +118,32 @@ export const useUserPermissionColumns = ({ setPermissions, currentUser }) => { accessor: 'userPermission', sortable: false, Cell: settings => { - return ( - - up === + if (!settings.row.original.group) { + return ( + + up === + settings.row.original + .permissionCodeName, + ), + )} + onChange={e => + setPermissions( settings.row.original .permissionCodeName, - ), - )} - onChange={e => - setPermissions( - settings.row.original.permissionCodeName, - e.target.checked, - ) - } - name={settings.row.original.permissionCodeName} - color="primary" - /> - ); + e.target.checked, + ) + } + name={settings.row.original.permissionCodeName} + color="primary" + /> + ); + } + return ''; }, }, ]; @@ -152,16 +156,19 @@ export const useUserPermissionColumns = ({ setPermissions, currentUser }) => { sortable: false, width: 50, Cell: settings => { - if ( - role.permissions.find( - permission => - permission === - settings.row.original.permissionCodeName, - ) - ) { - return ; + if (!settings.row.original.group) { + if ( + role.permissions.find( + permission => + permission === + settings.row.original.permissionCodeName, + ) + ) { + return ; + } + return ; } - return ; + return ''; }, }); }); diff --git a/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx b/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx index e4fa7d0acb..7baa43b6ef 100644 --- a/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx +++ b/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx @@ -29,17 +29,20 @@ export const useGetUserPermissions = ( }); return useMemo(() => { - const data: any = {}; + const data: Row[] = []; Object.keys(sortedPermissions).forEach(group => { - data[group] = []; + let row: any = {}; + row.permission = group; + row.group = true; + data.push(row); sortedPermissions[group].forEach(p => { - const row: any = {}; + row = {}; row.permission = permissionLabel(p.codename); row.userPermissions = userPermissions; row.permissionCodeName = p.codename; - data[group].push(row); + data.push(row); }); }); return data; From a48a0570278c200a327e4d433a49b2000eaffe77 Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Mon, 20 May 2024 14:51:21 +0200 Subject: [PATCH 16/27] add translations for permissions groups --- .../js/apps/Iaso/domains/app/translations/en.json | 10 ++++++++++ .../js/apps/Iaso/domains/app/translations/fr.json | 10 ++++++++++ .../userRoles/components/PermissionsSwitches.tsx | 11 ++++++++++- hat/assets/js/apps/Iaso/domains/users/config.js | 15 +++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/hat/assets/js/apps/Iaso/domains/app/translations/en.json b/hat/assets/js/apps/Iaso/domains/app/translations/en.json index d205e7ee72..ad585eeb0c 100644 --- a/hat/assets/js/apps/Iaso/domains/app/translations/en.json +++ b/hat/assets/js/apps/Iaso/domains/app/translations/en.json @@ -909,6 +909,16 @@ "iaso.permissions.dataTasks": "Batch monitoring", "iaso.permissions.entities": "Entities", "iaso.permissions.forms": "Forms management", + "iaso.permissions.group.admin": "Admin", + "iaso.permissions.group.dhis2_mapping": "Dhis2 mapping", + "iaso.permissions.group.embedded_links": "Embedded links", + "iaso.permissions.group.entities": "Beneficiaries", + "iaso.permissions.group.external_storage": "External storage", + "iaso.permissions.group.forms": "Forms", + "iaso.permissions.group.org_units": "Org units", + "iaso.permissions.group.payments": "Payments", + "iaso.permissions.group.planning": "Planning", + "iaso.permissions.group.polio": "Polio", "iaso.permissions.iaso_dhis2_link": "Link with DHIS2", "iaso.permissions.iaso_entity_duplicates_read": "Entity duplicates - Read only", "iaso.permissions.iaso_entity_duplicates_write": "Entity duplicates - Read and Write", diff --git a/hat/assets/js/apps/Iaso/domains/app/translations/fr.json b/hat/assets/js/apps/Iaso/domains/app/translations/fr.json index 2396ad2077..5955b91f07 100644 --- a/hat/assets/js/apps/Iaso/domains/app/translations/fr.json +++ b/hat/assets/js/apps/Iaso/domains/app/translations/fr.json @@ -909,6 +909,16 @@ "iaso.permissions.dataTasks": "Batch monitoring", "iaso.permissions.entities": "Entités", "iaso.permissions.forms": "Gestion des formulaires", + "iaso.permissions.group.admin": "Admin", + "iaso.permissions.group.dhis2_mapping": "Mappage DHIS2", + "iaso.permissions.group.embedded_links": "Liens intégrés", + "iaso.permissions.group.entities": "Bénéficiaires", + "iaso.permissions.group.external_storage": "Stockage externe", + "iaso.permissions.group.forms": "Formulaires", + "iaso.permissions.group.org_units": "Unités d'organisation", + "iaso.permissions.group.payments": "Paiements", + "iaso.permissions.group.planning": "Planning", + "iaso.permissions.group.polio": "Polio", "iaso.permissions.iaso_dhis2_link": "Lier avec DHIS2", "iaso.permissions.iaso_entity_duplicates_read": "Doublons d’entités - Lecture seule", "iaso.permissions.iaso_entity_duplicates_write": "Doublons d’entités - Lecture et écriture", diff --git a/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx b/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx index 716bc526f4..a08d8660a5 100644 --- a/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx +++ b/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx @@ -10,6 +10,7 @@ import { useSnackQuery } from '../../../libs/apiHooks'; import { getRequest } from '../../../libs/Api'; import { Permission } from '../types/userRoles'; import PERMISSIONS_MESSAGES from '../../users/permissionsMessages'; +import PERMISSIONS_GROUPS_MESSAGES from '../../users/permissionsGroupsMessages'; const styles = theme => ({ container: { @@ -63,6 +64,12 @@ export const PermissionsSwitches: React.FunctionComponent = ({ : permissionCodeName; }; + const getGroupPermissionLabel = groupName => { + return PERMISSIONS_GROUPS_MESSAGES[groupName] + ? formatMessage(PERMISSIONS_GROUPS_MESSAGES[groupName]) + : groupName; + }; + const getPermissionToolTip = permissionCodeName => { let title; const toolTipMessageObject = @@ -142,7 +149,9 @@ export const PermissionsSwitches: React.FunctionComponent = ({ {Object.keys(permissions).map(group => { return (
- {group} + + {getGroupPermissionLabel(group)} + diff --git a/hat/assets/js/apps/Iaso/domains/users/config.js b/hat/assets/js/apps/Iaso/domains/users/config.js index b26771102d..62496685ab 100644 --- a/hat/assets/js/apps/Iaso/domains/users/config.js +++ b/hat/assets/js/apps/Iaso/domains/users/config.js @@ -14,6 +14,7 @@ import { userHasPermission } from './utils'; import * as Permission from '../../utils/permissions.ts'; import PermissionTooltip from './components/PermissionTooltip.tsx'; +import PERMISSIONS_GROUPS_MESSAGES from './permissionsGroupsMessages.ts'; export const usersTableColumns = ({ formatMessage, @@ -111,6 +112,20 @@ export const useUserPermissionColumns = ({ setPermissions, currentUser }) => { sortable: false, width: 250, align: 'left', + Cell: settings => { + if (settings.row.original.group) { + return ( + + {formatMessage( + PERMISSIONS_GROUPS_MESSAGES[ + settings.row.original.permission + ], + )} + + ); + } + return settings.row.original.permission; + }, }, { Header: formatMessage(MESSAGES.userPermissions), From 4273d7309a82cc4b3d0661e827a267398a34c166 Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Wed, 22 May 2024 09:49:10 +0200 Subject: [PATCH 17/27] code review --- hat/assets/js/apps/Iaso/constants/routes.js | 2 +- .../registry/components/map/MapSettings.tsx | 6 ++-- .../map/OrgUnitChildrenLocations.tsx | 30 +++++++++---------- .../components/map/OrgUnitChildrenMap.tsx | 20 ++++++------- .../selectedOrgUnit/InstanceTitle.tsx | 15 ++++------ .../domains/registry/hooks/useGetForms.tsx | 2 ++ .../domains/registry/hooks/useGetOrgUnit.ts | 6 ++++ .../registry/hooks/useGetOrgUnitType.ts | 4 ++- .../apps/Iaso/domains/registry/types/index.ts | 2 +- 9 files changed, 45 insertions(+), 42 deletions(-) diff --git a/hat/assets/js/apps/Iaso/constants/routes.js b/hat/assets/js/apps/Iaso/constants/routes.js index d4804379ad..0e2562b008 100644 --- a/hat/assets/js/apps/Iaso/constants/routes.js +++ b/hat/assets/js/apps/Iaso/constants/routes.js @@ -575,7 +575,7 @@ export const registryPath = { }, { isRequired: false, - key: 'useCluster', + key: 'clusterEnabled', }, { isRequired: false, diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/MapSettings.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapSettings.tsx index 88e48cab97..2bae8cc895 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/MapSettings.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/MapSettings.tsx @@ -79,7 +79,7 @@ const styles: SxStyles = { }; export type Settings = { showTooltip: boolean; - useCluster: boolean; + clusterEnabled: boolean; }; type Props = { @@ -157,10 +157,10 @@ export const MapSettings: FunctionComponent = ({ control={ handleChangeSettings( - 'useCluster', + 'clusterEnabled', ) } color="primary" diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx index 49d5831d15..1fc4b29838 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenLocations.tsx @@ -14,6 +14,8 @@ import MarkersListComponent from '../../../../components/maps/markers/MarkersLis import { MapToolTip } from './MapTooltip'; import { selectedOrgUnitColor } from './OrgUnitChildrenMap'; +const MARKER_RADIUS = 12; + type Props = { selectedChildrenId: string | undefined; activeChildren: OrgUnit[]; @@ -26,7 +28,7 @@ type Props = { // eslint-disable-next-line no-unused-vars handleDoubleClick: (event: L.LeafletMouseEvent, ou: OrgUnit) => void; showTooltip: boolean; - useCluster: boolean; + clusterEnabled: boolean; index: number; subType: OrgunitType; }; @@ -36,7 +38,7 @@ export const OrgUnitChildrenLocations: FunctionComponent = ({ handleDoubleClick, activeChildren, showTooltip, - useCluster, + clusterEnabled, index, subType, selectedChildrenId, @@ -50,11 +52,13 @@ export const OrgUnitChildrenLocations: FunctionComponent = ({ ); }, [activeChildren, subType]); - const getColor = useCallback( - (children: OrgUnit) => { - return `${children.id}` === selectedChildrenId - ? selectedOrgUnitColor - : subType.color || ''; + const getMarkerProps = useCallback( + children => { + const color = + `${children.id}` === selectedChildrenId + ? selectedOrgUnitColor + : subType.color || ''; + return { ...circleColorMarkerOptions(color, MARKER_RADIUS) }; }, [selectedChildrenId, subType], ); @@ -63,7 +67,7 @@ export const OrgUnitChildrenLocations: FunctionComponent = ({ name={`children-locations-orgunit-type-${subType.id}`} style={{ zIndex: 600 + index }} > - {useCluster && ( + {clusterEnabled && ( colorClusterCustomMarker(cluster, subType.color || '') @@ -77,9 +81,7 @@ export const OrgUnitChildrenLocations: FunctionComponent = ({ ({ - ...circleColorMarkerOptions(getColor(children), 12), - })} + markerProps={getMarkerProps} onDblclick={handleDoubleClick} onMarkerClick={handleSingleClick} tooltipProps={e => ({ @@ -92,13 +94,11 @@ export const OrgUnitChildrenLocations: FunctionComponent = ({ /> )} - {!useCluster && ( + {!clusterEnabled && ( ({ - ...circleColorMarkerOptions(getColor(children), 12), - })} + markerProps={getMarkerProps} onDblclick={handleDoubleClick} onMarkerClick={handleSingleClick} tooltipProps={e => ({ diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx index 8291e2008b..cfc54b03c7 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/map/OrgUnitChildrenMap.tsx @@ -34,10 +34,11 @@ import { CustomTileLayer } from '../../../../components/maps/tools/CustomTileLay import { CustomZoomControl } from '../../../../components/maps/tools/CustomZoomControl'; import TILES from '../../../../constants/mapTiles'; import { baseUrls } from '../../../../constants/urls'; +import { useObjectState } from '../../../../hooks/useObjectState'; import { redirectTo, redirectToReplace } from '../../../../routing/actions'; import { HEIGHT } from '../../config'; import { RegistryParams } from '../../types'; -import { MapSettings, Settings } from './MapSettings'; +import { MapSettings } from './MapSettings'; import { OrgUnitChildrenLocations } from './OrgUnitChildrenLocations'; import { OrgUnitChildrenShapes } from './OrgUnitChildrenShapes'; import { OrgUnitLocation } from './OrgUnitLocation'; @@ -91,9 +92,9 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ }) => { const classes: Record = useStyles(); const dispatch = useDispatch(); - const [settings, setSettings] = useState({ + const [settings, setSettings] = useObjectState({ showTooltip: params.showTooltip === 'true', - useCluster: params.useCluster === 'true', + clusterEnabled: params.clusterEnabled === 'true', }); const [isMapFullScreen, setIsMapFullScreen] = useState( params.isFullScreen === 'true', @@ -113,7 +114,7 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ ); }, [orgUnitChildren, legendOptionsMap]); const isOrgUnitActive = Boolean(legendOptions[0]?.active); - const { showTooltip, useCluster } = settings; + const { showTooltip, clusterEnabled } = settings; const bounds = useMemo( () => mergeBounds( @@ -125,11 +126,8 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ const handleChangeSettings = useCallback( (setting: string) => { const newSetting = !settings[setting]; - setSettings(prevSettings => { - return { - ...prevSettings, - [setting]: newSetting, - }; + setSettings({ + [setting]: newSetting, }); dispatch( @@ -139,7 +137,7 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ }), ); }, - [dispatch, params, settings], + [dispatch, params, setSettings, settings], ); const handleToggleFullScreen = useCallback( @@ -248,7 +246,7 @@ export const OrgUnitChildrenMap: FunctionComponent = ({ = ({ const classes: Record = useStyles(); const { formatMessage } = useSafeIntl(); const getEnketoUrl = useGetEnketoUrl(window.location.href, currentInstance); - const currentUser = useCurrentUser(); - const currentInstanceId = useMemo(() => { return params.submissionId || orgUnit?.reference_instances?.[0]?.id; }, [params.submissionId, orgUnit]); @@ -120,10 +116,9 @@ export const InstanceTitle: FunctionComponent = ({ className={classes.paperTitleButtonContainer} > - {userHasPermission( - Permission.SUBMISSIONS_UPDATE, - currentUser, - ) && ( + getEnketoUrl()} overrideIcon={EnketoIcon} @@ -132,7 +127,7 @@ export const InstanceTitle: FunctionComponent = ({ size="small" tooltipMessage={MESSAGES.editOnEnketo} /> - )} + getRequest(url), options: { + staleTime: 1000 * 60 * 15, // in MS + cacheTime: 1000 * 60 * 5, select: data => data?.forms?.map(t => ({ label: t.name, diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts index 27204eac34..e8b2bb7da5 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnit.ts @@ -20,6 +20,8 @@ export const useGetOrgUnit = ( retry: false, enabled: Boolean(orgUnitId), keepPreviousData: true, + staleTime: 1000 * 60 * 15, // in MS + cacheTime: 1000 * 60 * 5, }, }); }; @@ -64,6 +66,8 @@ export const useGetOrgUnitListChildren = ( options: { keepPreviousData: true, enabled: Boolean(orgUnitParentId && orgUnitTypes), + staleTime: 1000 * 60 * 15, // in MS + cacheTime: 1000 * 60 * 5, select: data => { if (!data) return undefined; const orgunits: OrgUnit[] = data.orgunits.filter( @@ -101,6 +105,8 @@ export const useGetOrgUnitsMapChildren = ( queryKey: ['orgUnits', params], queryFn: () => getRequest(url), options: { + staleTime: 1000 * 60 * 15, // in MS + cacheTime: 1000 * 60 * 5, enabled: Boolean(orgUnitParentId && orgUnitTypes), select: (data: Result): OrgUnit[] => data?.orgUnits || [], }, diff --git a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnitType.ts b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnitType.ts index 2b1043461f..477729ccc0 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnitType.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/hooks/useGetOrgUnitType.ts @@ -1,7 +1,7 @@ import { UseQueryResult } from 'react-query'; -import { useSnackQuery } from '../../../libs/apiHooks'; import { getRequest } from '../../../libs/Api'; +import { useSnackQuery } from '../../../libs/apiHooks'; import { OrgunitType } from '../../orgUnits/types/orgunitTypes'; @@ -13,6 +13,8 @@ export const useGetOrgUnitType = ( queryKey, queryFn: () => getRequest(`/api/v2/orgunittypes/${orgUnitTypeId}/`), options: { + staleTime: 1000 * 60 * 15, // in MS + cacheTime: 1000 * 60 * 5, retry: false, enabled: Boolean(orgUnitTypeId), }, diff --git a/hat/assets/js/apps/Iaso/domains/registry/types/index.ts b/hat/assets/js/apps/Iaso/domains/registry/types/index.ts index 7f3d724f93..9f58663c3f 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/types/index.ts +++ b/hat/assets/js/apps/Iaso/domains/registry/types/index.ts @@ -17,7 +17,7 @@ export type RegistryParams = UrlParams & { submissionId?: string; missingSubmissionVisible?: 'true'; showTooltip?: 'true'; - useCluster?: 'true'; + clusterEnabled?: 'true'; isFullScreen?: 'true'; missingSubmissionsPageSize?: string; missingSubmissionsOrder?: string; From 263b5f00097c5ccca821b82b7d8eeb74d450035f Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Wed, 22 May 2024 14:49:28 +0200 Subject: [PATCH 18/27] display user role permissions in same as user ones --- .../Iaso/domains/app/translations/en.json | 1 + .../Iaso/domains/app/translations/fr.json | 1 + .../components/CreateEditUserRole.tsx | 8 +- .../components/PermissionsSwitches.tsx | 171 ++++++++---------- .../js/apps/Iaso/domains/userRoles/config.tsx | 79 +++++++- .../apps/Iaso/domains/userRoles/messages.ts | 4 + 6 files changed, 163 insertions(+), 101 deletions(-) diff --git a/hat/assets/js/apps/Iaso/domains/app/translations/en.json b/hat/assets/js/apps/Iaso/domains/app/translations/en.json index ad585eeb0c..69e8ff826e 100644 --- a/hat/assets/js/apps/Iaso/domains/app/translations/en.json +++ b/hat/assets/js/apps/Iaso/domains/app/translations/en.json @@ -1246,6 +1246,7 @@ "iaso.userRoles.delete": "Are you sure you want to delete this user role?", "iaso.userRoles.edit": "Edit user role", "iaso.userRoles.title": "User roles", + "iaso.userRoles.userRolePermissions": "User role permissions", "iaso.users.addLocations": "Add location(s)", "iaso.users.addProjects": "Add to project(s)", "iaso.users.addRoles": "Add user role(s)", diff --git a/hat/assets/js/apps/Iaso/domains/app/translations/fr.json b/hat/assets/js/apps/Iaso/domains/app/translations/fr.json index 5955b91f07..5ea0003eca 100644 --- a/hat/assets/js/apps/Iaso/domains/app/translations/fr.json +++ b/hat/assets/js/apps/Iaso/domains/app/translations/fr.json @@ -1246,6 +1246,7 @@ "iaso.userRoles.delete": "Etes-vous sûr(e) de vouloir effacer ce role d'utilisateur?", "iaso.userRoles.edit": "Editer un rôle d'utilisateur", "iaso.userRoles.title": "Rôles des utilisateurs", + "iaso.userRoles.userRolePermissions": "Permissions du rôle d'utilisateurs", "iaso.users.addLocations": "Ajouter la(les) localisation(s)", "iaso.users.addProjects": "Ajouter au(x) projet(s)", "iaso.users.addRoles": "Ajouter le(s) rôle(s)", diff --git a/hat/assets/js/apps/Iaso/domains/userRoles/components/CreateEditUserRole.tsx b/hat/assets/js/apps/Iaso/domains/userRoles/components/CreateEditUserRole.tsx index efcc2e3771..0a97d5b83e 100644 --- a/hat/assets/js/apps/Iaso/domains/userRoles/components/CreateEditUserRole.tsx +++ b/hat/assets/js/apps/Iaso/domains/userRoles/components/CreateEditUserRole.tsx @@ -93,6 +93,11 @@ export const CreateEditUserRole: FunctionComponent = ({ dialogType === 'create' ? formatMessage(MESSAGES.createUserRole) : formatMessage(MESSAGES.editUserRole); + + const handlePermissionsChange = newPermissions => { + setUserRolePermissoins(newPermissions); + setFieldValue('permissions', newPermissions); + }; return ( = ({ { - setUserRolePermissoins(newPermissions); - setFieldValue('permissions', newPermissions); + handlePermissionsChange(newPermissions); }} /> diff --git a/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx b/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx index a08d8660a5..7b8851f7ed 100644 --- a/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx +++ b/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx @@ -1,24 +1,31 @@ import React, { useCallback, useMemo } from 'react'; -import { Box, FormControlLabel, Switch, Tooltip, Grid } from '@mui/material'; +import { Box } from '@mui/material'; import { makeStyles } from '@mui/styles'; // @ts-ignore -import { useSafeIntl, LoadingSpinner } from 'bluesquare-components'; - -import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; +import { useSafeIntl, LoadingSpinner, Table } from 'bluesquare-components'; import MESSAGES from '../messages'; import { useSnackQuery } from '../../../libs/apiHooks'; import { getRequest } from '../../../libs/Api'; import { Permission } from '../types/userRoles'; import PERMISSIONS_MESSAGES from '../../users/permissionsMessages'; +import { useUserPermissionColumns } from '../config'; import PERMISSIONS_GROUPS_MESSAGES from '../../users/permissionsGroupsMessages'; const styles = theme => ({ container: { + '& .MuiTableHead-root': { + position: 'sticky', + top: 0, + zIndex: 10, + }, + '& .MuiTableContainer-root': { + maxHeight: '58vh', + overflow: 'auto', + border: `1px solid ${theme.palette.border.main}`, + }, marginTop: theme.spacing(2), - padding: theme.spacing(1), maxHeight: '60vh', overflow: 'scroll', - border: `1px solid ${theme.palette.border.main}`, }, }); @@ -30,6 +37,13 @@ type Props = { handleChange: (newValue: any) => void; }; +type Row = { + name?: string; + codename?: string; + group?: boolean; + id?: number; +}; + export const PermissionsSwitches: React.FunctionComponent = ({ userRolePermissions, handleChange, @@ -44,120 +58,81 @@ export const PermissionsSwitches: React.FunctionComponent = ({ const setPermissions = useCallback( (permission: Permission, isChecked: boolean) => { - const newUserPerms = [...userRolePermissions]; + const newUserRolePerms = [...userRolePermissions]; if (!isChecked) { - const permIndex = newUserPerms.findIndex(item => { + const permIndex = newUserRolePerms.findIndex(item => { return item.codename === permission.codename; }); - newUserPerms.splice(permIndex, 1); + newUserRolePerms.splice(permIndex, 1); } else { - newUserPerms.push(permission); + newUserRolePerms.push({ + id: permission.id, + codename: permission.codename, + name: permission.name, + }); } - handleChange(newUserPerms); + handleChange(newUserRolePerms); }, [handleChange, userRolePermissions], ); - const getPermissionLabel = permissionCodeName => { - return PERMISSIONS_MESSAGES[permissionCodeName] - ? formatMessage(PERMISSIONS_MESSAGES[permissionCodeName]) - : permissionCodeName; - }; - - const getGroupPermissionLabel = groupName => { + const groupPermissionLabel = groupName => { return PERMISSIONS_GROUPS_MESSAGES[groupName] ? formatMessage(PERMISSIONS_GROUPS_MESSAGES[groupName]) : groupName; }; - const getPermissionToolTip = permissionCodeName => { - let title; - const toolTipMessageObject = - PERMISSIONS_MESSAGES[`${permissionCodeName}_tooltip`]; - if (toolTipMessageObject) { - title = formatMessage(toolTipMessageObject); - } - if (title) { - return ( - - - - ); - } - return ''; - }; - - const permissions = useMemo( + const permissions_groups = useMemo( () => data?.permissions ?? [], [data?.permissions], ); - const DisplayPermissions = ({ group_permissions }) => { - return group_permissions - .sort((a, b) => - getPermissionLabel(a.codename).localeCompare( - getPermissionLabel(b.codename), - undefined, - { - sensitivity: 'accent', - }, - ), - ) - .map(p => ( - - -
- - up.codename === p.codename, - ), - )} - onChange={e => - setPermissions(p, e.target.checked) - } - name={p.codename} - color="primary" - /> - } - label={getPermissionLabel(p.codename)} - /> -
-
- - {getPermissionToolTip(p.codename)} - -
- )); - }; + const permissionLabel = useCallback( + permissionCodeName => { + return PERMISSIONS_MESSAGES[permissionCodeName] + ? formatMessage(PERMISSIONS_MESSAGES[permissionCodeName]) + : permissionCodeName; + }, + [formatMessage], + ); + + const permissions: any = []; + + Object.keys(permissions_groups).forEach(group => { + let row: Row = {}; + row.codename = groupPermissionLabel(group); + row.group = true; + permissions.push(row); + permissions_groups[group].forEach(permission => { + row = {}; + row.id = permission.id; + row.codename = permission.codename; + row.name = permissionLabel(permission.codename); + permissions.push(row); + }); + }); + + const columns = useUserPermissionColumns( + setPermissions, + userRolePermissions, + ); return ( {isLoading && } - - {Object.keys(permissions).map(group => { - return ( -
- - {getGroupPermissionLabel(group)} - - -
- ); - })} + {/* @ts-ignore */} + ); }; diff --git a/hat/assets/js/apps/Iaso/domains/userRoles/config.tsx b/hat/assets/js/apps/Iaso/domains/userRoles/config.tsx index 5e548b5b85..158de608bf 100644 --- a/hat/assets/js/apps/Iaso/domains/userRoles/config.tsx +++ b/hat/assets/js/apps/Iaso/domains/userRoles/config.tsx @@ -1,10 +1,13 @@ import React, { ReactElement, useMemo } from 'react'; import { useSafeIntl, Column, IntlFormatMessage } from 'bluesquare-components'; +import { Switch } from '@mui/material'; import { EditUserRoleDialog } from './components/CreateEditUserRole'; import { DateTimeCell } from '../../components/Cells/DateTimeCell'; import MESSAGES from './messages'; +import USER_MESSAGES from '../users/messages'; import DeleteDialog from '../../components/dialogs/DeleteDialogComponent'; -import { UserRole } from './types/userRoles'; +import { UserRole, Permission } from './types/userRoles'; +import PermissionTooltip from '../users/components/PermissionTooltip'; export const useGetUserRolesColumns = ( // eslint-disable-next-line no-unused-vars @@ -61,3 +64,77 @@ export const useGetUserRolesColumns = ( return columns; }, [deleteUserRole, formatMessage]); }; + +export const useUserPermissionColumns = ( + // eslint-disable-next-line no-unused-vars + setPermissions: (permission: Permission, isChecked: boolean) => void, + userRolePermissions: Permission[], +): Array => { + const { formatMessage } = useSafeIntl(); + return useMemo(() => { + return [ + { + Header: '', + id: 'tooltip', + sortable: false, + align: 'center', + width: 50, + Cell: settings => { + return ( + + ); + }, + }, + { + Header: formatMessage(USER_MESSAGES.permissions), + id: 'name', + accessor: 'name', + sortable: false, + width: 250, + align: 'left', + Cell: settings => { + if (settings.row.original.group) { + return ( + {settings.row.original.codename} + ); + } + return settings.row.original.name; + }, + }, + { + Header: formatMessage(MESSAGES.userRolePermissions), + id: 'codename', + accessor: 'codename', + sortable: false, + Cell: settings => { + if (!settings.row.original.group) { + return ( + + permi.codename === + settings.row.original.codename, + ), + )} + onChange={e => + setPermissions( + settings.row.original, + e.target.checked, + ) + } + name={settings.row.original.codename} + color="primary" + /> + ); + } + return ''; + }, + }, + ]; + }, [formatMessage, setPermissions, userRolePermissions]); +}; diff --git a/hat/assets/js/apps/Iaso/domains/userRoles/messages.ts b/hat/assets/js/apps/Iaso/domains/userRoles/messages.ts index 52581a2e26..a28fd3b577 100644 --- a/hat/assets/js/apps/Iaso/domains/userRoles/messages.ts +++ b/hat/assets/js/apps/Iaso/domains/userRoles/messages.ts @@ -65,6 +65,10 @@ const MESSAGES = defineMessages({ id: 'iaso.forms.error.fieldRequired', defaultMessage: 'This field is required', }, + userRolePermissions: { + id: 'iaso.userRoles.userRolePermissions', + defaultMessage: 'User role permissions', + }, }); export default MESSAGES; From ce0dea6206c971b4bce2a3119d29f47b6d695733 Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Wed, 22 May 2024 15:12:45 +0200 Subject: [PATCH 19/27] change static method into instance method --- iaso/api/permissions.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/iaso/api/permissions.py b/iaso/api/permissions.py index 15b56fbdd1..42cedf8a6e 100644 --- a/iaso/api/permissions.py +++ b/iaso/api/permissions.py @@ -24,9 +24,8 @@ class PermissionsViewSet(viewsets.ViewSet): permission_classes = [permissions.IsAuthenticated] - @staticmethod - def list(request): - perms = PermissionsViewSet.queryset(request) + def list(self, request): + perms = self.queryset(request) result = [] for permission in perms: @@ -34,19 +33,18 @@ def list(request): return Response({"permissions": sorted(result, key=itemgetter("name"))}) - @staticmethod @action(methods=["GET"], detail=False) - def grouped_permissions(request): - perms = PermissionsViewSet.queryset(request) + def grouped_permissions(self, request): + perms = self.queryset(request) result = {} for group in PERMISSIONS_PRESENTATION.keys(): result[group] = [] for permission in perms.filter(codename__in=PERMISSIONS_PRESENTATION[group]): result[group].append({"id": permission.id, "name": _(permission.name), "codename": permission.codename}) + return Response({"permissions": result}) - @staticmethod - def queryset(request): + def queryset(self, request): if request.user.has_perm(p.USERS_ADMIN) or request.user.has_perm(p.USERS_MANAGED): perms = Permission.objects else: From 80664e05e3082c252605b4516bc04ee90efb816c Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Wed, 22 May 2024 15:23:42 +0200 Subject: [PATCH 20/27] add in docs the way to add permission to a group --- .../how_to/add_new_permission/add_new_permission.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/pages/dev/how_to/add_new_permission/add_new_permission.md b/docs/pages/dev/how_to/add_new_permission/add_new_permission.md index 5b85c08a39..5b5b72203a 100644 --- a/docs/pages/dev/how_to/add_new_permission/add_new_permission.md +++ b/docs/pages/dev/how_to/add_new_permission/add_new_permission.md @@ -14,16 +14,22 @@ - If no existing module fits, create one (see exiting modules for inspiration) - If a new module is created, add it to `MODULES` (in the same file) -## 3. Make and run migration +## 3. Include the permission in the corresponding group +- Go to `/hat/menupermissions/constants.py` +- Add the permission to a group from `PERMISSIONS_PRESENTATION` +- If no existing group fits, create one (see exiting groups for inspiration) +- If the corresponding group exists add the new permission to that group (see exiting groups for inspiration) + +## 4. Make and run migration `docker-compose run --rm iaso manage makemigration && docker-compose run --rm iaso manage migrate` -## 4. Add translations in the front-end +## 5. Add translations in the front-end - Add a translation for the permission, and its tooltip in `permissionMessages.ts` - Add corresponding translations in `en.json` and `fr.json` -## 5. Add translation for new module (if applicable) +## 6. Add translation for new module (if applicable) - Go to `/hat/assets/js/apps/Iaso/domains/modules/messages.ts` - Add translation for the new module. The translation key should follow the pattern: `iaso.module.' From 65b8d6b7c32a450dffa38e8640a51b0eedb3ffc7 Mon Sep 17 00:00:00 2001 From: Beygorghor Date: Wed, 22 May 2024 16:23:34 +0200 Subject: [PATCH 21/27] hotfix registry links --- .../registry/components/selectedOrgUnit/OrgUnitTitle.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/OrgUnitTitle.tsx b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/OrgUnitTitle.tsx index cd8d1dff81..9c07d2210b 100644 --- a/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/OrgUnitTitle.tsx +++ b/hat/assets/js/apps/Iaso/domains/registry/components/selectedOrgUnit/OrgUnitTitle.tsx @@ -57,7 +57,7 @@ export const OrgUnitTitle: FunctionComponent = ({ orgUnit, params }) => { {isRootOrgUnit && ( = ({ orgUnit, params }) => { /> )} Date: Wed, 22 May 2024 15:25:31 +0100 Subject: [PATCH 22/27] IA-2991 Fix for bug only when no limit parameter was given --- iaso/models/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iaso/models/base.py b/iaso/models/base.py index 3b97a436d3..56f1f39b45 100644 --- a/iaso/models/base.py +++ b/iaso/models/base.py @@ -1350,8 +1350,8 @@ def as_short_dict(self): "email": self.user.email, "language": self.language, "user_id": self.user.id, - "phone_number": self.phone_number if len(self.phone_number) > 0 else None, - "country_code": self.phone_number.country_code if self.phone_number else None, + "phone_number": self.phone_number.as_e164 if self.phone_number else None, + "country_code": region_code_for_number(self.phone_number).lower() if self.phone_number else None, "projects": [p.as_dict() for p in self.projects.all().order_by("name")], } From e61b357a5a4d0bb5481c1aa5e9608abb9636c619 Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Thu, 23 May 2024 12:44:43 +0200 Subject: [PATCH 23/27] Refactor the code to use useMemo in getting allPermissions and rename the permissionLabel into getPermissionLabel --- .../components/PermissionsSwitches.tsx | 45 ++++++++++--------- .../users/components/PermissionsSwitches.tsx | 23 +++++----- .../users/hooks/useGetUserPermissions.tsx | 18 ++++---- 3 files changed, 47 insertions(+), 39 deletions(-) diff --git a/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx b/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx index 7b8851f7ed..0513976f7d 100644 --- a/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx +++ b/hat/assets/js/apps/Iaso/domains/userRoles/components/PermissionsSwitches.tsx @@ -76,18 +76,21 @@ export const PermissionsSwitches: React.FunctionComponent = ({ [handleChange, userRolePermissions], ); - const groupPermissionLabel = groupName => { - return PERMISSIONS_GROUPS_MESSAGES[groupName] - ? formatMessage(PERMISSIONS_GROUPS_MESSAGES[groupName]) - : groupName; - }; + const groupPermissionLabel = useCallback( + groupName => { + return PERMISSIONS_GROUPS_MESSAGES[groupName] + ? formatMessage(PERMISSIONS_GROUPS_MESSAGES[groupName]) + : groupName; + }, + [formatMessage], + ); const permissions_groups = useMemo( () => data?.permissions ?? [], [data?.permissions], ); - const permissionLabel = useCallback( + const getPermissionLabel = useCallback( permissionCodeName => { return PERMISSIONS_MESSAGES[permissionCodeName] ? formatMessage(PERMISSIONS_MESSAGES[permissionCodeName]) @@ -96,21 +99,23 @@ export const PermissionsSwitches: React.FunctionComponent = ({ [formatMessage], ); - const permissions: any = []; - - Object.keys(permissions_groups).forEach(group => { - let row: Row = {}; - row.codename = groupPermissionLabel(group); - row.group = true; - permissions.push(row); - permissions_groups[group].forEach(permission => { - row = {}; - row.id = permission.id; - row.codename = permission.codename; - row.name = permissionLabel(permission.codename); - permissions.push(row); + const permissions: Row[] = useMemo(() => { + const grouped_permissions: Row[] = []; + Object.keys(permissions_groups).forEach(group => { + let row: Row = {}; + row.codename = groupPermissionLabel(group); + row.group = true; + grouped_permissions.push(row); + permissions_groups[group].forEach(permission => { + row = {}; + row.id = permission.id; + row.codename = permission.codename; + row.name = getPermissionLabel(permission.codename); + grouped_permissions.push(row); + }); }); - }); + return grouped_permissions; + }, [getPermissionLabel, groupPermissionLabel, permissions_groups]); const columns = useUserPermissionColumns( setPermissions, diff --git a/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx b/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx index 26a3f603e3..7181212716 100644 --- a/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx +++ b/hat/assets/js/apps/Iaso/domains/users/components/PermissionsSwitches.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Box, Typography } from '@mui/material'; import { makeStyles } from '@mui/styles'; import { @@ -89,15 +89,18 @@ const PermissionsSwitches: React.FunctionComponent = ({ handleChange(newUserPerms); }; const loggedInUser = useCurrentUser(); - const groups = data?.permissions ? Object.keys(data?.permissions) : []; - const allPermissions = {}; - groups.forEach(group => { - allPermissions[group] = - data?.permissions[group]?.filter(permission => - canAssignPermission(loggedInUser, permission), - ) ?? []; - }); + const allPermissions = useMemo(() => { + const groups = data?.permissions ? Object.keys(data?.permissions) : []; + const permissions = {}; + groups.forEach(group => { + permissions[group] = + data?.permissions[group]?.filter(permission => + canAssignPermission(loggedInUser, permission), + ) ?? []; + }); + return permissions; + }, [data?.permissions, loggedInUser]); const userPermissions = currentUser.user_permissions.value; const { data: userRoles, isFetching } = useGetUserRolesDropDown(); @@ -105,7 +108,7 @@ const PermissionsSwitches: React.FunctionComponent = ({ allPermissions, userPermissions, ); - // console.log(permissionsData); + // This is a problem with the type definition of Column is bluesquare-components // @ts-ignore const columns: Column[] = useUserPermissionColumns({ diff --git a/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx b/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx index 7baa43b6ef..6817301062 100644 --- a/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx +++ b/hat/assets/js/apps/Iaso/domains/users/hooks/useGetUserPermissions.tsx @@ -14,7 +14,7 @@ export const useGetUserPermissions = ( userPermissions: string[], ): any => { const { formatMessage } = useSafeIntl(); - const permissionLabel = useCallback( + const getPermissionLabel = useCallback( permissionCodeName => { return PERMISSIONS_MESSAGES[permissionCodeName] ? formatMessage(PERMISSIONS_MESSAGES[permissionCodeName]) @@ -25,7 +25,7 @@ export const useGetUserPermissions = ( const sortedPermissions = useGetSortedPermissions({ allPermissions, - permissionLabel, + getPermissionLabel, }); return useMemo(() => { @@ -38,7 +38,7 @@ export const useGetUserPermissions = ( data.push(row); sortedPermissions[group].forEach(p => { row = {}; - row.permission = permissionLabel(p.codename); + row.permission = getPermissionLabel(p.codename); row.userPermissions = userPermissions; row.permissionCodeName = p.codename; @@ -46,25 +46,25 @@ export const useGetUserPermissions = ( }); }); return data; - }, [permissionLabel, sortedPermissions, userPermissions]); + }, [getPermissionLabel, sortedPermissions, userPermissions]); }; type SortProps = { allPermissions: Permission[]; // eslint-disable-next-line no-unused-vars - permissionLabel: (codename: string) => string; + getPermissionLabel: (codename: string) => string; }; const useGetSortedPermissions = ({ allPermissions, - permissionLabel, + getPermissionLabel, }: SortProps): any => { return useMemo(() => { const sortedPermissions = {}; Object.keys(allPermissions).forEach(group => { sortedPermissions[group] = allPermissions[group].sort((a, b) => - permissionLabel(a.codename).localeCompare( - permissionLabel(b.codename), + getPermissionLabel(a.codename).localeCompare( + getPermissionLabel(b.codename), undefined, { sensitivity: 'accent', @@ -73,5 +73,5 @@ const useGetSortedPermissions = ({ ); }); return sortedPermissions; - }, [allPermissions, permissionLabel]); + }, [allPermissions, getPermissionLabel]); }; From a8a9013323ae3c76db922a2b3c167a50b5c8b8bc Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Thu, 23 May 2024 14:26:01 +0200 Subject: [PATCH 24/27] fix import of ./permissionsGroupsMessages --- hat/assets/js/apps/Iaso/domains/users/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hat/assets/js/apps/Iaso/domains/users/config.js b/hat/assets/js/apps/Iaso/domains/users/config.js index 62496685ab..d6fc3455f0 100644 --- a/hat/assets/js/apps/Iaso/domains/users/config.js +++ b/hat/assets/js/apps/Iaso/domains/users/config.js @@ -14,7 +14,7 @@ import { userHasPermission } from './utils'; import * as Permission from '../../utils/permissions.ts'; import PermissionTooltip from './components/PermissionTooltip.tsx'; -import PERMISSIONS_GROUPS_MESSAGES from './permissionsGroupsMessages.ts'; +import PERMISSIONS_GROUPS_MESSAGES from './permissionsGroupsMessages'; export const usersTableColumns = ({ formatMessage, From 199d93b2fa14e077cd901c45dd33806ad78884c0 Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Thu, 23 May 2024 14:36:00 +0200 Subject: [PATCH 25/27] add missing file --- .../users/permissionsGroupsMessages.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 hat/assets/js/apps/Iaso/domains/users/permissionsGroupsMessages.ts diff --git a/hat/assets/js/apps/Iaso/domains/users/permissionsGroupsMessages.ts b/hat/assets/js/apps/Iaso/domains/users/permissionsGroupsMessages.ts new file mode 100644 index 0000000000..5fef7afd65 --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/users/permissionsGroupsMessages.ts @@ -0,0 +1,49 @@ +/* eslint-disable max-len */ +import { defineMessages } from 'react-intl'; + +// List of translations for Iaso groups of permissions used all along the project + +const PERMISSIONS_GROUPS_MESSAGES = defineMessages({ + forms: { + id: 'iaso.permissions.group.forms', + defaultMessage: 'Forms :', + }, + org_units: { + id: 'iaso.permissions.group.org_units', + defaultMessage: 'Org units :', + }, + entities: { + id: 'iaso.permissions.group.entities', + defaultMessage: 'Beneficiaries :', + }, + payments: { + id: 'iaso.permissions.group.payments', + defaultMessage: 'Payments :', + }, + dhis2_mapping: { + id: 'iaso.permissions.group.dhis2_mapping', + defaultMessage: 'Dhis2 mapping :', + }, + external_storage: { + id: 'iaso.permissions.group.external_storage', + defaultMessage: 'External storage :', + }, + planning: { + id: 'iaso.permissions.group.planning', + defaultMessage: 'Planning :', + }, + embedded_links: { + id: 'iaso.permissions.group.embedded_links', + defaultMessage: 'Embedded links :', + }, + polio: { + id: 'iaso.permissions.group.polio', + defaultMessage: 'Polio :', + }, + admin: { + id: 'iaso.permissions.group.admin', + defaultMessage: 'Admin :', + }, +}); + +export default PERMISSIONS_GROUPS_MESSAGES; From be7376293c84d279b33cf748d4f9e36106677b82 Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Thu, 23 May 2024 14:36:58 +0200 Subject: [PATCH 26/27] use the correct import --- hat/assets/js/apps/Iaso/domains/users/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hat/assets/js/apps/Iaso/domains/users/config.js b/hat/assets/js/apps/Iaso/domains/users/config.js index d6fc3455f0..62496685ab 100644 --- a/hat/assets/js/apps/Iaso/domains/users/config.js +++ b/hat/assets/js/apps/Iaso/domains/users/config.js @@ -14,7 +14,7 @@ import { userHasPermission } from './utils'; import * as Permission from '../../utils/permissions.ts'; import PermissionTooltip from './components/PermissionTooltip.tsx'; -import PERMISSIONS_GROUPS_MESSAGES from './permissionsGroupsMessages'; +import PERMISSIONS_GROUPS_MESSAGES from './permissionsGroupsMessages.ts'; export const usersTableColumns = ({ formatMessage, From 3cd55ed12cff8b5b37ce8ef0cf6b2475cfe825ef Mon Sep 17 00:00:00 2001 From: HAKIZIMANA Franck Date: Thu, 23 May 2024 15:29:18 +0200 Subject: [PATCH 27/27] use a separated switch component --- .../js/apps/Iaso/domains/userRoles/config.tsx | 35 +++++---------- .../users/components/PermissionSwitch.tsx | 44 +++++++++++++++++++ .../js/apps/Iaso/domains/users/config.js | 37 +++++----------- 3 files changed, 64 insertions(+), 52 deletions(-) create mode 100644 hat/assets/js/apps/Iaso/domains/users/components/PermissionSwitch.tsx diff --git a/hat/assets/js/apps/Iaso/domains/userRoles/config.tsx b/hat/assets/js/apps/Iaso/domains/userRoles/config.tsx index 158de608bf..6b3fd7a4cf 100644 --- a/hat/assets/js/apps/Iaso/domains/userRoles/config.tsx +++ b/hat/assets/js/apps/Iaso/domains/userRoles/config.tsx @@ -1,6 +1,5 @@ import React, { ReactElement, useMemo } from 'react'; import { useSafeIntl, Column, IntlFormatMessage } from 'bluesquare-components'; -import { Switch } from '@mui/material'; import { EditUserRoleDialog } from './components/CreateEditUserRole'; import { DateTimeCell } from '../../components/Cells/DateTimeCell'; import MESSAGES from './messages'; @@ -8,6 +7,7 @@ import USER_MESSAGES from '../users/messages'; import DeleteDialog from '../../components/dialogs/DeleteDialogComponent'; import { UserRole, Permission } from './types/userRoles'; import PermissionTooltip from '../users/components/PermissionTooltip'; +import PermissionSwitch from '../users/components/PermissionSwitch'; export const useGetUserRolesColumns = ( // eslint-disable-next-line no-unused-vars @@ -109,30 +109,15 @@ export const useUserPermissionColumns = ( accessor: 'codename', sortable: false, Cell: settings => { - if (!settings.row.original.group) { - return ( - - permi.codename === - settings.row.original.codename, - ), - )} - onChange={e => - setPermissions( - settings.row.original, - e.target.checked, - ) - } - name={settings.row.original.codename} - color="primary" - /> - ); - } - return ''; + return ( + + ); }, }, ]; diff --git a/hat/assets/js/apps/Iaso/domains/users/components/PermissionSwitch.tsx b/hat/assets/js/apps/Iaso/domains/users/components/PermissionSwitch.tsx new file mode 100644 index 0000000000..ee5b76301e --- /dev/null +++ b/hat/assets/js/apps/Iaso/domains/users/components/PermissionSwitch.tsx @@ -0,0 +1,44 @@ +import { Switch } from '@mui/material'; +import React from 'react'; +import { Permission } from '../../userRoles/types/userRoles'; + +type Props = { + value: string | Permission; + codeName: string; + settings: any; + // eslint-disable-next-line no-unused-vars + setPermissions: (permission: string | Permission, checked: boolean) => void; + permissions: Permission[]; +}; + +const PermissionSwitch: React.FunctionComponent = ({ + value, + codeName, + settings, + setPermissions, + permissions, +}) => { + if (!settings.row.original.group) { + return ( + { + return typeof up === 'string' + ? up === settings.row.original[codeName] + : up.codename === settings.row.original[codeName]; + }), + )} + onChange={e => { + setPermissions(value, e.target.checked); + }} + name={settings.row.original[codeName]} + color="primary" + /> + ); + } + return null; +}; + +export default PermissionSwitch; diff --git a/hat/assets/js/apps/Iaso/domains/users/config.js b/hat/assets/js/apps/Iaso/domains/users/config.js index 62496685ab..cb563b9d2c 100644 --- a/hat/assets/js/apps/Iaso/domains/users/config.js +++ b/hat/assets/js/apps/Iaso/domains/users/config.js @@ -1,6 +1,5 @@ import React, { useMemo } from 'react'; import { textPlaceholder, useSafeIntl } from 'bluesquare-components'; -import { Switch } from '@mui/material'; import { HighlightOffOutlined as NotCheckedIcon, CheckCircleOutlineOutlined as CheckedIcon, @@ -15,6 +14,7 @@ import { userHasPermission } from './utils'; import * as Permission from '../../utils/permissions.ts'; import PermissionTooltip from './components/PermissionTooltip.tsx'; import PERMISSIONS_GROUPS_MESSAGES from './permissionsGroupsMessages.ts'; +import PermissionSwitch from './components/PermissionSwitch.tsx'; export const usersTableColumns = ({ formatMessage, @@ -133,32 +133,15 @@ export const useUserPermissionColumns = ({ setPermissions, currentUser }) => { accessor: 'userPermission', sortable: false, Cell: settings => { - if (!settings.row.original.group) { - return ( - - up === - settings.row.original - .permissionCodeName, - ), - )} - onChange={e => - setPermissions( - settings.row.original - .permissionCodeName, - e.target.checked, - ) - } - name={settings.row.original.permissionCodeName} - color="primary" - /> - ); - } - return ''; + return ( + + ); }, }, ];