diff --git a/frontend/src/components/MapView/DateSelector/index.tsx b/frontend/src/components/MapView/DateSelector/index.tsx index d4d1d3779..a161c1ad1 100644 --- a/frontend/src/components/MapView/DateSelector/index.tsx +++ b/frontend/src/components/MapView/DateSelector/index.tsx @@ -34,7 +34,9 @@ import { format } from 'date-fns'; import { leftPanelTabValueSelector } from 'context/leftPanelStateSlice'; import { updateDateRange } from 'context/mapStateSlice'; import { getRequestDate } from 'utils/server-utils'; -import { AAAvailableDatesSelector } from 'context/anticipatoryAction/AADroughtStateSlice'; +import { isAnticipatoryActionLayer, isWindowedDates } from 'config/utils'; +import { getAAConfig } from 'context/anticipatoryAction/config'; +import { RootState } from 'context/store'; import TickSvg from './tick.svg'; import DateSelectorInput from './DateSelectorInput'; import TimelineItems from './TimelineItems'; @@ -59,7 +61,9 @@ const calculateStartAndEndDates = (startDate: Date, selectedTab: string) => { (selectedTab === Panel.AnticipatoryActionDrought && startDate.getMonth() < 3 ? 1 : 0); - const startMonth = selectedTab === Panel.AnticipatoryActionDrought ? 3 : 0; // April for anticipatory_action, January otherwise + + const startMonth = isAnticipatoryActionLayer(selectedTab) ? 3 : 0; // April for anticipatory_action, January otherwise + const start = new Date(year, startMonth, 1); const end = new Date(year, startMonth + 11, 31); @@ -119,32 +123,56 @@ const DateSelector = memo(() => { [availableDates], ); - const AAAvailableDates = useSelector(AAAvailableDatesSelector); + const AAConfig = useMemo(() => { + const anticipatoryLayer = selectedLayers.find(layer => + isAnticipatoryActionLayer(layer.type), + ); + if (anticipatoryLayer) { + return getAAConfig(anticipatoryLayer.type as AnticipatoryAction); + } + return null; + }, [selectedLayers]); - // Create a temporary layer for each AA window - const AALayers: DateCompatibleLayerWithDateItems[] = useMemo( - () => - AAAvailableDates - ? [ - { - id: 'anticipatory_action_window_1', - title: 'Window 1', - dateItems: AAAvailableDates['Window 1'], - type: AnticipatoryAction.drought, - opacity: 1, - }, - { - id: 'anticipatory_action_window_2', - title: 'Window 2', - dateItems: AAAvailableDates['Window 2'], - type: AnticipatoryAction.drought, - opacity: 1, - }, - ] - : [], - [AAAvailableDates], + const AAAvailableDates = useSelector((state: RootState) => + AAConfig ? AAConfig.availableDatesSelector(state) : null, ); + // Create a temporary layer for each AA window + const AALayers: DateCompatibleLayerWithDateItems[] = useMemo(() => { + if (!AAAvailableDates) { + return []; + } + + if (isWindowedDates(AAAvailableDates)) { + return [ + { + id: 'anticipatory_action_window_1', + title: 'Window 1', + dateItems: AAAvailableDates['Window 1'], + type: AnticipatoryAction.drought, + opacity: 1, + }, + { + id: 'anticipatory_action_window_2', + title: 'Window 2', + dateItems: AAAvailableDates['Window 2'], + type: AnticipatoryAction.drought, + opacity: 1, + }, + ]; + } + + return [ + { + id: 'anticipatory_action_storm', + title: 'Anticipatory Action Storm', + dateItems: AAAvailableDates, + type: AnticipatoryAction.storm, + opacity: 1, + }, + ]; + }, [AAAvailableDates]); + // Replace anticipatory action unique layer by window1 and window2 layers // Keep anticipatory actions at the top of the timeline const orderedLayers: DateCompatibleLayerWithDateItems[] = useMemo( @@ -162,7 +190,12 @@ const DateSelector = memo(() => { } return 0; }) - .map(l => (l.id === 'anticipatory_action_drought' ? AALayers : l)) + .map(layer => { + if (isAnticipatoryActionLayer(layer.type)) { + return AALayers.filter(al => al.type === layer.type); + } + return layer; + }) .flat(), [selectedLayers, AALayers], ); @@ -337,12 +370,17 @@ const DateSelector = memo(() => { ); // All dates in AA windows should be selectable, regardless of overlap - if (panelTab === Panel.AnticipatoryActionDrought && AAAvailableDates) { - // eslint-disable-next-line fp/no-mutating-methods - dates.push( - AAAvailableDates?.['Window 1']?.map(d => d.displayDate) ?? [], - AAAvailableDates?.['Window 2']?.map(d => d.displayDate) ?? [], - ); + if (isAnticipatoryActionLayer(panelTab) && AAAvailableDates) { + if (isWindowedDates(AAAvailableDates)) { + // eslint-disable-next-line fp/no-mutating-methods + dates.push( + AAAvailableDates?.['Window 1']?.map(d => d.displayDate) ?? [], + AAAvailableDates?.['Window 2']?.map(d => d.displayDate) ?? [], + ); + } else { + // eslint-disable-next-line fp/no-mutating-methods + dates.push(AAAvailableDates?.map(d => d.displayDate) ?? []); + } // eslint-disable-next-line fp/no-mutating-methods return dates diff --git a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx index de8754c3c..ea0fb7807 100644 --- a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx +++ b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx @@ -60,7 +60,7 @@ const AnticipatoryActionStormLayer = React.memo( useDefaultDate(layer.id); const map = useSelector(mapSelector); const { viewType, selectedDate } = useSelector(AAFiltersSelector); - const AAStormData = useSelector(AADataSelector); + const stormData = useSelector(AADataSelector); const boundaryLayerState = useSelector( layerDataSelector(boundaryLayer.id), ) as LayerData | undefined; @@ -117,8 +117,8 @@ const AnticipatoryActionStormLayer = React.memo( } const timeSeries: any = - AAStormData && AAStormData.timeSeries - ? enhanceTimeSeries(AAStormData.timeSeries as unknown as TimeSeries) + stormData && stormData.timeSeries + ? enhanceTimeSeries(stormData.timeSeries as unknown as TimeSeries) : null; function getIconNameByWindType(windType: string) { @@ -249,7 +249,7 @@ const AnticipatoryActionStormLayer = React.memo( .map(feature => { const districtName = feature.properties?.[boundaryLayer.adminLevelLocalNames[1]]; - const colorInfo = getDistrictColor(districtName, AAStormData); + const colorInfo = getDistrictColor(districtName, stormData); if (!colorInfo) { return null; @@ -266,9 +266,9 @@ const AnticipatoryActionStormLayer = React.memo( }) .filter(Boolean), }; - }, [boundaryData, AAStormData]); + }, [boundaryData, stormData]); - if (!boundaryData || !AAStormData) { + if (!boundaryData || !stormData) { return null; } @@ -305,7 +305,7 @@ const AnticipatoryActionStormLayer = React.memo( {viewType === 'forecast' && ( <> - {selectedFeature && AAStormData.landfall && selectedDate && ( + {selectedFeature && stormData.landfall && selectedDate && ( landfallPopupCloseHandler()} timelineDate={selectedDate} /> diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx index f548c1b33..d030be0b2 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx @@ -21,8 +21,6 @@ import { } from 'context/anticipatoryAction/AADroughtStateSlice/types'; import { AAWindowKeys } from 'config/utils'; import { - AAAvailableDatesSelector, - AADataSelector, AAFiltersSelector, AAMonitoredDistrictsSelector, AASelectedDistrictSelector, @@ -32,13 +30,8 @@ import { setAAView, } from 'context/anticipatoryAction/AADroughtStateSlice'; import { dateRangeSelector } from 'context/mapStateSlice/selectors'; -import { - getAAAvailableDatesCombined, - getRequestDate, -} from 'utils/server-utils'; import { getFormattedDate } from 'utils/date-utils'; -import { DateFormat } from 'utils/name-utils'; -import { PanelSize } from 'config/types'; +import { AnticipatoryAction, PanelSize } from 'config/types'; import { StyledCheckboxLabel, StyledRadioLabel } from './utils'; import { StyledSelect } from '../utils'; import DistrictView from './DistrictView/index'; @@ -46,6 +39,7 @@ import HomeTable from './HomeTable'; import HowToReadModal from '../HowToReadModal'; import Timeline from './Timeline'; import Forecast from './Forecast'; +import { useAnticipatoryAction } from '../useAnticipatoryAction'; const isZimbabwe = safeCountry === 'zimbabwe'; @@ -67,13 +61,12 @@ function AnticipatoryActionDroughtPanel() { const classes = useStyles(); const dispatch = useDispatch(); const { t } = useSafeTranslation(); + const { AAData } = useAnticipatoryAction(AnticipatoryAction.drought); const monitoredDistricts = useSelector(AAMonitoredDistrictsSelector); - const AAAvailableDates = useSelector(AAAvailableDatesSelector); const selectedDistrict = useSelector(AASelectedDistrictSelector); const { categories: categoryFilters, selectedIndex } = useSelector(AAFiltersSelector); const { startDate: selectedDate } = useSelector(dateRangeSelector); - const aaData = useSelector(AADataSelector); const view = useSelector(AAViewSelector); const [indexOptions, setIndexOptions] = React.useState([]); const [howToReadModalOpen, setHowToReadModalOpen] = React.useState(false); @@ -89,25 +82,14 @@ function AnticipatoryActionDroughtPanel() { if (!selectedDistrict) { return; } - const entries = Object.values(aaData) + const entries = Object.values(AAData) .map(x => x[selectedDistrict]) .flat() .filter(x => x); const options = [...new Set(entries.map(x => x.index))]; setIndexOptions(options); - }, [aaData, selectedDistrict]); - - const layerAvailableDates = - AAAvailableDates !== undefined - ? getAAAvailableDatesCombined(AAAvailableDates) - : []; - const queryDate = getRequestDate(layerAvailableDates, selectedDate); - const date = getFormattedDate(queryDate, DateFormat.Default) as string; - - React.useEffect(() => { - dispatch(setAAFilters({ selectedDate: date })); - }, [date, dispatch]); + }, [AAData, selectedDistrict]); return (
( 'forecast', @@ -49,15 +46,10 @@ function AnticipatoryActionStormPanel() { dispatch( setAAFilters({ viewType, - selectedDate: getFormattedDate(selectedDate, 'default'), }), ); - dispatch(updateDateRange({ startDate: selectedDate })); - }, [viewType, selectedDate, dispatch]); - - if (!selectedDate) { - return null; - } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [viewType]); return (
= + T extends AnticipatoryAction.storm + ? AAStormData + : Record<'Window 1' | 'Window 2', AnticipatoryActionData>; + +type AAAvailableDatesByAction = + T extends AnticipatoryAction.storm + ? DateItem[] + : Record<'Window 1' | 'Window 2', DateItem[]>; + +export function useAnticipatoryAction( + actionType: T, +): { + AAData: AADataByAction; + AAConfig: any; + AAAvailableDates: AAAvailableDatesByAction; +} { + const dispatch = useDispatch(); + const selectedLayers = useSelector(layersSelector); + const map = useSelector(mapSelector); + const { updateHistory, appendLayerToUrl, removeLayerFromUrl } = + useUrlHistory(); + + const AALayerInUrl = selectedLayers.find(x => + AALayerIds.includes(x.id as AnticipatoryAction), + ); + const AAConfig = getAAConfig(actionType); + const AAData = useSelector((state: RootState) => + AAConfig.dataSelector(state), + ); + const AAAvailableDates = useSelector((state: RootState) => + AAConfig.availableDatesSelector(state), + ); + const loadAAData = AAConfig.loadAction; + const setFilters = AAConfig.setFiltersAction; + const serverAvailableDates = useSelector(availableDatesSelector); + const { startDate: selectedDate } = useSelector(dateRangeSelector); + + // Load data when component mounts + useEffect(() => { + dispatch(loadAAData()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (AAAvailableDates) { + const combinedAvailableDates = isWindowedDates(AAAvailableDates) + ? getAAAvailableDatesCombined(AAAvailableDates) + : AAAvailableDates; + + if (!selectedDate) { + const updatedCapabilities = AALayerIds.reduce( + (acc, layerId) => ({ + ...acc, + [layerId]: combinedAvailableDates, + }), + { ...serverAvailableDates }, + ); + + dispatch(updateLayersCapabilities(updatedCapabilities)); + dispatch(updateDateRange(updatedCapabilities)); + } else { + const queryDate = getRequestDate(combinedAvailableDates, selectedDate); + const date = getFormattedDate(queryDate, DateFormat.Default) as string; + dispatch(setFilters({ selectedDate: date })); + } + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [AAAvailableDates, selectedDate]); + + // Handle URL updates when mounting/unmounting + useEffect(() => { + const layer = LayerDefinitions[actionType]; + + if (AALayerInUrl?.id !== layer.id) { + if (AALayerInUrl) { + toggleRemoveLayer( + AALayerInUrl, + map, + getUrlKey(AALayerInUrl), + dispatch, + removeLayerFromUrl, + ); + } + const updatedUrl = appendLayerToUrl( + getUrlKey(layer), + selectedLayers, + layer, + ); + updateHistory(getUrlKey(layer), updatedUrl); + dispatch(updateDateRange({ startDate: undefined })); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return { + AAData: AAData as AADataByAction, + AAConfig, + AAAvailableDates: AAAvailableDates as AAAvailableDatesByAction, + }; +} diff --git a/frontend/src/components/MapView/LeftPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/index.tsx index f4690e765..00ad1ed55 100644 --- a/frontend/src/components/MapView/LeftPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/index.tsx @@ -5,30 +5,15 @@ import { leftPanelTabValueSelector, setTabValue, } from 'context/leftPanelStateSlice'; -import { - AnticipatoryAction, - AnticipatoryActionLayerProps, - Panel, -} from 'config/types'; +import { AnticipatoryAction, Panel } from 'config/types'; import { AALayerIds, - LayerDefinitions, areChartLayersAvailable, isAnticipatoryActionLayer, - isWindowEmpty, - isWindowedDates, } from 'config/utils'; -import { getUrlKey, useUrlHistory } from 'utils/url-utils'; -import { layersSelector, mapSelector } from 'context/mapStateSlice/selectors'; import { setSelectedBoundaries } from 'context/mapSelectionLayerStateSlice'; -import { - availableDatesSelector, - updateLayersCapabilities, -} from 'context/serverStateSlice'; -import { getAAAvailableDatesCombined } from 'utils/server-utils'; -import { updateDateRange } from 'context/mapStateSlice'; -import { getAAConfig } from 'context/anticipatoryAction/config'; -import type { RootState } from 'context/store'; +import { layersSelector, mapSelector } from 'context/mapStateSlice/selectors'; +import { getUrlKey, useUrlHistory } from 'utils/url-utils'; import AnalysisPanel from './AnalysisPanel'; import ChartsPanel from './ChartsPanel'; import TablesPanel from './TablesPanel'; @@ -42,8 +27,8 @@ import { isAnticipatoryActionDroughtAvailable, isAnticipatoryActionStormAvailable, } from './utils'; -import { toggleRemoveLayer } from './layersPanel/MenuItem/MenuSwitch/SwitchItem/utils'; import AlertsPanel from './AlertsPanel'; +import { toggleRemoveLayer } from './layersPanel/MenuItem/MenuSwitch/SwitchItem/utils'; interface TabPanelProps { children?: React.ReactNode; @@ -72,144 +57,17 @@ const TabPanel = memo(({ children, value, index, ...other }: TabPanelProps) => ( const LeftPanel = memo(() => { const dispatch = useDispatch(); const tabValue = useSelector(leftPanelTabValueSelector); - const selectedLayers = useSelector(layersSelector); const map = useSelector(mapSelector); - const { updateHistory, appendLayerToUrl, removeLayerFromUrl } = - useUrlHistory(); - - const classes = useStyles({ tabValue }); - - const isPanelHidden = tabValue === Panel.None; + const { removeLayerFromUrl } = useUrlHistory(); - const AALayerInUrl = React.useMemo( - () => - selectedLayers.find(x => AALayerIds.includes(x.id as AnticipatoryAction)), - [selectedLayers], + const AALayerInUrl = selectedLayers.find(x => + AALayerIds.includes(x.id as AnticipatoryAction), ); - // Load AA config based on AA layer id - const AAConfig = React.useMemo(() => { - if (AALayerInUrl && isAnticipatoryActionLayer(AALayerInUrl.id)) { - return getAAConfig(AALayerInUrl.id as AnticipatoryAction); - } - return null; - }, [AALayerInUrl]); - - const AAData = useSelector((state: RootState) => - AAConfig ? AAConfig.dataSelector(state) : null, - ); - const AAAvailableDates = useSelector((state: RootState) => - AAConfig ? AAConfig.availableDatesSelector(state) : null, - ); - const loadAAData = AAConfig ? AAConfig.loadAction : null; - const serverAvailableDates = useSelector(availableDatesSelector); - - // Sync serverAvailableDates with AAAvailableDates when the latter updates. - React.useEffect(() => { - if (AAAvailableDates) { - const combinedAvailableDates = isWindowedDates(AAAvailableDates) - ? getAAAvailableDatesCombined(AAAvailableDates) - : AAAvailableDates; - const updatedCapabilities = AALayerIds.reduce( - (acc, layerId) => ({ - ...acc, - [layerId]: combinedAvailableDates, - }), - { ...serverAvailableDates }, - ); - dispatch(updateLayersCapabilities(updatedCapabilities)); - } - // To avoid an infinite loop, we only want to run this effect when AAAvailableDates changes. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [AAAvailableDates, dispatch]); - - // navigate to AA tab when app originally load with AA layer in url - React.useEffect(() => { - // TODO: update Object.keys(AAData).length === 0 condition with something more solid - // Move to AA tab when directly linked there - if ( - !isAnticipatoryActionLayer(tabValue) && - AALayerInUrl !== undefined && - AAData - ) { - if ( - AALayerInUrl.id === AnticipatoryAction.drought && - isWindowEmpty(AAData, 'Window 1') - ) { - dispatch(setTabValue(Panel.AnticipatoryActionDrought)); - } else if (AALayerInUrl.id === AnticipatoryAction.storm) { - dispatch(setTabValue(Panel.AnticipatoryActionStorm)); - } - } - }, [AAData, AALayerInUrl, dispatch, tabValue]); - - // Remove from url when leaving from AA tab - React.useEffect(() => { - if ( - !isAnticipatoryActionLayer(tabValue) && - tabValue !== Panel.None && - AALayerInUrl !== undefined && - !isWindowEmpty(AAData, 'Window 1') - ) { - toggleRemoveLayer( - AALayerInUrl, - map, - getUrlKey(AALayerInUrl), - dispatch, - removeLayerFromUrl, - ); - } - }, [AAData, AALayerInUrl, dispatch, map, removeLayerFromUrl, tabValue]); - - // fetch csv data when loading AA page - React.useEffect(() => { - if (!isAnticipatoryActionLayer(tabValue) || !loadAAData) { - return; - } - dispatch(loadAAData()); - }, [dispatch, tabValue, loadAAData]); - - // Add or switch AA layers in url - React.useEffect(() => { - if (!isAnticipatoryActionLayer(tabValue)) { - return; - } - const selectedLayerId = AALayerIds.find(x => x === tabValue); - if (!selectedLayerId) { - return; - } - - const layer = LayerDefinitions[ - selectedLayerId - ] as AnticipatoryActionLayerProps; - if (!layer || AALayerInUrl?.id === layer.id) { - return; - } - - if (AALayerInUrl) { - toggleRemoveLayer( - AALayerInUrl, - map, - getUrlKey(AALayerInUrl), - dispatch, - removeLayerFromUrl, - ); - } - - const updatedUrl = appendLayerToUrl( - getUrlKey(layer), - selectedLayers, - layer, - ); - updateHistory(getUrlKey(layer), updatedUrl); - - // Reset startDate - dispatch(updateDateRange({ startDate: undefined })); + const classes = useStyles({ tabValue }); - // url does not instantly update. updateHistory and appendLayerToUrl functions re-trigger useEffect, before selected layers is set - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedLayers, tabValue, dispatch, AALayerInUrl]); + const isPanelHidden = tabValue === Panel.None; const renderedChartsPanel = React.useMemo(() => { if (!areChartLayersAvailable) { @@ -242,6 +100,24 @@ const LeftPanel = memo(() => { [tabValue], ); + // Remove from url when leaving from AA tab + React.useEffect(() => { + if ( + !isAnticipatoryActionLayer(tabValue) && + tabValue !== Panel.None && + AALayerInUrl !== undefined + ) { + toggleRemoveLayer( + AALayerInUrl, + map, + getUrlKey(AALayerInUrl), + dispatch, + removeLayerFromUrl, + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tabValue]); + // Reset selected boundaries when tab changes from Alerts React.useEffect(() => { if (tabValue !== Panel.Alerts) { @@ -249,10 +125,25 @@ const LeftPanel = memo(() => { } }, [tabValue, dispatch]); + // Redirect to the correct Anticipatory Action tab when loading AA layer from url + React.useEffect(() => { + if (!isAnticipatoryActionLayer(tabValue) && AALayerInUrl) { + if (AALayerInUrl.id === AnticipatoryAction.drought) { + dispatch(setTabValue(Panel.AnticipatoryActionDrought)); + } else if (AALayerInUrl.id === AnticipatoryAction.storm) { + dispatch(setTabValue(Panel.AnticipatoryActionStorm)); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [AALayerInUrl]); + const renderedAnticipatoryActionPanel = React.useMemo(() => { + const shouldLoadAAPanel = tabValue && isAnticipatoryActionLayer(tabValue); + if ( isAnticipatoryActionDroughtAvailable && - tabValue === Panel.AnticipatoryActionDrought + tabValue === Panel.AnticipatoryActionDrought && + shouldLoadAAPanel ) { return ( @@ -262,7 +153,8 @@ const LeftPanel = memo(() => { } if ( isAnticipatoryActionStormAvailable && - tabValue === Panel.AnticipatoryActionStorm + tabValue === Panel.AnticipatoryActionStorm && + shouldLoadAAPanel ) { return ( diff --git a/frontend/src/context/anticipatoryAction/config.ts b/frontend/src/context/anticipatoryAction/config.ts index 60c4806d9..435f7ebd3 100644 --- a/frontend/src/context/anticipatoryAction/config.ts +++ b/frontend/src/context/anticipatoryAction/config.ts @@ -3,11 +3,13 @@ import { AAAvailableDatesSelector as droughtAvailableDatesSelector, AADataSelector as droughtDataSelector, loadAAData as loadDroughtAAData, + setAAFilters as setDroughtAAFilters, } from 'context/anticipatoryAction/AADroughtStateSlice'; import { AAAvailableDatesSelector as stormAvailableDatesSelector, AADataSelector as stormDataSelector, loadAAData as loadStormAAData, + setAAFilters as setStormAAFilters, } from 'context/anticipatoryAction/AAStormStateSlice'; export const anticipatoryActionConfig = { @@ -15,11 +17,13 @@ export const anticipatoryActionConfig = { dataSelector: stormDataSelector, availableDatesSelector: stormAvailableDatesSelector, loadAction: loadStormAAData, + setFiltersAction: setStormAAFilters, }, [AnticipatoryAction.drought]: { dataSelector: droughtDataSelector, availableDatesSelector: droughtAvailableDatesSelector, loadAction: loadDroughtAAData, + setFiltersAction: setDroughtAAFilters, }, }; diff --git a/frontend/src/utils/keep-layer-utils.ts b/frontend/src/utils/keep-layer-utils.ts index a11c30b0a..18924ff99 100644 --- a/frontend/src/utils/keep-layer-utils.ts +++ b/frontend/src/utils/keep-layer-utils.ts @@ -1,5 +1,6 @@ -import { AnticipatoryAction, LayerType, MenuGroup } from 'config/types'; +import { LayerType, MenuGroup } from 'config/types'; import { menuList } from 'components/MapView/LeftPanel/utils'; +import { isAnticipatoryActionLayer } from 'config/utils'; // Layer types that are allowed to have multiple layers overlap on the map. export const TYPES_ALLOWED_TO_OVERLAP = [ @@ -37,10 +38,7 @@ export function keepLayer(layer: LayerType, newLayer: LayerType) { return false; } - if ( - newLayer.type === layer.type && - layer.type === AnticipatoryAction.drought - ) { + if (newLayer.type === layer.type && isAnticipatoryActionLayer(layer.type)) { return true; } diff --git a/frontend/src/utils/layers-utils.tsx b/frontend/src/utils/layers-utils.tsx index 7178ca7cc..057a169a7 100644 --- a/frontend/src/utils/layers-utils.tsx +++ b/frontend/src/utils/layers-utils.tsx @@ -16,6 +16,7 @@ import { LayerDefinitions, getBoundaryLayerSingleton, isAnticipatoryActionLayer, + isWindowedDates, } from 'config/utils'; import { addLayer, @@ -41,9 +42,10 @@ import { } from 'utils/server-utils'; import { UrlLayerKey, getUrlKey, useUrlHistory } from 'utils/url-utils'; -import { AAAvailableDatesSelector } from 'context/anticipatoryAction/AADroughtStateSlice'; import { useTranslation } from 'react-i18next'; +import { getAAConfig } from 'context/anticipatoryAction/config'; +import { RootState } from 'context/store'; import { datesAreEqualWithoutTime, binaryIncludes, @@ -71,15 +73,32 @@ const useLayers = () => { const unsortedSelectedLayers = useSelector(layersSelector); const serverAvailableDates = useSelector(availableDatesSelector); - const AAAvailableDates = useSelector(AAAvailableDatesSelector); const { startDate: selectedDate } = useSelector(dateRangeSelector); - const AAAvailableDatesCombined = useMemo( - () => - AAAvailableDates ? getAAAvailableDatesCombined(AAAvailableDates) : [], - [AAAvailableDates], + // get AA config + const AAConfig = useMemo(() => { + const anticipatoryLayer = unsortedSelectedLayers.find(layer => + isAnticipatoryActionLayer(layer.type), + ); + if (anticipatoryLayer) { + return getAAConfig(anticipatoryLayer.type as AnticipatoryAction); + } + return null; + }, [unsortedSelectedLayers]); + + const AAAvailableDates = useSelector((state: RootState) => + AAConfig ? AAConfig.availableDatesSelector(state) : null, ); + const AAAvailableDatesCombined = useMemo(() => { + if (!AAAvailableDates) { + return []; + } + return isWindowedDates(AAAvailableDates) + ? getAAAvailableDatesCombined(AAAvailableDates) + : AAAvailableDates; + }, [AAAvailableDates]); + const hazardLayerIds = useMemo( () => urlParams.get(UrlLayerKey.HAZARD), [urlParams],