Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Waitp 1224 overlay selector desktop #4685

Merged
merged 7 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/tupaia-web/src/api/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export { useEntities } from './useEntities';
export { useEmailVerification } from './useEmailVerification';
export { useDashboards } from './useDashboards';
export { useReport } from './useReport';
export { useFetchMapOverlays } from './useFetchMapOverlays';
export { useMapOverlayData } from './useMapOverlayData';
20 changes: 20 additions & 0 deletions packages/tupaia-web/src/api/queries/useFetchMapOverlays.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Tupaia
* Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd
*/

import { useQuery } from 'react-query';
import { get } from '../api';
import { EntityCode, ProjectCode } from '../../types';

export const useFetchMapOverlays = (projectCode?: ProjectCode, entityCode?: EntityCode) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure what to call this query hook, but I have another hook in utils called useMapOverlays which means I can't call this the same thing

Copy link
Contributor

@tcaiger tcaiger Jun 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to just move all the code from packages/tupaia-web/src/utils/useMapOverlays.ts into this file and name this one useMapOverlays since it is just derived api data with the exception of updateSelectedMapOverlay. You could then put updateSelectedMapOverlay inline in MapOverlayList, make it a stand alone util or keep it with useMapOverlays if you really want.

return useQuery(
['mapOverlays', projectCode, entityCode],
async () => {
return get(`mapOverlays/${projectCode}/${entityCode}`);
},
{
enabled: !!projectCode && !!entityCode,
},
);
};
28 changes: 28 additions & 0 deletions packages/tupaia-web/src/api/queries/useMapOverlayData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Tupaia
* Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd
*/

import { useQuery } from 'react-query';
import { get } from '../api';
import { EntityCode, ProjectCode, SingleMapOverlayItem } from '../../types';

export const useMapOverlayData = (
projectCode?: ProjectCode,
entityCode?: EntityCode,
mapOverlayCode?: SingleMapOverlayItem['code'],
) => {
return useQuery(
['mapOverlayData', projectCode, entityCode, mapOverlayCode],
async () => {
return get(
`measureData?mapOverlayCode=${mapOverlayCode}&organisationUnitCode=${entityCode}&projectCode=${projectCode}&shouldShowAllParentCountryResults=${
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be great to use the axios params field here for readability and ease of maintenance.
Also Ethan has re-done the endpoints for the map overlays so this should use legacyMapOverlayReport or report depending on whether or not it is legacy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that endpoint for fetching the measure data, or for something else?

Copy link
Contributor

@tcaiger tcaiger Jun 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that's right for measureData

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently getting an error using this, and it seems to be different to measureData which is for map overlays (is that right?) and reports which I was using for dashboard report data. I'll add this in as part of part 2

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah it looks like there is a bug in the endpoint so yes that's leave this as is for now

projectCode !== entityCode
}`, // TODO: figure out the logic here for shouldShowAllParentCountryResults
);
},
{
enabled: !!projectCode && !!entityCode && !!mapOverlayCode,
},
);
};
1 change: 1 addition & 0 deletions packages/tupaia-web/src/constants/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const URL_SEARCH_PARAMS = {
PROJECT: 'project',
TAB: 'tab',
PASSWORD_RESET_TOKEN: 'passwordResetToken',
MAP_OVERLAY: 'overlay',
};

