From 7f52f0ac73dffbcc7ac718a6ea4602679fd008f9 Mon Sep 17 00:00:00 2001 From: Tom Caiger Date: Wed, 12 Jul 2023 17:38:20 +1200 Subject: [PATCH] WAITP-1223: Tupaia web map legend (#4724) * setup map legend * update legend props * mobile map legend * Update MobileMapLegend.tsx * refactor map legend * fix console warnings --- packages/tupaia-web/package.json | 2 +- packages/tupaia-web/src/features/Map/Map.tsx | 13 +++-- .../Map/MapLegend/DesktopMapLegend.tsx | 37 ------------ .../src/features/Map/MapLegend/MapLegend.tsx | 56 +++++++++++++++++-- .../Map/MapLegend/MobileMapLegend.tsx | 14 +++-- .../DesktopMapOverlaySelector.tsx | 17 ++---- .../MapOverlayDatePicker.tsx | 13 +---- .../features/Map/MarkerLayer/MarkerLayer.tsx | 11 ++-- .../src/features/Map/utils/index.ts | 8 +++ .../Map/{ => utils}/useDefaultMapOverlay.ts | 6 +- .../features/Map/utils/useHiddenMapValues.ts | 29 ++++++++++ .../features/Map/utils/useMapOverlayReport.ts | 25 +++++++++ .../src/components/Legend/Legend.tsx | 7 +-- .../src/components/Legend/LegendEntry.tsx | 2 +- .../ui-map-components/src/types/legend.ts | 2 +- 15 files changed, 154 insertions(+), 88 deletions(-) delete mode 100644 packages/tupaia-web/src/features/Map/MapLegend/DesktopMapLegend.tsx create mode 100644 packages/tupaia-web/src/features/Map/utils/index.ts rename packages/tupaia-web/src/features/Map/{ => utils}/useDefaultMapOverlay.ts (93%) create mode 100644 packages/tupaia-web/src/features/Map/utils/useHiddenMapValues.ts create mode 100644 packages/tupaia-web/src/features/Map/utils/useMapOverlayReport.ts diff --git a/packages/tupaia-web/package.json b/packages/tupaia-web/package.json index 9c896aa250..0602a5919d 100644 --- a/packages/tupaia-web/package.json +++ b/packages/tupaia-web/package.json @@ -15,7 +15,7 @@ "lint": "yarn package:lint", "lint:fix": "yarn lint --fix", "preview": "vite preview", - "start-dev": "REACT_APP_DEPLOYMENT_NAME='dev' && vite", + "start-dev": "vite", "start-fullstack": "npm-run-all -c -l -p start-servers start-frontend", "start-central-server": "yarn workspace @tupaia/central-server start-dev", "start-entity-server": "yarn workspace @tupaia/entity-server start-dev", diff --git a/packages/tupaia-web/src/features/Map/Map.tsx b/packages/tupaia-web/src/features/Map/Map.tsx index 4a8fac95fb..ee7b9d3baa 100644 --- a/packages/tupaia-web/src/features/Map/Map.tsx +++ b/packages/tupaia-web/src/features/Map/Map.tsx @@ -20,7 +20,7 @@ import { MapOverlaySelector } from './MapOverlaySelector'; import { useEntity, useMapOverlays } from '../../api/queries'; import { PolygonLayer } from './PolygonLayer'; import { MarkerLayer } from './MarkerLayer'; -import { useDefaultMapOverlay } from './useDefaultMapOverlay'; +import { useHiddenMapValues, useMapOverlayReport, useDefaultMapOverlay } from './utils'; const MapContainer = styled.div` height: 100%; @@ -91,13 +91,18 @@ const MapControlColumn = styled.div` export const Map = () => { const { projectCode, entityCode } = useParams(); - const [activeTileSet, setActiveTileSet] = useState(TILE_SETS[0]); const { data: entity } = useEntity(entityCode); // set the map default overlay if there isn't one selected const { mapOverlaysByCode } = useMapOverlays(projectCode, entityCode); useDefaultMapOverlay(projectCode!, mapOverlaysByCode); + // Setup legend hidden values + const { data: measureData } = useMapOverlayReport(); + const { hiddenValues, setValueHidden } = useHiddenMapValues(measureData?.serieses); + + // Setup Tile Picker + const [activeTileSet, setActiveTileSet] = useState(TILE_SETS[0]); const onTileSetChange = (tileSetKey: string) => { setActiveTileSet(TILE_SETS.find(({ key }) => key === tileSetKey) as typeof TILE_SETS[0]); }; @@ -107,7 +112,7 @@ export const Map = () => { - + @@ -115,7 +120,7 @@ export const Map = () => { - + { - return ( - - - - ); -}; diff --git a/packages/tupaia-web/src/features/Map/MapLegend/MapLegend.tsx b/packages/tupaia-web/src/features/Map/MapLegend/MapLegend.tsx index 9c3d09c650..718869085e 100644 --- a/packages/tupaia-web/src/features/Map/MapLegend/MapLegend.tsx +++ b/packages/tupaia-web/src/features/Map/MapLegend/MapLegend.tsx @@ -4,14 +4,62 @@ */ import React from 'react'; +import { Legend, LegendProps } from '@tupaia/ui-map-components'; import { MobileMapLegend } from './MobileMapLegend'; -import { DesktopMapLegend } from './DesktopMapLegend'; +import { useSearchParams } from 'react-router-dom'; +import { MOBILE_BREAKPOINT, URL_SEARCH_PARAMS } from '../../../constants'; +import { useMapOverlayReport } from '../utils'; +import styled from 'styled-components'; + +const DesktopWrapper = styled.div` + pointer-events: auto; + + @media screen and (max-width: ${MOBILE_BREAKPOINT}) { + display: none; + } +`; + +const SeriesDivider = styled.div` + height: 0; + border-width: 1px; + border-style: solid; + border-color: ${({ theme }) => (theme.palette.type === 'light' ? '#00000022' : '#ffffff22')}; + width: calc(100% - 2rem); + margin: 0.3rem auto 0.2rem; + + @media screen and (min-width: ${MOBILE_BREAKPOINT}) { + display: none; + } +`; + +export const MapLegend = ({ hiddenValues, setValueHidden }: LegendProps) => { + const [urlSearchParams] = useSearchParams(); + const selectedOverlay = urlSearchParams.get(URL_SEARCH_PARAMS.MAP_OVERLAY); + const { data: overlayReportData } = useMapOverlayReport(); + + if (!selectedOverlay) { + return null; + } + + const LegendComponent = () => ( + + ); -export const MapLegend = () => { return ( <> - - + + + + + + ); }; diff --git a/packages/tupaia-web/src/features/Map/MapLegend/MobileMapLegend.tsx b/packages/tupaia-web/src/features/Map/MapLegend/MobileMapLegend.tsx index 8094513b8a..9c1264e350 100644 --- a/packages/tupaia-web/src/features/Map/MapLegend/MobileMapLegend.tsx +++ b/packages/tupaia-web/src/features/Map/MapLegend/MobileMapLegend.tsx @@ -2,7 +2,7 @@ * Tupaia * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd */ -import React, { useState } from 'react'; +import React, { ReactNode, useState } from 'react'; import { Close, ExpandLess } from '@material-ui/icons'; import { Button, IconButton } from '@tupaia/ui-components'; import styled from 'styled-components'; @@ -10,8 +10,10 @@ import { MOBILE_BREAKPOINT } from '../../../constants'; const Wrapper = styled.div` position: absolute; - bottom: 1rem; + pointer-events: auto; + bottom: 0.8rem; right: 1rem; + padding: 0; @media screen and (min-width: ${MOBILE_BREAKPOINT}) { display: none; } @@ -30,10 +32,11 @@ const ExpandIcon = styled(ExpandLess)` `; const ExpandedLegend = styled.div` + display: block; background-color: ${({ theme }) => theme.mobile.background}; - height: 20rem; - width: 12rem; border-radius: 0.5rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; `; const CloseButton = styled(IconButton).attrs({ @@ -44,7 +47,7 @@ const CloseButton = styled(IconButton).attrs({ right: 0; `; -export const MobileMapLegend = () => { +export const MobileMapLegend = ({ children }: { children: ReactNode }) => { const [expanded, setExpanded] = useState(false); const toggleExpanded = () => { setExpanded(!expanded); @@ -56,6 +59,7 @@ export const MobileMapLegend = () => { + {children} ) : ( diff --git a/packages/tupaia-web/src/features/Map/MapOverlaySelector/DesktopMapOverlaySelector.tsx b/packages/tupaia-web/src/features/Map/MapOverlaySelector/DesktopMapOverlaySelector.tsx index 85ff06f677..0a0ebf507b 100644 --- a/packages/tupaia-web/src/features/Map/MapOverlaySelector/DesktopMapOverlaySelector.tsx +++ b/packages/tupaia-web/src/features/Map/MapOverlaySelector/DesktopMapOverlaySelector.tsx @@ -9,12 +9,12 @@ import { useParams } from 'react-router'; import { Accordion, Typography, AccordionSummary, AccordionDetails } from '@material-ui/core'; import { ExpandMore, Layers } from '@material-ui/icons'; import { periodToMoment } from '@tupaia/utils'; -import { MOBILE_BREAKPOINT, URL_SEARCH_PARAMS } from '../../../constants'; +import { MOBILE_BREAKPOINT } from '../../../constants'; import { Entity } from '../../../types'; -import { useMapOverlayReport, useMapOverlays } from '../../../api/queries'; +import { useMapOverlays } from '../../../api/queries'; +import { useMapOverlayReport } from '../utils'; import { MapOverlayList } from './MapOverlayList'; import { MapOverlaySelectorTitle } from './MapOverlaySelectorTitleSection'; -import { useDateRanges } from '../../../utils'; const MaxHeightContainer = styled.div` max-height: 100%; @@ -134,15 +134,8 @@ export const DesktopMapOverlaySelector = ({ toggleOverlayLibrary, }: DesktopMapOverlaySelectorProps) => { const { projectCode, entityCode } = useParams(); - const { hasMapOverlays, selectedOverlay } = useMapOverlays(projectCode, entityCode); - const { startDate, endDate } = useDateRanges( - URL_SEARCH_PARAMS.MAP_OVERLAY_PERIOD, - selectedOverlay, - ); - const { data: mapOverlayData } = useMapOverlayReport(projectCode, entityCode, selectedOverlay, { - startDate, - endDate, - }); + const { hasMapOverlays } = useMapOverlays(projectCode, entityCode); + const { data: mapOverlayData } = useMapOverlayReport(); return ( diff --git a/packages/tupaia-web/src/features/Map/MapOverlaySelector/MapOverlayDatePicker.tsx b/packages/tupaia-web/src/features/Map/MapOverlaySelector/MapOverlayDatePicker.tsx index 3c8e676af6..8fb29e05d0 100644 --- a/packages/tupaia-web/src/features/Map/MapOverlaySelector/MapOverlayDatePicker.tsx +++ b/packages/tupaia-web/src/features/Map/MapOverlaySelector/MapOverlayDatePicker.tsx @@ -5,7 +5,8 @@ import React from 'react'; import { useParams } from 'react-router-dom'; import { Skeleton } from '@material-ui/lab'; -import { useMapOverlayReport, useMapOverlays } from '../../../api/queries'; +import { useMapOverlays } from '../../../api/queries'; +import { useMapOverlayReport } from '../utils'; import { DateRangePicker } from '../../../components'; import { useDateRanges } from '../../../utils'; import { URL_SEARCH_PARAMS } from '../../../constants'; @@ -23,15 +24,7 @@ export const MapOverlayDatePicker = () => { periodGranularity, } = useDateRanges(URL_SEARCH_PARAMS.MAP_OVERLAY_PERIOD, selectedOverlay); - const { isLoading: isLoadingMapOverlayData } = useMapOverlayReport( - projectCode, - entityCode, - selectedOverlay, - { - startDate, - endDate, - }, - ); + const { isLoading: isLoadingMapOverlayData } = useMapOverlayReport(); if (!showDatePicker) return null; return ( diff --git a/packages/tupaia-web/src/features/Map/MarkerLayer/MarkerLayer.tsx b/packages/tupaia-web/src/features/Map/MarkerLayer/MarkerLayer.tsx index d10664a9e0..f4d3c1a2cb 100644 --- a/packages/tupaia-web/src/features/Map/MarkerLayer/MarkerLayer.tsx +++ b/packages/tupaia-web/src/features/Map/MarkerLayer/MarkerLayer.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { useParams } from 'react-router'; import camelCase from 'camelcase'; import { useLocation, useNavigate } from 'react-router-dom'; -import { MarkerLayer as UIMarkerLayer, MeasureData } from '@tupaia/ui-map-components'; +import { LegendProps, MarkerLayer as UIMarkerLayer, MeasureData } from '@tupaia/ui-map-components'; import { useEntitiesWithLocation, useEntity, - useMapOverlayReport, useMapOverlays, useProject, } from '../../../api/queries'; +import { useMapOverlayReport } from '../utils'; import { EntityCode } from '../../../types'; import { processMeasureData } from './processMeasureData'; @@ -61,12 +61,12 @@ const useEntitiesByMeasureLevel = (measureLevel?: string) => { ); }; -export const MarkerLayer = () => { +export const MarkerLayer = ({ hiddenValues }: { hiddenValues: LegendProps['hiddenValues'] }) => { const navigateToDashboard = useNavigateToDashboard(); const { projectCode, entityCode } = useParams(); const { selectedOverlay } = useMapOverlays(projectCode, entityCode); const { data: entitiesData } = useEntitiesByMeasureLevel(selectedOverlay?.measureLevel); - const { data: mapOverlayData } = useMapOverlayReport(projectCode, entityCode, selectedOverlay); + const { data: mapOverlayData } = useMapOverlayReport(); const { data: entity } = useEntity(entityCode); if (!entitiesData || !mapOverlayData || !entity) { @@ -84,8 +84,7 @@ export const MarkerLayer = () => { entitiesData, measureData: mapOverlayData.measureData, serieses: mapOverlayData.serieses, - // Implement this when we add the legend - hiddenValues: {}, + hiddenValues, }); return ( diff --git a/packages/tupaia-web/src/features/Map/utils/index.ts b/packages/tupaia-web/src/features/Map/utils/index.ts new file mode 100644 index 0000000000..5a0f1f6989 --- /dev/null +++ b/packages/tupaia-web/src/features/Map/utils/index.ts @@ -0,0 +1,8 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +export { useDefaultMapOverlay } from './useDefaultMapOverlay'; +export { useHiddenMapValues } from './useHiddenMapValues'; +export { useMapOverlayReport } from './useMapOverlayReport'; diff --git a/packages/tupaia-web/src/features/Map/useDefaultMapOverlay.ts b/packages/tupaia-web/src/features/Map/utils/useDefaultMapOverlay.ts similarity index 93% rename from packages/tupaia-web/src/features/Map/useDefaultMapOverlay.ts rename to packages/tupaia-web/src/features/Map/utils/useDefaultMapOverlay.ts index d555d6609d..bee0c3077f 100644 --- a/packages/tupaia-web/src/features/Map/useDefaultMapOverlay.ts +++ b/packages/tupaia-web/src/features/Map/utils/useDefaultMapOverlay.ts @@ -5,13 +5,13 @@ import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { useProject } from '../../api/queries'; +import { useProject } from '../../../api/queries'; import { DEFAULT_MAP_OVERLAY_ID, DEFAULT_PERIOD_PARAM_STRING, URL_SEARCH_PARAMS, -} from '../../constants'; -import { MapOverlayGroup, ProjectCode, EntityCode } from '../../types'; +} from '../../../constants'; +import { MapOverlayGroup, ProjectCode, EntityCode } from '../../../types'; // When the map overlay groups change, update the default map overlay export const useDefaultMapOverlay = ( diff --git a/packages/tupaia-web/src/features/Map/utils/useHiddenMapValues.ts b/packages/tupaia-web/src/features/Map/utils/useHiddenMapValues.ts new file mode 100644 index 0000000000..5900eb15d0 --- /dev/null +++ b/packages/tupaia-web/src/features/Map/utils/useHiddenMapValues.ts @@ -0,0 +1,29 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ +import { useState } from 'react'; +import { LegendProps, Series, Value } from '@tupaia/ui-map-components'; + +export const useHiddenMapValues = (serieses: Series[] = []) => { + const [hiddenValues, setHiddenValues] = useState({}); + const [prevSerieses, setPrevSerieses] = useState(serieses); + + // reset hidden values when changing overlay or entity + if (JSON.stringify(serieses) !== JSON.stringify(prevSerieses)) { + setPrevSerieses(serieses); + const hiddenByDefault = serieses.reduce((values, { hideByDefault, key }) => { + return { ...values, [key]: hideByDefault }; + }, {}); + setHiddenValues(hiddenByDefault); + } + + const setValueHidden = (key: string, value: Value, hidden: boolean) => { + setHiddenValues((currentState: LegendProps['hiddenValues']) => ({ + ...currentState, + [key]: { ...currentState[key], [value!]: hidden }, + })); + }; + + return { setValueHidden, hiddenValues }; +}; diff --git a/packages/tupaia-web/src/features/Map/utils/useMapOverlayReport.ts b/packages/tupaia-web/src/features/Map/utils/useMapOverlayReport.ts new file mode 100644 index 0000000000..cf1b7671d3 --- /dev/null +++ b/packages/tupaia-web/src/features/Map/utils/useMapOverlayReport.ts @@ -0,0 +1,25 @@ +/* + * Tupaia + * Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd + */ + +import { useParams } from 'react-router'; +import { + useMapOverlays, + useMapOverlayReport as useMapOverlayReportQuery, +} from '../../../api/queries'; +import { useDateRanges } from '../../../utils'; +import { URL_SEARCH_PARAMS } from '../../../constants'; + +export const useMapOverlayReport = () => { + const { projectCode, entityCode } = useParams(); + const { selectedOverlay } = useMapOverlays(projectCode, entityCode); + const { startDate, endDate } = useDateRanges( + URL_SEARCH_PARAMS.MAP_OVERLAY_PERIOD, + selectedOverlay, + ); + return useMapOverlayReportQuery(projectCode, entityCode, selectedOverlay, { + startDate, + endDate, + }); +}; diff --git a/packages/ui-map-components/src/components/Legend/Legend.tsx b/packages/ui-map-components/src/components/Legend/Legend.tsx index 53d1acc678..bff18eec76 100644 --- a/packages/ui-map-components/src/components/Legend/Legend.tsx +++ b/packages/ui-map-components/src/components/Legend/Legend.tsx @@ -140,11 +140,10 @@ export const Legend = React.memo( const { type } = series; const LegendComponent = getLegendComponent(type as MeasureType); return ( - <> - + + {legendsHaveSameType && {`${series.name}: `}} {index < serieses.length - 1 && SeriesDivider && } - + ); }); })} diff --git a/packages/ui-map-components/src/components/Legend/LegendEntry.tsx b/packages/ui-map-components/src/components/Legend/LegendEntry.tsx index 9212c56788..90edc73925 100644 --- a/packages/ui-map-components/src/components/Legend/LegendEntry.tsx +++ b/packages/ui-map-components/src/components/Legend/LegendEntry.tsx @@ -60,7 +60,7 @@ export const LegendEntry = React.memo( const handleClick = () => { if (!unClickable && onClick) { - onClick(dataKey, value, !hidden); + onClick(dataKey!, value, !hidden); } }; diff --git a/packages/ui-map-components/src/types/legend.ts b/packages/ui-map-components/src/types/legend.ts index f8f8528ac1..7825956c6f 100644 --- a/packages/ui-map-components/src/types/legend.ts +++ b/packages/ui-map-components/src/types/legend.ts @@ -6,7 +6,7 @@ import { MarkerSeries, SpectrumSeries, Value } from './series'; export type LegendProps = { - setValueHidden: (dataKey?: string, value?: Value, hidden?: boolean) => void; + setValueHidden: (dataKey: string, value: Value, hidden: boolean) => void; hiddenValues: Record>; };