diff --git a/client/src/containers/analysis-visualization/analysis-legend/component.tsx b/client/src/containers/analysis-visualization/analysis-legend/component.tsx index fcebe5312..b8388b767 100644 --- a/client/src/containers/analysis-visualization/analysis-legend/component.tsx +++ b/client/src/containers/analysis-visualization/analysis-legend/component.tsx @@ -21,6 +21,7 @@ export const Legend: React.FC = () => { const dispatch = useAppDispatch(); const { data: upstreamLayers } = useContextualLayers(); + const { layers } = useAppSelector(analysisMap); useEffect(() => { diff --git a/client/src/containers/analysis-visualization/analysis-legend/contextual-legend-item/component.tsx b/client/src/containers/analysis-visualization/analysis-legend/contextual-legend-item/component.tsx index c0aae814d..cdc05b6ad 100644 --- a/client/src/containers/analysis-visualization/analysis-legend/contextual-legend-item/component.tsx +++ b/client/src/containers/analysis-visualization/analysis-legend/contextual-legend-item/component.tsx @@ -1,7 +1,7 @@ import { useCallback, useMemo } from 'react'; import { useAppDispatch } from 'store/hooks'; -import { setLayer } from 'store/features/analysis/map'; +import { setLayer, setLayerDeckGLProps } from 'store/features/analysis/map'; import LegendItem from 'components/legend/item'; import type { Layer } from 'types'; @@ -10,6 +10,7 @@ import LegendTypeCategorical from 'components/legend/types/categorical'; import LegendTypeChoropleth from 'components/legend/types/choropleth'; import LegendTypeGradient from 'components/legend/types/gradient'; import { useContextualLayer } from 'hooks/layers/contextual'; +import useContextualLayers from 'hooks/layers/getContextualLayers'; interface ContextualLegendItemProps { layer: Layer; @@ -18,11 +19,13 @@ interface ContextualLegendItemProps { const ContextualLegendItem: React.FC = ({ layer }) => { const dispatch = useAppDispatch(); - const { isLoading } = useContextualLayer(layer.id); + const { isFetching: areLayersLoading } = useContextualLayers(); + const { isFetching: isFetchingData } = useContextualLayer(layer.id); const handleActive = useCallback( - (active) => { + (active: boolean) => { dispatch(setLayer({ id: layer.id, layer: { active } })); + dispatch(setLayerDeckGLProps({ id: layer.id, props: { visible: active } })); }, [dispatch, layer], ); @@ -60,7 +63,7 @@ const ContextualLegendItem: React.FC = ({ layer }) => return ( { const contextualData = useAllContextualLayersData(); const layers = useMemo(() => { - const legends = Object.values(layerDeckGLProps).map((props) => { - const layerInfo = layersMetadata[props.id]; - - const data = layerInfo.isContextual ? contextualData.get(props.id)?.data : impactData; - - return new H3HexagonLayer({ - ...props, - data, - getHexagon: (d) => d.h, - getFillColor: (d) => d.c, - getLineColor: (d) => d.c, - onHover: ({ object, x, y, viewport }) => { - dispatch( - setTooltipPosition({ - x, - y, - viewport: viewport ? { width: viewport.width, height: viewport.height } : null, - }), - ); - dispatch( - setTooltipData({ - id: props.id, - name: layerInfo.metadata?.name || layerInfo.metadata?.legend.name, - value: object?.v, - unit: layerInfo.metadata?.legend.unit, - }), - ); - }, - }); - }); + const legends = Object.values(layerDeckGLProps) + .map((props) => { + const layerInfo = layersMetadata[props.id]; + if (!layerInfo) { + return null; + } + + const data = layerInfo.isContextual ? contextualData.get(props.id)?.data : impactData; + + return new H3HexagonLayer({ + ...props, + data, + getHexagon: (d) => d.h, + getFillColor: (d) => d.c, + getLineColor: (d) => d.c, + onHover: ({ object, x, y, viewport }) => { + dispatch( + setTooltipPosition({ + x, + y, + viewport: viewport ? { width: viewport.width, height: viewport.height } : null, + }), + ); + dispatch( + setTooltipData({ + id: props.id, + name: layerInfo.metadata?.name || layerInfo.metadata?.legend.name, + value: object?.v, + unit: layerInfo.metadata?.legend.unit, + }), + ); + }, + }); + }) + .filter((l) => !!l); return sortBy(legends, (l) => layersMetadata[l.id].order).reverse(); }, [contextualData, dispatch, impactData, layerDeckGLProps, layersMetadata]); diff --git a/client/src/hooks/h3-data/index.ts b/client/src/hooks/h3-data/index.ts index 252e862f2..a96032e33 100644 --- a/client/src/hooks/h3-data/index.ts +++ b/client/src/hooks/h3-data/index.ts @@ -184,11 +184,7 @@ const fetchContextualLayerData: QueryFunction< // Adding color to the response .then((response) => responseContextualParser(response)); -export const useH3ContextualData = ( - id: string, - // params: Partial, - options: Partial, -): H3ContextualResponse => { +export const useH3ContextualData = (id: string, options: Partial = {}) => { const filters = useAppSelector(analysisFilters); const { startYear, materials, indicator, suppliers, origins, locationTypes } = filters; const urlParams = { @@ -200,6 +196,7 @@ export const useH3ContextualData = ( ...(locationTypes?.length ? { locationTypes: locationTypes?.map(({ value }) => value) } : {}), resolution: origins?.length ? 6 : 4, }; + const query = useQuery(['h3-data-contextual', id, urlParams], fetchContextualLayerData, { ...DEFAULT_QUERY_OPTIONS, placeholderData: { @@ -213,7 +210,7 @@ export const useH3ContextualData = ( }, }, ...options, - enabled: options.enabled && !!id && !!urlParams.year, + enabled: (options.enabled ?? true) && !!id && !!urlParams.year, }); const { data, isError } = query; @@ -228,7 +225,7 @@ export const useH3ContextualData = ( ); }; -export const useAllContextualLayersData = (options?: Partial) => { +export const useAllContextualLayersData = (options: Partial = {}) => { const { layers } = useAppSelector(analysisMap); const filters = useAppSelector(analysisFilters); const { startYear, materials, indicator, suppliers, origins, locationTypes } = filters; @@ -250,11 +247,8 @@ export const useAllContextualLayersData = (options?: Partial) = queryFn: fetchContextualLayerData, ...DEFAULT_QUERY_OPTIONS, ...options, - enabled: - ('enabled' in (options || {}) ? options.enabled : true) && - layer.active && - !!layer.id && - !!urlParams.year, + keepPreviousData: true, + enabled: (options.enabled ?? true) && layer.active && !!layer.id && !!urlParams.year, select: (data: H3APIResponse) => ({ ...data, layerId: layer.id }), })), ); diff --git a/client/src/hooks/layers/contextual.ts b/client/src/hooks/layers/contextual.ts index b8d6f9e69..f19099afa 100644 --- a/client/src/hooks/layers/contextual.ts +++ b/client/src/hooks/layers/contextual.ts @@ -1,68 +1,30 @@ -import { useEffect } from 'react'; - import { useAppDispatch, useAppSelector } from 'store/hooks'; -import { analysisMap, setLayer, setLayerDeckGLProps } from 'store/features/analysis/map'; +import { analysisMap, setLayerDeckGLProps } from 'store/features/analysis/map'; import { useH3ContextualData } from 'hooks/h3-data'; import type { Layer } from 'types'; -export const useContextualLayer: (id: Layer['id']) => ReturnType = ( - id, -) => { +export const useContextualLayer = (id: Layer['id']) => { const dispatch = useAppDispatch(); const { layers: { [id]: layerInfo }, } = useAppSelector(analysisMap); - const query = useH3ContextualData(id, { enabled: layerInfo.active }); - const { data, isFetching, isFetched } = query; - - // Populating legend - useEffect(() => { - if (data && isFetching) { + const query = useH3ContextualData(id, { + enabled: layerInfo.active, + onSuccess: () => { dispatch( - setLayer({ - id, - layer: { - loading: isFetching, - }, - }), - ); - } - if (data && isFetched) { - dispatch( - setLayer({ - id, - layer: { - loading: isFetching, + setLayerDeckGLProps({ + id: layerInfo.id, + props: { + id: layerInfo.id, + opacity: layerInfo.opacity, + visible: layerInfo.active, }, }), ); - } - }, [data, isFetched, dispatch, isFetching, id]); - - useEffect(() => { - if (!isFetched) return; - dispatch( - setLayerDeckGLProps({ - id: layerInfo.id, - props: { - id: layerInfo.id, - opacity: layerInfo.opacity, - visible: layerInfo.active, - // data: data.data, - }, - }), - ); - }, [ - data.data, - dispatch, - isFetched, - layerInfo.active, - layerInfo.id, - layerInfo.metadata.name, - layerInfo.opacity, - ]); + }, + }); return query; }; diff --git a/client/src/hooks/layers/getContextualLayers.ts b/client/src/hooks/layers/getContextualLayers.ts index fad113edb..3d14292b3 100644 --- a/client/src/hooks/layers/getContextualLayers.ts +++ b/client/src/hooks/layers/getContextualLayers.ts @@ -1,6 +1,8 @@ import type { UseQueryResult } from 'react-query'; import { useQuery } from 'react-query'; import { apiRawService } from 'services/api'; +import { setLayer } from 'store/features/analysis/map'; +import { useAppDispatch } from 'store/hooks'; import type { LayerMetadata } from 'types'; interface ContextualLayerApiResponse { @@ -15,12 +17,23 @@ interface ApiResponse { } const useContextualLayers = () => { + const dispatch = useAppDispatch(); const query = useQuery, unknown, ContextualLayerApiResponse[]>( ['contextual-layers'], - async () => apiRawService.get('/contextual-layers/categories'), + () => apiRawService.get('/contextual-layers/categories'), { select: ({ data }) => data.data.flatMap((d) => d.layers).map((layer, i) => ({ ...layer, order: i + 1 })), + onSuccess: (data) => { + data.forEach((layer) => { + dispatch( + setLayer({ + id: layer.id, + layer: { ...layer, isContextual: true }, + }), + ); + }); + }, }, ); diff --git a/client/src/store/features/analysis/map.ts b/client/src/store/features/analysis/map.ts index 9b90421dd..1510f112d 100644 --- a/client/src/store/features/analysis/map.ts +++ b/client/src/store/features/analysis/map.ts @@ -108,40 +108,38 @@ export const analysisMapSlice = createSlice({ layer: Partial; }>, ) => { - // only one contextual layer active at the same time, set the rest as disabled - if ( - 'active' in action.payload.layer && - action.payload.layer.active && - (('isContextual' in action.payload.layer && action.payload.layer.isContextual) || - state.layers[action.payload.id]?.isContextual) - ) { - Object.keys(state.layers).forEach((layerId) => { - const layer = state.layers[layerId]; - if (layer.active && layerId !== action.payload.id && layer.isContextual) { - layer.active = false; - } - }); - } + const layers = { + ...state.layers, + [action.payload.id]: { + ...DEFAULT_LAYER_ATTRIBUTES, + ...state.layers[action.payload.id], + ...action.payload.layer, + }, + }; - state.layers[action.payload.id] = { - ...DEFAULT_LAYER_ATTRIBUTES, - ...state.layers[action.payload.id], - ...action.payload.layer, + return { + ...state, + layers, }; }, setLayerDeckGLProps: ( state, action: PayloadAction<{ id: Layer['id']; - props: DeckGLConstructorProps; + props: Partial; }>, ) => { - state.layerDeckGLProps[action.payload.id] = { - ...DEFAULT_DECKGL_PROPS, - ...state.layerDeckGLProps[action.payload.id], - ...action.payload.props, + return { + ...state, + layerDeckGLProps: { + ...state.layerDeckGLProps, + [action.payload.id]: { + ...DEFAULT_DECKGL_PROPS, + ...state.layerDeckGLProps[action.payload.id], + ...action.payload.props, + }, + }, }; - return state; }, setLayerOrder: (state, action: PayloadAction) => { Object.values(state.layers).forEach((layer) => {