export enum MODAL_ROUTES {
Expand Down
35 changes: 26 additions & 9 deletions packages/tupaia-web/src/features/Map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,24 @@ const StyledMap = styled(LeafletMap)`
`;
// Position this absolutely so it can be placed over the map
const TilePickerWrapper = styled.div`
position: absolute;
right: 0;
bottom: 0;
height: 100%;
@media screen and (max-width: ${MOBILE_BREAKPOINT}) {
display: none;
}
`;

// This contains the map controls (legend, overlay selector, etc, so that they can fit within the map appropriately)
const MapControlWrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
position: relative;
`;

const MapControlColumn = styled.div`
display: flex;
flex-direction: column;
flex: 1;
`;
const ENTITY_FIELDS = ['parent_code', 'code', 'name', 'type', 'bounds', 'region'];

export const Map = () => {
Expand All @@ -85,13 +94,21 @@ export const Map = () => {
<TileLayer tileSetUrl={activeTileSet.url} showAttribution={false} />
<PolygonLayer entity={entityData} />
<ZoomControl position="bottomright" />
<MapLegend />
<MapControlWrapper>
<MapControlColumn>
<MapOverlaySelector />
<MapLegend />
</MapControlColumn>
<TilePickerWrapper>
<TilePicker
tileSets={TILE_SETS}
activeTileSet={activeTileSet}
onChange={onTileSetChange}
/>
</TilePickerWrapper>
</MapControlWrapper>
<MapWatermark />
</StyledMap>
<MapOverlaySelector />
<TilePickerWrapper>
<TilePicker tileSets={TILE_SETS} activeTileSet={activeTileSet} onChange={onTileSetChange} />
</TilePickerWrapper>
</MapContainer>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,24 @@ const Wrapper = styled.div`
flex-direction: row;
align-items: flex-end;
justify-content: center;
position: absolute;
background-color: grey;
width: 300px;
height: 50px;
bottom: 1em;
left: 50%;
transform: translateX(-50%);
border-radius: 5px;
width: 100%;
padding: 1rem;
@media screen and (max-width: ${MOBILE_BREAKPOINT}) {
display: none;
}
`;

const Legend = styled.div`
height: 50px;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably should be rems, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should, but this is just a placeholder so I don't think it's important at this stage

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh true. I just see px and I jump 😆

width: 300px;
background-color: grey;
border-radius: 5px;
`;

export const DesktopMapLegend = () => {
return <Wrapper />;
return (
<Wrapper>
<Legend />
</Wrapper>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,209 @@

import React from 'react';
import styled from 'styled-components';
import { Accordion, Typography, AccordionSummary, AccordionDetails } from '@material-ui/core';
import { ExpandMore, Layers } from '@material-ui/icons';
import { Skeleton as MuiSkeleton } from '@material-ui/lab';
import { MOBILE_BREAKPOINT } from '../../../constants';
import { Entity } from '../../../types';
import { useMapOverlayData } from '../../../api/queries';
import { useParams } from 'react-router';
import { periodToMoment } from '@tupaia/utils';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just check order of imports here

import { useMapOverlays } from '../../../utils';
import { MapOverlayList } from './MapOverlayList';

// Placeholder for MapOverlaySelector component
const Wrapper = styled.div`
width: 18.75rem;
margin: 1em;
height: 2.5rem;
border-radius: 5px;
background-color: ${({ theme }) => theme.palette.secondary.main};
opacity: 0.6;
position: absolute;
top: 0;
const MaxHeightContainer = styled.div`
flex: 1;
max-height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
`;

const Wrapper = styled(MaxHeightContainer)`
max-width: 21.25rem;
margin: 0.625rem;
@media screen and (max-width: ${MOBILE_BREAKPOINT}) {
display: none;
}
`;

export const DesktopMapOverlaySelector = () => {
return <Wrapper />;
const Header = styled.div`
padding: 0.9rem 1rem;
background-color: ${({ theme }) => theme.palette.secondary.main};
border-radius: 5px 5px 0 0;
`;

const Heading = styled(Typography).attrs({
variant: 'h2',
})`
font-size: 0.75rem;
text-transform: uppercase;
font-weight: ${({ theme }) => theme.typography.fontWeightMedium};
`;

const Container = styled(MaxHeightContainer)`
border-radius: 0 0 5px 5px;
`;

const MapOverlayNameContainer = styled.div`
padding: 1.3rem 1rem 1rem 1.125rem;
background-color: ${({ theme }) => theme.overlaySelector.overlayNameBackground};
`;

const MapOverlayName = styled.span`
font-weight: ${({ theme }) => theme.typography.fontWeightMedium};
`;

const Skeleton = styled(MuiSkeleton)`
transform: scale(1, 1);
::after {
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
}
`;

const OverlayLibraryAccordion = styled(Accordion)`
display: flex;
flex-direction: column;
margin: 0 !important;
background-color: ${({ theme }) => theme.overlaySelector.menuBackground};
border-radius: 0 0 5px 5px;
&:before {
display: none;
}
&.MuiPaper-root.Mui-expanded {
height: 100%;
overflow: hidden; // make the accordion conform to the max-height of the parent container, regardless of how much content is present
> .MuiCollapse-container.MuiCollapse-entered {
max-height: 100%;
overflow-y: auto; // scrollable content when accordion is expanded;
}
}
`;

const OverlayLibraryIcon = styled(Layers)`
margin-right: 0.5rem;
.Mui-expanded & {
fill: ${({ theme }) => theme.palette.secondary.main};
}
`;

const OverlayLibraryTitle = styled(Typography)`
font-size: 0.75rem;
text-transform: uppercase;
font-weight: ${({ theme }) => theme.typography.fontWeightMedium};
`;

const OverlayLibraryHeader = styled(AccordionSummary)`
margin: 0 !important;
min-height: unset !important;
color: rgba(255, 255, 255, 0.6);
.MuiAccordionSummary-content {
margin: 0 !important;
align-items: center;
}

&:hover,
&.Mui-expanded,
&:focus-visible {
color: ${({ theme }) => theme.palette.text.primary};
}
`;

const OverlayLibraryContentWrapper = styled(AccordionDetails)`
padding: 0 1rem 1rem;
`;

const OverlayLibraryContentContainer = styled.div`
border-top: 1px solid rgba(255, 255, 255, 0.12);
width: 100%;
padding-top: 1rem;
`;

const LatestDataContainer = styled.div`
background-color: rgba(0, 0, 0, 0.3);
border-radius: 5px;
padding: 0.3rem 0.5rem 0.2rem;
margin-top: 0.5rem;
`;

const LatestDataText = styled(Typography)`
font-size: 0.875rem;
line-height: 1.3;
`;

interface DesktopMapOverlaySelectorProps {
entityName?: Entity['name'];
overlayLibraryOpen: boolean;
toggleOverlayLibrary: () => void;
}

export const DesktopMapOverlaySelector = ({
entityName,
overlayLibraryOpen,
toggleOverlayLibrary,
}: DesktopMapOverlaySelectorProps) => {
const {
hasMapOverlays,
isLoadingMapOverlays,
selectedOverlayCode,
selectedOverlay,
} = useMapOverlays();

const { projectCode, entityCode } = useParams();
const { data: mapOverlayData } = useMapOverlayData(projectCode, entityCode, selectedOverlayCode);

return (
<Wrapper>
<Header>
<Heading>Map Overlays</Heading>
</Header>
<Container>
<MapOverlayNameContainer>
{isLoadingMapOverlays ? (
<Skeleton animation="wave" width={200} height={20} />
) : (
<Typography>
{hasMapOverlays ? (
<MapOverlayName>{selectedOverlay?.name}</MapOverlayName>
) : (
`Select an area with valid data. ${
entityName && `${entityName} has no map overlays available.`
}`
)}
</Typography>
)}
</MapOverlayNameContainer>
{hasMapOverlays && (
<OverlayLibraryAccordion
expanded={overlayLibraryOpen}
onChange={toggleOverlayLibrary}
square
>
<OverlayLibraryHeader
expandIcon={<ExpandMore />}
aria-controls="overlay-library-content"
id="overlay-library-header"
>
<OverlayLibraryIcon />
<OverlayLibraryTitle>Overlay library</OverlayLibraryTitle>
</OverlayLibraryHeader>
<OverlayLibraryContentWrapper>
<OverlayLibraryContentContainer>
<MapOverlayList />
</OverlayLibraryContentContainer>
</OverlayLibraryContentWrapper>
</OverlayLibraryAccordion>
)}
{mapOverlayData?.period?.latestAvailable && (
<LatestDataContainer>
<LatestDataText>
Latest overlay data:{' '}
{periodToMoment(mapOverlayData?.period?.latestAvailable).format('DD/MM/YYYY')}
</LatestDataText>
</LatestDataContainer>
)}
</Container>
</Wrapper>
);
};
Loading