From 889f3fba8a482e47d995e225638fa8e1943e2cc2 Mon Sep 17 00:00:00 2001 From: donia benharara Date: Wed, 11 Dec 2024 17:44:20 +0100 Subject: [PATCH 01/30] centralize data fetch in state for AA storm --- .../PopupContent/index.tsx | 9 +-- .../AAStormLandfallPopup/index.tsx | 3 +- .../AnticipatoryActionStormLayer/index.tsx | 63 +++++++++---------- .../HomeTable/index.tsx | 4 +- .../ActivationTriggerView/index.tsx | 49 +++++++++++---- .../AnticipatoryActionStormPanel/utils.tsx | 27 ++++---- .../components/MapView/LeftPanel/index.tsx | 19 ++++-- .../src/components/MapView/LeftPanel/utils.ts | 5 +- frontend/src/components/NavBar/index.tsx | 33 ++++++---- frontend/src/config/mozambique/prism.json | 3 +- frontend/src/config/utils.ts | 6 +- frontend/src/config/zimbabwe/prism.json | 2 +- .../AADroughtStateSlice/index.ts | 2 +- .../AAStormStateSlice/index.ts | 8 ++- .../AAStormStateSlice/types.ts | 33 +++++----- .../AAStormStateSlice/utils.ts | 59 +++++++++-------- 16 files changed, 190 insertions(+), 135 deletions(-) diff --git a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.tsx b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.tsx index 0d731f319..52fda30b1 100644 --- a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.tsx +++ b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.tsx @@ -1,4 +1,5 @@ import { createStyles, makeStyles, Typography } from '@material-ui/core'; +import { LandfallInfo } from 'context/anticipatoryAction/AAStormStateSlice/types'; import { formatReportDate } from '../../utils'; function PopupContent({ landfallInfo, reportDate }: PopupContentProps) { @@ -45,7 +46,7 @@ function PopupContent({ landfallInfo, reportDate }: PopupContentProps) { variant="body1" className={`${classes.text} ${classes.textAlignRight}`} > - {landfallInfo.landfall_impact_district} + {landfallInfo.district} @@ -83,12 +84,6 @@ const useStyles = makeStyles(() => }), ); -export interface LandfallInfo { - landfall_time: string[]; - landfall_impact_district: string; - landfall_impact_intensity: string[]; -} - interface PopupContentProps { landfallInfo: LandfallInfo; reportDate: string; diff --git a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/index.tsx b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/index.tsx index e2a17af64..ffa9fe064 100644 --- a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/index.tsx +++ b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/index.tsx @@ -1,7 +1,8 @@ import { createStyles, makeStyles } from '@material-ui/core'; import { Point } from 'geojson'; import { Popup } from 'react-map-gl/maplibre'; -import PopupContent, { LandfallInfo } from './PopupContent'; +import { LandfallInfo } from 'context/anticipatoryAction/AAStormStateSlice/types'; +import PopupContent from './PopupContent'; function AAStormLandfallPopup({ point, diff --git a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx index 0c9ea2fcd..634d7c31a 100644 --- a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx +++ b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx @@ -12,7 +12,12 @@ import { useMapCallback } from 'utils/map-utils'; import { hidePopup } from 'context/tooltipStateSlice'; import { LayerData } from 'context/layers/layer-data'; import { getBoundaryLayersByAdminLevel } from 'config/utils'; -import { AAFiltersSelector } from 'context/anticipatoryAction/AAStormStateSlice'; +import { + AADataSelector, + AAFiltersSelector, +} from 'context/anticipatoryAction/AAStormStateSlice'; +import { AACategory } from 'context/anticipatoryAction/AAStormStateSlice/types'; +import { getAAColor } from 'components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/utils'; import AAStormDatePopup from './AAStormDatePopup'; import AAStormLandfallPopup from './AAStormLandfallPopup'; import moderateStorm from '../../../../../public/images/anticipatory-action-storm/moderate-tropical-storm.png'; @@ -35,6 +40,7 @@ const AnticipatoryActionStormLayer = React.memo( useDefaultDate(layer.id); const map = useSelector(mapSelector); const { viewType } = useSelector(AAFiltersSelector); + const AAStormData = useSelector(AADataSelector); const boundaryLayerState = useSelector( layerDataSelector(boundaryLayer.id), ) as LayerData | undefined; @@ -46,26 +52,6 @@ const AnticipatoryActionStormLayer = React.memo( /* this is the date the layer data corresponds to. It will be stored in redux ultimately */ // const layerDataDate = '2024-03-11'; - // Add state for storm data - const [AAStormData, setAAStormData] = useState(null); - - // Add fetch effect - useEffect(() => { - const fetchStormData = async () => { - try { - const response = await fetch( - 'https://data.earthobservation.vam.wfp.org/public-share/aa/ts/outputs/latest.json', - ); - const stormData = await response.json(); - setAAStormData(stormData); - } catch (error) { - console.error('Error fetching storm data:', error); - } - }; - - fetchStormData(); - }, []); - function enhanceTimeSeries(timeSeries: TimeSeries) { const { features, ...timeSeriesRest } = timeSeries; @@ -114,9 +100,10 @@ const AnticipatoryActionStormLayer = React.memo( } // Replace all AAStormData references with stormData - const timeSeries: any = AAStormData - ? enhanceTimeSeries(AAStormData.time_series as unknown as TimeSeries) - : null; + const timeSeries: any = + AAStormData && AAStormData.timeSeries + ? enhanceTimeSeries(AAStormData.timeSeries as unknown as TimeSeries) + : null; function getIconNameByWindType(windType: string) { if (windType === 'intense tropical cyclone') { @@ -277,14 +264,18 @@ const AnticipatoryActionStormLayer = React.memo( {/* 48kt wind forecast area - orange */} {viewType === 'forecast' && ( )} @@ -292,28 +283,36 @@ const AnticipatoryActionStormLayer = React.memo( {/* 64kt wind forecast area - red */} {viewType === 'forecast' && ( )} {/* Storm Risk Map view */} {viewType === 'risk' && ( )} @@ -352,11 +351,11 @@ const AnticipatoryActionStormLayer = React.memo( - {selectedFeature && ( + {selectedFeature && AAStormData.landfall && ( landfallPopupCloseHandler()} /> )} diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/HomeTable/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/HomeTable/index.tsx index d442cb72d..8bee0d009 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/HomeTable/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/HomeTable/index.tsx @@ -209,14 +209,14 @@ function HomeTable({ dialogs }: HomeTableProps) { const monitoredDistrict = useSelector(AAMonitoredDistrictsSelector); const { 'Window 2': window2Range } = useSelector(AAWindowRangesSelector); - const filename = appConfig.anticipatoryActionUrl?.split('/').at(-1); + const filename = appConfig.anticipatoryActionDroughtUrl?.split('/').at(-1); const homeButtons = [ { startIcon: , text: 'Assets', component: 'a', - href: `${appConfig.anticipatoryActionUrl}?date=${getCurrentDateTimeForUrl()}`, + href: `${appConfig.anticipatoryActionDroughtUrl}?date=${getCurrentDateTimeForUrl()}`, download: `${window2Range?.end}-${filename}`, }, { diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/ActivationTriggerView/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/ActivationTriggerView/index.tsx index cafe962ee..ff2b97286 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/ActivationTriggerView/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/ActivationTriggerView/index.tsx @@ -5,8 +5,6 @@ import { useSafeTranslation } from 'i18n'; import { AADisplayCategory, AACategory, - phaseValues, - AADisplayPhase, } from 'context/anticipatoryAction/AAStormStateSlice/types'; import { getAAColor } from '../utils'; import { useAACommonStyles } from '../../utils'; @@ -107,35 +105,60 @@ function ActivationTrigger({ dialogs }: ActivationTriggerProps) {
- {phaseValues.map(phase => - Object.entries(rawAAData.exposed || {}).map(([category, data]) => ( + {rawAAData.activeDistricts && + Object.entries(rawAAData.activeDistricts).map(([category, data]) => (
+ {/* Affichage pour les districts actifs */}
${AADisplayCategory[category as AACategory]}`, )} />
- {data[phase]?.districtNames.map((name: string) => ( + {data.districtNames.map((name: string) => (
))}
- )), - )} + ))} + {rawAAData.naDistricts && + Object.entries(rawAAData.naDistricts).map(([category, data]) => ( +
+ {/* Affichage pour les districts actifs */} +
+ ${AADisplayCategory[category as AACategory]}`, + )} + /> +
+
+ {data.districtNames.map((name: string) => ( +
+ +
+ ))} +
+
+ ))}
diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/utils.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/utils.tsx index a19394d09..16b9c6fb9 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/utils.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/utils.tsx @@ -4,16 +4,9 @@ import { AACategory, } from 'context/anticipatoryAction/AAStormStateSlice/types'; -const AACategoryPhaseMap: { [key in AACategory]: any } = { +const AACategoryPhaseMap: { [key in AACategory]?: any } = { [AACategory.Severe]: { - Set: { - color: { - background: '#E63701', - text: 'white', - }, - iconProps: { topText: 'S', bottomText: 'SEV', color: 'white' }, - }, - Ready: { + Active: { color: { background: '#E63701', text: 'white', @@ -29,15 +22,23 @@ const AACategoryPhaseMap: { [key in AACategory]: any } = { }, }, [AACategory.Moderate]: { - Set: { + Active: { color: { background: '#FF8934', text: 'black', }, - iconProps: { topText: 'S', bottomText: 'MOD', color: 'black' }, + iconProps: { topText: 'R', bottomText: 'MOD', color: 'black' }, }, - - Ready: { + na: { + color: { + background: 'grey', + text: 'white', + }, + iconProps: { topText: 'na', bottomText: 'MOD', color: 'black' }, + }, + }, + [AACategory.Risk]: { + Active: { color: { background: '#FF8934', text: 'black', diff --git a/frontend/src/components/MapView/LeftPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/index.tsx index ee35531a6..e9809d7dd 100644 --- a/frontend/src/components/MapView/LeftPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/index.tsx @@ -37,7 +37,11 @@ import { AnticipatoryActionStormPanel, } from './AnticipatoryActionPanel'; import LayersPanel from './layersPanel'; -import { areTablesAvailable, isAnticipatoryActionAvailable } from './utils'; +import { + areTablesAvailable, + isAnticipatoryActionDroughtAvailable, + isAnticipatoryActionStormAvailable, +} from './utils'; import { toggleRemoveLayer } from './layersPanel/MenuItem/MenuSwitch/SwitchItem/utils'; import AlertsPanel from './AlertsPanel'; @@ -243,17 +247,20 @@ const LeftPanel = memo(() => { }, [tabValue, dispatch]); const renderedAnticipatoryActionPanel = React.useMemo(() => { - if (!isAnticipatoryActionAvailable) { - return null; - } - if (tabValue === Panel.AnticipatoryActionDrought) { + if ( + isAnticipatoryActionDroughtAvailable && + tabValue === Panel.AnticipatoryActionDrought + ) { return ( ); } - if (tabValue === Panel.AnticipatoryActionStorm) { + if ( + isAnticipatoryActionStormAvailable && + tabValue === Panel.AnticipatoryActionStorm + ) { return ( diff --git a/frontend/src/components/MapView/LeftPanel/utils.ts b/frontend/src/components/MapView/LeftPanel/utils.ts index b1d817a38..d05a01422 100644 --- a/frontend/src/components/MapView/LeftPanel/utils.ts +++ b/frontend/src/components/MapView/LeftPanel/utils.ts @@ -117,7 +117,10 @@ export const tablesMenuItems = menuList.filter((menuItem: MenuItemType) => ); export const areTablesAvailable = tablesMenuItems.length >= 1; -export const isAnticipatoryActionAvailable = !!appConfig.anticipatoryActionUrl; +export const isAnticipatoryActionDroughtAvailable = + !!appConfig.anticipatoryActionDroughtUrl; +export const isAnticipatoryActionStormAvailable = + !!appConfig.anticipatoryActionStormUrl; export const oneDayInMs = 24 * 60 * 60 * 1000; export const oneYearInMs = 365 * oneDayInMs; diff --git a/frontend/src/components/NavBar/index.tsx b/frontend/src/components/NavBar/index.tsx index 87bc1add1..ef05485c9 100644 --- a/frontend/src/components/NavBar/index.tsx +++ b/frontend/src/components/NavBar/index.tsx @@ -34,7 +34,8 @@ import Legends from 'components/MapView/Legends'; import { areChartLayersAvailable } from 'config/utils'; import { areTablesAvailable, - isAnticipatoryActionAvailable, + isAnticipatoryActionDroughtAvailable, + isAnticipatoryActionStormAvailable, } from 'components/MapView/LeftPanel/utils'; import { Panel, PanelItem } from 'config/types'; import About from './About'; @@ -58,22 +59,30 @@ const panels: PanelItem[] = [ ...(areTablesAvailable ? [{ panel: Panel.Tables, label: 'Tables', icon: }] : []), - ...(isAnticipatoryActionAvailable + ...(isAnticipatoryActionDroughtAvailable || isAnticipatoryActionStormAvailable ? [ { label: 'A. Actions', icon: , children: [ - { - panel: Panel.AnticipatoryActionDrought, - label: 'A. Action Drought', - icon: , - }, - { - panel: Panel.AnticipatoryActionStorm, - label: 'A. Action Storm', - icon: , - }, + ...(isAnticipatoryActionDroughtAvailable + ? [ + { + panel: Panel.AnticipatoryActionDrought, + label: 'A. Action Drought', + icon: , + }, + ] + : []), + ...(isAnticipatoryActionStormAvailable + ? [ + { + panel: Panel.AnticipatoryActionStorm, + label: 'A. Action Storm', + icon: , + }, + ] + : []), ], }, ] diff --git a/frontend/src/config/mozambique/prism.json b/frontend/src/config/mozambique/prism.json index be6733fb7..8abcda2d2 100644 --- a/frontend/src/config/mozambique/prism.json +++ b/frontend/src/config/mozambique/prism.json @@ -1,7 +1,8 @@ { "country": "Mozambique", "defaultLanguage": "en", - "anticipatoryActionUrl": "https://data.earthobservation.vam.wfp.org/public-share/aa/aa_probabilities_triggers_moz.csv", + "anticipatoryActionDroughtUrl": "https://data.earthobservation.vam.wfp.org/public-share/aa/aa_probabilities_triggers_moz.csv", + "anticipatoryActionStormUrl": " https://data.earthobservation.vam.wfp.org/public-share/aa/ts/outputs/latest.json.", "header": { "title": "PRISM", "logo": "images/moz-flag-inam-logo.png" diff --git a/frontend/src/config/utils.ts b/frontend/src/config/utils.ts index d62461d31..7803d420c 100644 --- a/frontend/src/config/utils.ts +++ b/frontend/src/config/utils.ts @@ -168,7 +168,9 @@ export const AAWindowKeys = ['Window 1', 'Window 2'] as const; export const AALayerIds = Object.values(AnticipatoryAction); export const LayerDefinitions: LayersMap = (() => { - const aaUrl = appConfig.anticipatoryActionUrl; + const droughtUrl = appConfig.anticipatoryActionDroughtUrl; + const stormUrl = appConfig.anticipatoryActionStormUrl; + const AALayers: AnticipatoryActionLayerProps[] = [ { id: AnticipatoryAction.drought, @@ -194,7 +196,7 @@ export const LayerDefinitions: LayersMap = (() => { ...acc, [layerKey]: getLayerByKey(layerKey as LayerKey), }), - (aaUrl ? AALayersById : {}) as LayersMap, + (droughtUrl || stormUrl ? AALayersById : {}) as LayersMap, ); // Verify that the layers referenced by impact layers actually exist diff --git a/frontend/src/config/zimbabwe/prism.json b/frontend/src/config/zimbabwe/prism.json index 601311d3a..9a5178db2 100644 --- a/frontend/src/config/zimbabwe/prism.json +++ b/frontend/src/config/zimbabwe/prism.json @@ -3,7 +3,7 @@ "countryAdmin0Id": 271, "alertFormActive": false, "enableNavigationDropdown": true, - "anticipatoryActionUrl": "https://data.earthobservation.vam.wfp.org/public-share/aa/aa_probabilities_triggers_zwe.csv", + "anticipatoryActionDroughtUrl": "https://data.earthobservation.vam.wfp.org/public-share/aa/aa_probabilities_triggers_zwe.csv", "serversUrls": { "wms": ["https://api.earthobservation.vam.wfp.org/ows/wms"] }, diff --git a/frontend/src/context/anticipatoryAction/AADroughtStateSlice/index.ts b/frontend/src/context/anticipatoryAction/AADroughtStateSlice/index.ts index 3252b3d22..efc500aab 100644 --- a/frontend/src/context/anticipatoryAction/AADroughtStateSlice/index.ts +++ b/frontend/src/context/anticipatoryAction/AADroughtStateSlice/index.ts @@ -58,7 +58,7 @@ export const loadAAData = createAsyncThunk< undefined, CreateAsyncThunkTypes >('anticipatoryActionDroughtState/loadAAData', async () => { - const url = `${appConfig.anticipatoryActionUrl}?date=${getCurrentDateTimeForUrl()}`; + const url = `${appConfig.anticipatoryActionDroughtUrl}?date=${getCurrentDateTimeForUrl()}`; return new Promise((resolve, reject) => { Papa.parse(url, { diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts index f6f40c5fa..43636b3c7 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts @@ -8,7 +8,6 @@ import { StormData, } from './types'; import { parseAndTransformAA } from './utils'; -import anticipatoryActionStormData from '../../../../public/data/mozambique/anticipatory-action/aa_storm_temporary.json'; const initialState: AnticipatoryActionState = { data: {}, @@ -19,6 +18,7 @@ const initialState: AnticipatoryActionState = { categories: { Severe: true, Moderate: true, + Risk: true, }, }, loading: false, @@ -34,7 +34,11 @@ export const loadAAData = createAsyncThunk< CreateAsyncThunkTypes >('anticipatoryActionStormState/loadAAData', async (_, { rejectWithValue }) => { try { - const data = parseAndTransformAA(anticipatoryActionStormData as StormData); + const response = await fetch( + 'https://data.earthobservation.vam.wfp.org/public-share/aa/ts/outputs/latest.json', + ); + const stormData = await response.json(); + const data = parseAndTransformAA(stormData as StormData); return data; } catch (error) { return rejectWithValue(error); diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts index c6de7dece..f7388f585 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts @@ -4,11 +4,13 @@ import { DateItem } from 'config/types'; export enum AACategory { Severe = 'Severe', Moderate = 'Moderate', + Risk = 'Risk', } export enum AACategoryKey { Severe = 'exposed_area_64kt', Moderate = 'exposed_area_48kt', + Proba = 'proba_48kt_20_5d', } export enum AACategoryLandfall { @@ -16,20 +18,18 @@ export enum AACategoryLandfall { Moderate = 'moderate tropical storm', } +export const AAPanelCategories: AACategory[] = [ + AACategory.Severe, + AACategory.Moderate, +]; + export const AADisplayCategory: { - [key in AACategory]: string; + [key in AACategory]?: string; } = { [AACategory.Severe]: ' > 118 KM/H', [AACategory.Moderate]: ' > 89 KM/H', }; -export const AADisplayPhase: { - [key in AAPhaseType]: string; -} = { - Ready: 'Activation', - na: 'NA', -}; - export const AACategoryDataToLandfallMap: { [key in AACategoryLandfall]: AACategoryKey; } = { @@ -42,6 +42,7 @@ export const AACategoryKeyToCategoryMap: { } = { [AACategoryKey.Severe]: AACategory.Severe, [AACategoryKey.Moderate]: AACategory.Moderate, + [AACategoryKey.Proba]: AACategory.Risk, }; export interface AAData { @@ -49,15 +50,15 @@ export interface AAData { polygon: any; } -export type AACategoryData = Record; - export type DistrictDataType = { - [key in AACategory]?: AACategoryData; + [key in AACategory]?: AAData; }; export type AAStormData = { - exposed?: DistrictDataType; - landfall?: LandfallImpact; + activeDistricts?: DistrictDataType; + naDistricts?: DistrictDataType; + landfall?: LandfallInfo; + timeSeries?: any; }; export type ResultType = { @@ -86,7 +87,7 @@ interface TimeSeries { [key: string]: any; } -export interface LandfallImpact { +export interface LandfallInfo { district: string; time: { start: string; @@ -105,11 +106,11 @@ export interface StormData { ready_set_results?: { exposed_area_48kt: ExposedAreaStorm; exposed_area_64kt: ExposedAreaStorm; - proba_48kt_20_5d: any; + proba_48kt_20_5d: ExposedAreaStorm; }; } -export const AAPhase = ['Ready', 'na'] as const; +export const AAPhase = ['Active', 'na'] as const; export type AAPhaseType = (typeof AAPhase)[number]; export const phaseValues = Object.values(AAPhase); diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts index 93aa56456..33ceb9bfa 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts @@ -9,7 +9,7 @@ import { AACategoryKeyToCategoryMap, AACategoryLandfall, DistrictDataType, - LandfallImpact, + LandfallInfo, ResultType, StormData, } from './types'; @@ -24,6 +24,7 @@ const watchedDistricts: { [key in AACategory]: string[] } = { 'Vilankulo', ], [AACategory.Moderate]: ['Angoche', 'Maganja Da Costa', 'Machanga', 'Govuro'], + [AACategory.Risk]: [], }; function extractDatesFromTimeSeries(data: StormData): number[] { @@ -36,39 +37,45 @@ export function parseAndTransformAA(data: StormData): ResultType { const exposedAreas = data.ready_set_results; const landfallInfo = data.landfall_info; - const districtAreaData = exposedAreas + const [activeDistricts, naDistricts] = exposedAreas ? (Object.values(AACategoryKey) as AACategoryKey[]).reduce( - (result, categoryKey) => { + ([activeResult, naResult], categoryKey) => { if (exposedAreas[categoryKey]) { const area = exposedAreas[categoryKey]; const category = AACategoryKeyToCategoryMap[categoryKey]; - const activeDistricts = area.affected_districts.filter(district => - watchedDistricts[category].includes(district), - ); + if (area.affected_districts) { + const active = area.affected_districts.filter(district => + watchedDistricts[category].includes(district), + ); - const notActiveDistricts = watchedDistricts[category].filter( - district => !area.affected_districts.includes(district), - ); - return { - ...result, - [category]: { - Ready: { - districtNames: activeDistricts, - polygon: area.polygon.coordinates, + const notActive = watchedDistricts[category].filter( + district => !area.affected_districts.includes(district), + ); + + return [ + { + ...activeResult, + [category]: { + districtNames: active, + polygon: area.polygon, + }, }, - na: { - districtNames: notActiveDistricts, - polygon: {}, + { + ...naResult, + [category]: { + districtNames: notActive, + polygon: {}, + }, }, - }, - }; + ]; + } } - return result; + return [activeResult, naResult]; }, - {} as DistrictDataType, + [{} as DistrictDataType, {} as DistrictDataType], ) - : ({} as DistrictDataType); + : [{} as DistrictDataType, {} as DistrictDataType]; const landfallImpactData = landfallInfo ? { @@ -82,7 +89,7 @@ export function parseAndTransformAA(data: StormData): ResultType { AACategoryDataToLandfallMap[intensity], ), } - : ({} as LandfallImpact); + : ({} as LandfallInfo); const validity: Validity = { mode: DatesPropagation.DAYS, @@ -96,8 +103,10 @@ export function parseAndTransformAA(data: StormData): ResultType { return { data: { - exposed: districtAreaData, + activeDistricts, + naDistricts, landfall: landfallImpactData, + timeSeries: data.time_series, }, availableDates, range: { From f0eec728d4383ef43b47a1fce2899d75e8373198 Mon Sep 17 00:00:00 2001 From: donia benharara Date: Wed, 11 Dec 2024 18:02:33 +0100 Subject: [PATCH 02/30] fix landfall severity --- .../AAStormLandfallPopup/PopupContent/index.test.tsx | 10 ++++------ .../anticipatoryAction/AAStormStateSlice/types.ts | 8 ++++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.test.tsx b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.test.tsx index 82ff20a3f..651dde902 100644 --- a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.test.tsx +++ b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.test.tsx @@ -1,16 +1,14 @@ import { render } from '@testing-library/react'; +import { AACategory } from 'context/anticipatoryAction/AAStormStateSlice/types'; import PopupContent from '.'; describe('AAStormLandfallPopup component', () => { it('renders as expected', () => { const reportDate = '2024-03-01 18:00:00'; const landfallInfo = { - landfall_time: ['2024-03-12 01:00:00', '2024-03-12 06:00:00'], - landfall_impact_district: 'Inhassoro', - landfall_impact_intensity: [ - 'severe tropical storm', - 'moderate tropical storm', - ], + time: ['2024-03-12 01:00:00', '2024-03-12 06:00:00'], + district: 'Inhassoro', + severity: [AACategory.Severe, AACategory.Moderate], }; const timelineDate = '2024-03-12 00:00:00'; diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts index 3347979fd..e430a4296 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts @@ -31,10 +31,10 @@ export const AADisplayCategory: { }; export const AACategoryDataToLandfallMap: { - [key in AACategoryLandfall]: AACategoryKey; + [key in AACategoryLandfall]: AACategory; } = { - [AACategoryLandfall.Severe]: AACategoryKey.Severe, - [AACategoryLandfall.Moderate]: AACategoryKey.Moderate, + [AACategoryLandfall.Severe]: AACategory.Severe, + [AACategoryLandfall.Moderate]: AACategory.Moderate, }; export const AACategoryKeyToCategoryMap: { @@ -90,7 +90,7 @@ interface TimeSeries { export interface LandfallInfo { district: string; time: string[]; - severity: AACategoryKey[]; + severity: AACategory[]; } export interface StormData { From d29a4f782394c702c024c705c567aaaa679a48f2 Mon Sep 17 00:00:00 2001 From: ericboucher Date: Wed, 11 Dec 2024 18:48:24 +0100 Subject: [PATCH 03/30] Add riskArea --- .../MapView/Layers/AnticipatoryActionStormLayer/index.tsx | 8 ++------ .../context/anticipatoryAction/AAStormStateSlice/types.ts | 1 + .../context/anticipatoryAction/AAStormStateSlice/utils.ts | 2 ++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx index fdd2c76c6..111728d94 100644 --- a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx +++ b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx @@ -300,18 +300,14 @@ const AnticipatoryActionStormLayer = React.memo( )} {/* Storm Risk Map view */} {viewType === 'risk' && ( - + diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts index e430a4296..ed17c318d 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts @@ -55,6 +55,7 @@ export type DistrictDataType = { }; export type AAStormData = { + riskArea?: any; activeDistricts?: DistrictDataType; naDistricts?: DistrictDataType; landfall?: LandfallInfo; diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts index 4dbeed04a..57e3b558e 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts @@ -36,6 +36,7 @@ function extractDatesFromTimeSeries(data: StormData): number[] { export function parseAndTransformAA(data: StormData): ResultType { const exposedAreas = data.ready_set_results; const landfallInfo = data.landfall_info; + const riskArea = exposedAreas?.proba_48kt_20_5d; const [activeDistricts, naDistricts] = exposedAreas ? (Object.values(AACategoryKey) as AACategoryKey[]).reduce( @@ -101,6 +102,7 @@ export function parseAndTransformAA(data: StormData): ResultType { return { data: { activeDistricts, + riskArea, naDistricts, landfall: landfallImpactData, timeSeries: data.time_series, From 9251dcb5dae44fe86e9f93197f2e26224457036a Mon Sep 17 00:00:00 2001 From: ericboucher Date: Wed, 11 Dec 2024 18:49:17 +0100 Subject: [PATCH 04/30] Update index.tsx --- .../AAStormLandfallPopup/PopupContent/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.tsx b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.tsx index 89b955d20..d816519b6 100644 --- a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.tsx +++ b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/index.tsx @@ -20,7 +20,7 @@ function PopupContent({ variant="body1" className={`${classes.text} ${classes.title}`} > - Report date: {formatReportDate(reportDate)} + Forecast date: {formatReportDate(reportDate)}
From 5304913e92c3f10159b00bd763af93e06242bbf8 Mon Sep 17 00:00:00 2001 From: donia benharara Date: Wed, 11 Dec 2024 20:08:27 +0100 Subject: [PATCH 05/30] add storm date and name panel --- .../AnticipatoryActionStormLayer/index.tsx | 9 +-- .../AnticipatoryActionStormPanel/index.tsx | 67 ++++++++++++++++--- .../AAStormStateSlice/index.ts | 1 + .../AAStormStateSlice/types.ts | 12 ++++ .../AAStormStateSlice/utils.ts | 2 + 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx index fdd2c76c6..a9b296c87 100644 --- a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx +++ b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx @@ -39,7 +39,7 @@ const AnticipatoryActionStormLayer = React.memo( ({ layer }: AnticipatoryActionStormLayerProps) => { useDefaultDate(layer.id); const map = useSelector(mapSelector); - const { viewType } = useSelector(AAFiltersSelector); + const { viewType, selectedDate } = useSelector(AAFiltersSelector); const AAStormData = useSelector(AADataSelector); const boundaryLayerState = useSelector( layerDataSelector(boundaryLayer.id), @@ -49,9 +49,6 @@ const AnticipatoryActionStormLayer = React.memo( const [selectedFeature, setSelectedFeature] = useState | null>(null); - /* this is the date the layer data corresponds to. It will be stored in redux ultimately */ - const layerDataRequestDate = '2024-03-11'; - function enhanceTimeSeries(timeSeries: TimeSeries) { const { features, ...timeSeriesRest } = timeSeries; @@ -351,13 +348,13 @@ const AnticipatoryActionStormLayer = React.memo( - {selectedFeature && AAStormData.landfall && ( + {selectedFeature && AAStormData.landfall && selectedDate && ( landfallPopupCloseHandler()} - timelineDate={layerDataRequestDate} + timelineDate={selectedDate} /> )} diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx index ec7751ecd..746290560 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx @@ -6,40 +6,52 @@ import { FormControlLabel, Radio, FormControl, + MenuItem, + Input, } from '@material-ui/core'; import React from 'react'; import { useSafeTranslation } from 'i18n'; import { useDispatch, useSelector } from 'react-redux'; import { AAAvailableDatesSelector, + AADataSelector, + AAFiltersSelector, setAAFilters, } from 'context/anticipatoryAction/AAStormStateSlice'; -import { dateRangeSelector } from 'context/mapStateSlice/selectors'; -import { getRequestDate } from 'utils/server-utils'; -import { getFormattedDate } from 'utils/date-utils'; -import { DateFormat } from 'utils/name-utils'; import { PanelSize } from 'config/types'; import HowToReadModal from '../HowToReadModal'; import ActivationTrigger from './ActivationTriggerView'; +import { StyledSelect } from '../AnticipatoryActionDroughtPanel/utils'; function AnticipatoryActionStormPanel() { const classes = useStyles(); const dispatch = useDispatch(); const { t } = useSafeTranslation(); const AAAvailableDates = useSelector(AAAvailableDatesSelector); - const { startDate: selectedDate } = useSelector(dateRangeSelector); + const AAData = useSelector(AADataSelector); + const { selectedDate } = useSelector(AAFiltersSelector); const [howToReadModalOpen, setHowToReadModalOpen] = React.useState(false); const [viewType, setViewType] = React.useState<'forecast' | 'risk'>( 'forecast', ); - const queryDate = getRequestDate(AAAvailableDates, selectedDate); - const date = getFormattedDate(queryDate, DateFormat.Default) as string; + const formatDate = (timestamp: number | string) => { + const date = new Date(timestamp); + return date.toLocaleDateString('en-US', { + month: 'numeric', + day: 'numeric', + year: 'numeric', + }); + }; React.useEffect(() => { - dispatch(setAAFilters({ selectedDate: date, viewType })); - }, [date, viewType, dispatch]); + dispatch(setAAFilters({ viewType })); + }, [viewType, dispatch]); + + if (!selectedDate) { + return null; + } return (
{t('STORM - Global view')}
+
+
+ } + renderValue={() => ( + + {selectedDate + ? `CYCLONE ${t(AAData.forecastDetails?.cyclone_name || 'Unknown Cyclone')} ${t(selectedDate)} FORECAST` + : t('Timeline')} + + )} + > + {}}> + {t(formatDate(selectedDate))} + + + {AAAvailableDates && + AAAvailableDates.map(x => ( + {}} + > + {t(formatDate(x.displayDate))} + + ))} + +
+
alignItems: 'center', width: '100%', }, + select: { + border: '1px solid #000', + borderRadius: '4px', + padding: '0.25rem', + fontSize: '0.2rem', + }, }), ); diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts index 43636b3c7..721ac294c 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts @@ -71,6 +71,7 @@ export const anticipatoryActionStormStateSlice = createSlice({ ...state.filters, ...rest, categories: newCategories, + selectedDate: '2024-03-11', }; return { ...state, diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts index e430a4296..9d9067437 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts @@ -59,6 +59,8 @@ export type AAStormData = { naDistricts?: DistrictDataType; landfall?: LandfallInfo; timeSeries?: any; + landfallDetected?: boolean; + forecastDetails?: ForecastDetails; }; export type ResultType = { @@ -87,6 +89,14 @@ interface TimeSeries { [key: string]: any; } +interface ForecastDetails { + basin: string; + cyclone_name: string; + event_id: string; + reference_time: Date; + season: number; +} + export interface LandfallInfo { district: string; time: string[]; @@ -95,6 +105,8 @@ export interface LandfallInfo { export interface StormData { time_series: TimeSeries; + landfall_detected: boolean; + forecast_details: ForecastDetails; landfall_info: { landfall_time: string[]; landfall_impact_district: string; diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts index 4dbeed04a..ddeaa328b 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts @@ -104,6 +104,8 @@ export function parseAndTransformAA(data: StormData): ResultType { naDistricts, landfall: landfallImpactData, timeSeries: data.time_series, + landfallDetected: data.landfall_detected, + forecastDetails: data.forecast_details, }, availableDates, range: { From b48b029f5e7e8e88335227f9d3829b67db14b966 Mon Sep 17 00:00:00 2001 From: ericboucher Date: Wed, 11 Dec 2024 21:49:18 +0100 Subject: [PATCH 06/30] Update index.test.tsx.snap --- .../PopupContent/__snapshots__/index.test.tsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/__snapshots__/index.test.tsx.snap b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/__snapshots__/index.test.tsx.snap index 03036ea6c..07eff98e3 100644 --- a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/__snapshots__/index.test.tsx.snap +++ b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/AAStormLandfallPopup/PopupContent/__snapshots__/index.test.tsx.snap @@ -9,7 +9,7 @@ exports[`AAStormLandfallPopup component renders as expected 1`] = ` classname="makeStyles-text-4 makeStyles-title-5" variant="body1" > - Report date: + Forecast date: 2024-03-01 6pm
Date: Wed, 11 Dec 2024 22:01:29 +0100 Subject: [PATCH 07/30] Update utils.ts --- .../AAStormStateSlice/utils.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts index 80ab68ecc..f2b621e39 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts @@ -14,6 +14,11 @@ import { StormData, } from './types'; +const districtNameMapping: { [key: string]: string } = { + Maganja_Da_Costa: 'Maganja Da Costa', + Cidade_Da_Beira: 'Cidade Da Beira', +}; + const watchedDistricts: { [key in AACategory]: string[] } = { [AACategory.Severe]: [ 'Mogincual', @@ -46,12 +51,17 @@ export function parseAndTransformAA(data: StormData): ResultType { const category = AACategoryKeyToCategoryMap[categoryKey]; if (area.affected_districts) { - const active = area.affected_districts.filter(district => - watchedDistricts[category].includes(district), - ); + const active = area.affected_districts + .map(district => districtNameMapping[district] || district) + .filter(district => + watchedDistricts[category].includes(district), + ); const notActive = watchedDistricts[category].filter( - district => !area.affected_districts.includes(district), + district => + !area.affected_districts + .map(d => districtNameMapping[d] || d) + .includes(district), ); return [ From c4a776aed7366863d0f7352d4b0dcc087da6fc26 Mon Sep 17 00:00:00 2001 From: ericboucher Date: Wed, 11 Dec 2024 22:47:57 +0100 Subject: [PATCH 08/30] Use reference time from the file --- .../AnticipatoryActionStormPanel/index.tsx | 13 +++++-------- .../anticipatoryAction/AAStormStateSlice/index.ts | 2 +- .../anticipatoryAction/AAStormStateSlice/types.ts | 2 +- .../anticipatoryAction/AAStormStateSlice/utils.ts | 9 ++++----- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx index 746290560..74c0a0f98 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx @@ -15,10 +15,11 @@ import { useDispatch, useSelector } from 'react-redux'; import { AAAvailableDatesSelector, AADataSelector, - AAFiltersSelector, setAAFilters, } from 'context/anticipatoryAction/AAStormStateSlice'; import { PanelSize } from 'config/types'; +import { useDefaultDate } from 'utils/useDefaultDate'; +import { getFormattedDate } from 'utils/date-utils'; import HowToReadModal from '../HowToReadModal'; import ActivationTrigger from './ActivationTriggerView'; import { StyledSelect } from '../AnticipatoryActionDroughtPanel/utils'; @@ -29,9 +30,10 @@ function AnticipatoryActionStormPanel() { const { t } = useSafeTranslation(); const AAAvailableDates = useSelector(AAAvailableDatesSelector); const AAData = useSelector(AADataSelector); - const { selectedDate } = useSelector(AAFiltersSelector); const [howToReadModalOpen, setHowToReadModalOpen] = React.useState(false); + const selectedDate = useDefaultDate('anticipatory_action_storm'); + const [viewType, setViewType] = React.useState<'forecast' | 'risk'>( 'forecast', ); @@ -75,15 +77,11 @@ function AnticipatoryActionStormPanel() { renderValue={() => ( {selectedDate - ? `CYCLONE ${t(AAData.forecastDetails?.cyclone_name || 'Unknown Cyclone')} ${t(selectedDate)} FORECAST` + ? `CYCLONE ${t(AAData.forecastDetails?.cyclone_name || 'Unknown Cyclone')} ${getFormattedDate(selectedDate, 'default')} FORECAST` : t('Timeline')} )} > - {}}> - {t(formatDate(selectedDate))} - - {AAAvailableDates && AAAvailableDates.map(x => ( display: 'flex', flexDirection: 'column', gap: '1rem', - height: '100%', }, headerWrapper: { padding: '1rem 1rem 0 1rem', diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts index 721ac294c..91a9f881a 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts @@ -71,7 +71,7 @@ export const anticipatoryActionStormStateSlice = createSlice({ ...state.filters, ...rest, categories: newCategories, - selectedDate: '2024-03-11', + selectedDate: undefined, }; return { ...state, diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts index c10402841..964da7955 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/types.ts @@ -94,7 +94,7 @@ interface ForecastDetails { basin: string; cyclone_name: string; event_id: string; - reference_time: Date; + reference_time: string; season: number; } diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts index f2b621e39..25807bbbc 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/utils.ts @@ -1,6 +1,6 @@ import { DatesPropagation, Validity } from 'config/types'; import { generateIntermediateDateItemFromValidity } from 'utils/server-utils'; -import { getFormattedDate } from 'utils/date-utils'; +import { getFormattedDate, getTimeInMilliseconds } from 'utils/date-utils'; import { DateFormat } from 'utils/name-utils'; import { AACategory, @@ -32,10 +32,9 @@ const watchedDistricts: { [key in AACategory]: string[] } = { [AACategory.Risk]: [], }; +// TODO - wait for dates endpoint to be implemented in the WFP API function extractDatesFromTimeSeries(data: StormData): number[] { - return data.time_series.features.map(feature => - new Date(feature.properties.time).getTime(), - ); + return [getTimeInMilliseconds(data.forecast_details.reference_time)]; } // DRAFT: This is a provisional implementation based on a test dataset with a temporary structure that is subject to change. export function parseAndTransformAA(data: StormData): ResultType { @@ -101,7 +100,7 @@ export function parseAndTransformAA(data: StormData): ResultType { const validity: Validity = { mode: DatesPropagation.DAYS, - forward: 3, + forward: 0, }; const dates = extractDatesFromTimeSeries(data); const availableDates = generateIntermediateDateItemFromValidity( From ce222e5bcb3e3722c9fbfc4264a72cd0a757197c Mon Sep 17 00:00:00 2001 From: ericboucher Date: Wed, 11 Dec 2024 22:55:31 +0100 Subject: [PATCH 09/30] Fix missing landfall --- .../AnticipatoryActionStormPanel/index.tsx | 9 +++++++-- .../anticipatoryAction/AAStormStateSlice/index.ts | 1 - 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx index 74c0a0f98..8fd0ba259 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx @@ -48,8 +48,13 @@ function AnticipatoryActionStormPanel() { }; React.useEffect(() => { - dispatch(setAAFilters({ viewType })); - }, [viewType, dispatch]); + dispatch( + setAAFilters({ + viewType, + selectedDate: getFormattedDate(selectedDate, 'default'), + }), + ); + }, [viewType, selectedDate, dispatch]); if (!selectedDate) { return null; diff --git a/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts b/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts index 91a9f881a..43636b3c7 100644 --- a/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts +++ b/frontend/src/context/anticipatoryAction/AAStormStateSlice/index.ts @@ -71,7 +71,6 @@ export const anticipatoryActionStormStateSlice = createSlice({ ...state.filters, ...rest, categories: newCategories, - selectedDate: undefined, }; return { ...state, From 587cb9ab034853ab4e4f27e65c587a2138a3ccc1 Mon Sep 17 00:00:00 2001 From: ericboucher Date: Wed, 11 Dec 2024 23:51:34 +0100 Subject: [PATCH 10/30] Fix timeline shift --- .../src/components/MapView/DateSelector/index.tsx | 11 +++++------ .../AnticipatoryActionStormPanel/index.tsx | 2 ++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/MapView/DateSelector/index.tsx b/frontend/src/components/MapView/DateSelector/index.tsx index 65054eabb..74a6332d0 100644 --- a/frontend/src/components/MapView/DateSelector/index.tsx +++ b/frontend/src/components/MapView/DateSelector/index.tsx @@ -57,10 +57,10 @@ const POINTER_ID = 'datePointerSelector'; const calculateStartAndEndDates = (startDate: Date, selectedTab: string) => { const year = startDate.getFullYear() - - (isAnticipatoryActionLayer(selectedTab) && startDate.getMonth() < 3 + (selectedTab === Panel.AnticipatoryActionDrought && startDate.getMonth() < 3 ? 1 : 0); - const startMonth = isAnticipatoryActionLayer(selectedTab) ? 3 : 0; // April for anticipatory_action, January otherwise + const startMonth = selectedTab === Panel.AnticipatoryActionDrought ? 3 : 0; // April for anticipatory_action, January otherwise const start = new Date(year, startMonth, 1); const end = new Date(year, startMonth + 11, 31); @@ -153,8 +153,8 @@ const DateSelector = memo(() => { // eslint-disable-next-line fp/no-mutating-methods selectedLayers .sort((a, b) => { - const aIsAnticipatory = a.id.includes('anticipatory_action'); - const bIsAnticipatory = b.id.includes('anticipatory_action'); + const aIsAnticipatory = a.id.includes('anticipatory_action_drought'); + const bIsAnticipatory = b.id.includes('anticipatory_action_drought'); if (aIsAnticipatory && !bIsAnticipatory) { return -1; } @@ -163,8 +163,7 @@ const DateSelector = memo(() => { } return 0; }) - .filter(l => l.type !== AnticipatoryAction.storm) // disable showing anticipatory action storm data - .map(l => (isAnticipatoryActionLayer(l.type) ? AALayers : l)) + .map(l => (l.id === 'anticipatory_action_drought' ? AALayers : l)) .flat(), [selectedLayers, AALayers], ); diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx index 8fd0ba259..1697fc3dd 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx @@ -23,6 +23,7 @@ import { getFormattedDate } from 'utils/date-utils'; import HowToReadModal from '../HowToReadModal'; import ActivationTrigger from './ActivationTriggerView'; import { StyledSelect } from '../AnticipatoryActionDroughtPanel/utils'; +import { updateDateRange } from 'context/mapStateSlice'; function AnticipatoryActionStormPanel() { const classes = useStyles(); @@ -54,6 +55,7 @@ function AnticipatoryActionStormPanel() { selectedDate: getFormattedDate(selectedDate, 'default'), }), ); + dispatch(updateDateRange({ startDate: selectedDate })); }, [viewType, selectedDate, dispatch]); if (!selectedDate) { From 851163bb33e0ba03f1c953790a52d3c517fa0934 Mon Sep 17 00:00:00 2001 From: ericboucher Date: Wed, 11 Dec 2024 23:52:22 +0100 Subject: [PATCH 11/30] Update index.tsx --- frontend/src/components/MapView/DateSelector/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/MapView/DateSelector/index.tsx b/frontend/src/components/MapView/DateSelector/index.tsx index 74a6332d0..d4d1d3779 100644 --- a/frontend/src/components/MapView/DateSelector/index.tsx +++ b/frontend/src/components/MapView/DateSelector/index.tsx @@ -35,7 +35,6 @@ import { leftPanelTabValueSelector } from 'context/leftPanelStateSlice'; import { updateDateRange } from 'context/mapStateSlice'; import { getRequestDate } from 'utils/server-utils'; import { AAAvailableDatesSelector } from 'context/anticipatoryAction/AADroughtStateSlice'; -import { isAnticipatoryActionLayer } from 'config/utils'; import TickSvg from './tick.svg'; import DateSelectorInput from './DateSelectorInput'; import TimelineItems from './TimelineItems'; From d492c6d87e4bc60dd749026437cbaaef3948e5e9 Mon Sep 17 00:00:00 2001 From: ericboucher Date: Wed, 11 Dec 2024 23:55:07 +0100 Subject: [PATCH 12/30] Update index.tsx --- .../AnticipatoryActionStormPanel/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx index 1697fc3dd..9c88bd57b 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx @@ -18,12 +18,12 @@ import { setAAFilters, } from 'context/anticipatoryAction/AAStormStateSlice'; import { PanelSize } from 'config/types'; +import { updateDateRange } from 'context/mapStateSlice'; import { useDefaultDate } from 'utils/useDefaultDate'; import { getFormattedDate } from 'utils/date-utils'; import HowToReadModal from '../HowToReadModal'; import ActivationTrigger from './ActivationTriggerView'; import { StyledSelect } from '../AnticipatoryActionDroughtPanel/utils'; -import { updateDateRange } from 'context/mapStateSlice'; function AnticipatoryActionStormPanel() { const classes = useStyles(); From affcd01d03b0e5d47a97043ae2af0695bec9749d Mon Sep 17 00:00:00 2001 From: ericboucher Date: Thu, 12 Dec 2024 00:03:37 +0100 Subject: [PATCH 13/30] Fix active and NA titles --- .../ActivationTriggerView/index.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/ActivationTriggerView/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/ActivationTriggerView/index.tsx index ff2b97286..5976aff88 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/ActivationTriggerView/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/ActivationTriggerView/index.tsx @@ -116,7 +116,7 @@ function ActivationTrigger({ dialogs }: ActivationTriggerProps) { ${AADisplayCategory[category as AACategory]}`, + `Active ${AADisplayCategory[category as AACategory]}`, )} />
@@ -142,9 +142,7 @@ function ActivationTrigger({ dialogs }: ActivationTriggerProps) {
${AADisplayCategory[category as AACategory]}`, - )} + text={t(`NA ${AADisplayCategory[category as AACategory]}`)} />
From 0a07f338b61dd60ba5401f3dd0177c5840605f9d Mon Sep 17 00:00:00 2001 From: donia benharara Date: Thu, 12 Dec 2024 12:08:07 +0100 Subject: [PATCH 14/30] updated select style in panel --- .../AnticipatoryActionDroughtPanel/index.tsx | 3 ++- .../AnticipatoryActionDroughtPanel/utils.tsx | 10 ---------- .../AnticipatoryActionStormPanel/index.tsx | 14 +++++++++++--- .../LeftPanel/AnticipatoryActionPanel/utils.tsx | 16 +++++++++++++++- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx index ca5bd53eb..f548c1b33 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx @@ -39,7 +39,8 @@ import { import { getFormattedDate } from 'utils/date-utils'; import { DateFormat } from 'utils/name-utils'; import { PanelSize } from 'config/types'; -import { StyledCheckboxLabel, StyledRadioLabel, StyledSelect } from './utils'; +import { StyledCheckboxLabel, StyledRadioLabel } from './utils'; +import { StyledSelect } from '../utils'; import DistrictView from './DistrictView/index'; import HomeTable from './HomeTable'; import HowToReadModal from '../HowToReadModal'; diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/utils.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/utils.tsx index 6cfd7aab4..261ce52e9 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/utils.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/utils.tsx @@ -6,8 +6,6 @@ import { FormControlLabelProps, Radio, RadioProps, - Select, - SelectProps, useRadioGroup, withStyles, } from '@material-ui/core'; @@ -112,14 +110,6 @@ export const StyledCheckboxLabel = withStyles({ ), ); -export const StyledSelect = withStyles({ - root: { - '&:focus': { - backgroundColor: 'transparent', - }, - }, -})((props: SelectProps) => } renderValue={() => ( - + {selectedDate ? `CYCLONE ${t(AAData.forecastDetails?.cyclone_name || 'Unknown Cyclone')} ${getFormattedDate(selectedDate, 'default')} FORECAST` : t('Timeline')} @@ -164,7 +164,15 @@ const useStyles = makeStyles(() => border: '1px solid #000', borderRadius: '4px', padding: '0.25rem', - fontSize: '0.2rem', + paddingRight: '1rem', + }, + selectText: { + fontSize: '14px', + fontWeight: 600, + lineHeight: '1.5rem', + padding: '0rem 0.5rem', + whiteSpace: 'normal', + wordWrap: 'break-word', }, }), ); diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/utils.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/utils.tsx index f3305f5c7..8ae2d9325 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/utils.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/utils.tsx @@ -1,6 +1,20 @@ /* eslint-disable react-refresh/only-export-components */ import { black, cyanBlue } from 'muiTheme'; -import { createStyles, makeStyles } from '@material-ui/core'; +import { + createStyles, + makeStyles, + Select, + SelectProps, + withStyles, +} from '@material-ui/core'; + +export const StyledSelect = withStyles({ + root: { + '&:focus': { + backgroundColor: 'transparent', + }, + }, +})((props: SelectProps) => } renderValue={() => ( - {date - ? `CYCLONE ${t(AAData.forecastDetails?.cyclone_name || 'Unknown Cyclone')} ${date} FORECAST` - : t('Timeline')} + {date ? ( + <> + CYCLONE{' '} + {t(AAData.forecastDetails?.cyclone_name || 'Unknown Cyclone')}{' '} + {hour} +
+ {date} FORECAST + + ) : ( + t('Timeline') + )}
)} > @@ -155,14 +164,12 @@ const useStyles = makeStyles(() => select: { border: '1px solid #000', borderRadius: '4px', - padding: '0.25rem', - paddingRight: '1rem', + padding: '0rem 0.5rem', }, selectText: { - fontSize: '14px', + fontSize: '18px', fontWeight: 600, - lineHeight: '1.5rem', - padding: '0rem 0.5rem', + lineHeight: '18px', whiteSpace: 'normal', wordWrap: 'break-word', }, diff --git a/frontend/src/utils/date-utils.ts b/frontend/src/utils/date-utils.ts index d5cbe8554..83b976184 100644 --- a/frontend/src/utils/date-utils.ts +++ b/frontend/src/utils/date-utils.ts @@ -131,7 +131,9 @@ export const getFormattedDate = ( | DateFormat.Default | DateFormat.DateTime | DateFormat.DayFirstSnakeCase - | DateFormat.ISO, + | DateFormat.ISO + | DateFormat.MiddleEndian + | DateFormat.TimeOnly, dateLocale: string = 'default', ) => { if (date === undefined) { @@ -152,11 +154,16 @@ export const getFormattedDate = ( return `${year}_${month}_${day}`; case DateFormat.DayFirstSnakeCase: return `${day}_${month}_${year}`; + case DateFormat.MiddleEndian: + return `${month}/${day}/${year}`; + case DateFormat.TimeOnly: case DateFormat.DateTime: case DateFormat.ISO: { const hours = String(jsDate.getUTCHours()).padStart(2, '0'); const minutes = String(jsDate.getUTCMinutes()).padStart(2, '0'); switch (format) { + case DateFormat.TimeOnly: + return `${hours}:${minutes}`; case DateFormat.DateTime: return `${year}-${month}-${day} ${hours}:${minutes}`; case DateFormat.ISO: { diff --git a/frontend/src/utils/name-utils.ts b/frontend/src/utils/name-utils.ts index ff303eeb0..eb7525172 100644 --- a/frontend/src/utils/name-utils.ts +++ b/frontend/src/utils/name-utils.ts @@ -30,4 +30,6 @@ export enum DateFormat { ISO = 'YYYY-MM-DDTHH:mm:ss', DateTime = 'YYYY-MM-DD HH:mm', DayFirstSnakeCase = 'DD_MM_YYYY', + MiddleEndian = 'MM/DD/YYYY', + TimeOnly = 'HH:mm', } From 12a215ac00d1754431f52bf55c3ed45f4a712125 Mon Sep 17 00:00:00 2001 From: donia benharara Date: Thu, 12 Dec 2024 18:57:55 +0100 Subject: [PATCH 22/30] fix load AA layer from url or on reload page WIP --- .../components/MapView/LeftPanel/index.tsx | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/MapView/LeftPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/index.tsx index e9809d7dd..f4690e765 100644 --- a/frontend/src/components/MapView/LeftPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/index.tsx @@ -72,12 +72,29 @@ 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 AALayerInUrl = React.useMemo( + () => + selectedLayers.find(x => AALayerIds.includes(x.id as AnticipatoryAction)), + [selectedLayers], + ); + + // Load AA config based on AA layer id const AAConfig = React.useMemo(() => { - if (isAnticipatoryActionLayer(tabValue)) { - return getAAConfig(tabValue as AnticipatoryAction); + if (AALayerInUrl && isAnticipatoryActionLayer(AALayerInUrl.id)) { + return getAAConfig(AALayerInUrl.id as AnticipatoryAction); } return null; - }, [tabValue]); + }, [AALayerInUrl]); const AAData = useSelector((state: RootState) => AAConfig ? AAConfig.dataSelector(state) : null, @@ -87,14 +104,6 @@ const LeftPanel = memo(() => { ); const loadAAData = AAConfig ? AAConfig.loadAction : null; const serverAvailableDates = useSelector(availableDatesSelector); - const selectedLayers = useSelector(layersSelector); - const map = useSelector(mapSelector); - const { updateHistory, appendLayerToUrl, removeLayerFromUrl } = - useUrlHistory(); - - const classes = useStyles({ tabValue }); - - const isPanelHidden = tabValue === Panel.None; // Sync serverAvailableDates with AAAvailableDates when the latter updates. React.useEffect(() => { @@ -115,12 +124,6 @@ const LeftPanel = memo(() => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [AAAvailableDates, dispatch]); - const AALayerInUrl = React.useMemo( - () => - selectedLayers.find(x => AALayerIds.includes(x.id as AnticipatoryAction)), - [selectedLayers], - ); - // 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 From 3847acc04e8a28cb0a22b9b219183b62e7cf2ab8 Mon Sep 17 00:00:00 2001 From: ericboucher Date: Fri, 13 Dec 2024 15:27:11 +0100 Subject: [PATCH 23/30] Tentative cleanup --- .../AnticipatoryActionDroughtPanel/index.tsx | 7 +- .../AnticipatoryActionStormPanel/index.tsx | 6 +- .../useAnticipatoryAction.ts | 74 ++++++++++ .../components/MapView/LeftPanel/index.tsx | 134 ------------------ 4 files changed, 84 insertions(+), 137 deletions(-) create mode 100644 frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx index f548c1b33..5c34c29e8 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx @@ -38,7 +38,7 @@ import { } 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 +46,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,6 +68,10 @@ function AnticipatoryActionDroughtPanel() { const classes = useStyles(); const dispatch = useDispatch(); const { t } = useSafeTranslation(); + const { AAData, AAConfig } = useAnticipatoryAction( + AnticipatoryAction.drought, + ); + console.log({ AAData, AAConfig }); const monitoredDistricts = useSelector(AAMonitoredDistrictsSelector); const AAAvailableDates = useSelector(AAAvailableDatesSelector); const selectedDistrict = useSelector(AASelectedDistrictSelector); diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx index 4513682a5..63513a3ea 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx @@ -17,7 +17,7 @@ import { AADataSelector, setAAFilters, } from 'context/anticipatoryAction/AAStormStateSlice'; -import { PanelSize } from 'config/types'; +import { AnticipatoryAction, PanelSize } from 'config/types'; import { updateDateRange } from 'context/mapStateSlice'; import { useDefaultDate } from 'utils/useDefaultDate'; import { getFormattedDate } from 'utils/date-utils'; @@ -26,13 +26,15 @@ import { DateFormat } from 'utils/name-utils'; import HowToReadModal from '../HowToReadModal'; import ActivationTrigger from './ActivationTriggerView'; import { StyledSelect } from '../utils'; +import { useAnticipatoryAction } from '../useAnticipatoryAction'; function AnticipatoryActionStormPanel() { const classes = useStyles(); const dispatch = useDispatch(); const { t } = useSafeTranslation(); + const { AAData } = useAnticipatoryAction(AnticipatoryAction.storm); const AAAvailableDates = useSelector(AAAvailableDatesSelector); - const AAData = useSelector(AADataSelector); + // const AAData = useSelector(AADataSelector); const [howToReadModalOpen, setHowToReadModalOpen] = React.useState(false); const selectedDate = useDefaultDate('anticipatory_action_storm'); diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts new file mode 100644 index 000000000..f8244a9fe --- /dev/null +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts @@ -0,0 +1,74 @@ +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from 'context/store'; +import { AnticipatoryAction } from 'config/types'; +import { getAAConfig } from 'context/anticipatoryAction/config'; +import { layersSelector, mapSelector } from 'context/mapStateSlice/selectors'; +import { updateDateRange } from 'context/mapStateSlice'; +import { getUrlKey, useUrlHistory } from 'utils/url-utils'; +import { AALayerIds, LayerDefinitions, isWindowEmpty } from 'config/utils'; +import { toggleRemoveLayer } from '../layersPanel/MenuItem/MenuSwitch/SwitchItem/utils'; + +export function useAnticipatoryAction(actionType: AnticipatoryAction) { + 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 loadAAData = AAConfig.loadAction; + + // Load data when component mounts + useEffect(() => { + dispatch(loadAAData()); + }, [dispatch, loadAAData]); + + // 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 })); + } + + return () => { + if (!isWindowEmpty(AAData, 'Window 1')) { + toggleRemoveLayer( + layer, + map, + getUrlKey(layer), + dispatch, + removeLayerFromUrl, + ); + } + }; + }, []); + + return { + AAData, + AAConfig, + }; +} diff --git a/frontend/src/components/MapView/LeftPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/index.tsx index f4690e765..8dad59cd6 100644 --- a/frontend/src/components/MapView/LeftPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/index.tsx @@ -73,144 +73,10 @@ 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 AALayerInUrl = React.useMemo( - () => - selectedLayers.find(x => AALayerIds.includes(x.id as AnticipatoryAction)), - [selectedLayers], - ); - - // 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 })); - - // 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 renderedChartsPanel = React.useMemo(() => { if (!areChartLayersAvailable) { return null; From 9159c377be1326cc979c6e52d7b9c99a3911482c Mon Sep 17 00:00:00 2001 From: donia benharara Date: Fri, 13 Dec 2024 20:33:54 +0100 Subject: [PATCH 24/30] try to fix aa loading --- .../components/MapView/DateSelector/index.tsx | 107 ++++++++++++------ .../AnticipatoryActionDroughtPanel/index.tsx | 11 +- .../AnticipatoryActionStormPanel/index.tsx | 2 - .../useAnticipatoryAction.ts | 40 ++++++- .../components/MapView/LeftPanel/index.tsx | 41 ++++--- frontend/src/utils/keep-layer-utils.ts | 8 +- frontend/src/utils/layers-utils.tsx | 31 ++++- 7 files changed, 161 insertions(+), 79 deletions(-) diff --git a/frontend/src/components/MapView/DateSelector/index.tsx b/frontend/src/components/MapView/DateSelector/index.tsx index d4d1d3779..6d7de14bc 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'; @@ -56,10 +58,11 @@ const POINTER_ID = 'datePointerSelector'; const calculateStartAndEndDates = (startDate: Date, selectedTab: string) => { const year = startDate.getFullYear() - - (selectedTab === Panel.AnticipatoryActionDrought && startDate.getMonth() < 3 + (isAnticipatoryActionLayer(selectedTab) && 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 +122,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( @@ -152,8 +179,8 @@ const DateSelector = memo(() => { // eslint-disable-next-line fp/no-mutating-methods selectedLayers .sort((a, b) => { - const aIsAnticipatory = a.id.includes('anticipatory_action_drought'); - const bIsAnticipatory = b.id.includes('anticipatory_action_drought'); + const aIsAnticipatory = a.id.includes('anticipatory_action'); + const bIsAnticipatory = b.id.includes('anticipatory_action'); if (aIsAnticipatory && !bIsAnticipatory) { return -1; } @@ -162,7 +189,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 +369,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/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx index 5c34c29e8..dfd66dc4f 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx @@ -38,7 +38,7 @@ import { } from 'utils/server-utils'; import { getFormattedDate } from 'utils/date-utils'; import { DateFormat } from 'utils/name-utils'; -import { AnticipatoryAction, PanelSize } from 'config/types'; +import { PanelSize } from 'config/types'; import { StyledCheckboxLabel, StyledRadioLabel } from './utils'; import { StyledSelect } from '../utils'; import DistrictView from './DistrictView/index'; @@ -46,7 +46,6 @@ import HomeTable from './HomeTable'; import HowToReadModal from '../HowToReadModal'; import Timeline from './Timeline'; import Forecast from './Forecast'; -import { useAnticipatoryAction } from '../useAnticipatoryAction'; const isZimbabwe = safeCountry === 'zimbabwe'; @@ -68,10 +67,10 @@ function AnticipatoryActionDroughtPanel() { const classes = useStyles(); const dispatch = useDispatch(); const { t } = useSafeTranslation(); - const { AAData, AAConfig } = useAnticipatoryAction( - AnticipatoryAction.drought, - ); - console.log({ AAData, AAConfig }); + // TODO: we should use useAnticipatoryAction hook + // const { AAData, AAConfig } = useAnticipatoryAction( + // AnticipatoryAction.drought, + // ); const monitoredDistricts = useSelector(AAMonitoredDistrictsSelector); const AAAvailableDates = useSelector(AAAvailableDatesSelector); const selectedDistrict = useSelector(AASelectedDistrictSelector); diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx index 63513a3ea..36b58e88c 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx @@ -14,7 +14,6 @@ import { useSafeTranslation } from 'i18n'; import { useDispatch, useSelector } from 'react-redux'; import { AAAvailableDatesSelector, - AADataSelector, setAAFilters, } from 'context/anticipatoryAction/AAStormStateSlice'; import { AnticipatoryAction, PanelSize } from 'config/types'; @@ -34,7 +33,6 @@ function AnticipatoryActionStormPanel() { const { t } = useSafeTranslation(); const { AAData } = useAnticipatoryAction(AnticipatoryAction.storm); const AAAvailableDates = useSelector(AAAvailableDatesSelector); - // const AAData = useSelector(AADataSelector); const [howToReadModalOpen, setHowToReadModalOpen] = React.useState(false); const selectedDate = useDefaultDate('anticipatory_action_storm'); diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts index f8244a9fe..18690da1f 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts @@ -6,7 +6,17 @@ import { getAAConfig } from 'context/anticipatoryAction/config'; import { layersSelector, mapSelector } from 'context/mapStateSlice/selectors'; import { updateDateRange } from 'context/mapStateSlice'; import { getUrlKey, useUrlHistory } from 'utils/url-utils'; -import { AALayerIds, LayerDefinitions, isWindowEmpty } from 'config/utils'; +import { + AALayerIds, + LayerDefinitions, + isWindowEmpty, + isWindowedDates, +} from 'config/utils'; +import { getAAAvailableDatesCombined } from 'utils/server-utils'; +import { + availableDatesSelector, + updateLayersCapabilities, +} from 'context/serverStateSlice'; import { toggleRemoveLayer } from '../layersPanel/MenuItem/MenuSwitch/SwitchItem/utils'; export function useAnticipatoryAction(actionType: AnticipatoryAction) { @@ -23,13 +33,36 @@ export function useAnticipatoryAction(actionType: AnticipatoryAction) { const AAData = useSelector((state: RootState) => AAConfig.dataSelector(state), ); + const AAAvailableDates = useSelector((state: RootState) => + AAConfig.availableDatesSelector(state), + ); const loadAAData = AAConfig.loadAction; + const serverAvailableDates = useSelector(availableDatesSelector); // Load data when component mounts useEffect(() => { dispatch(loadAAData()); }, [dispatch, loadAAData]); + useEffect(() => { + if (AAAvailableDates) { + const combinedAvailableDates = isWindowedDates(AAAvailableDates) + ? getAAAvailableDatesCombined(AAAvailableDates) + : AAAvailableDates; + const updatedCapabilities = AALayerIds.reduce( + (acc, layerId) => ({ + ...acc, + [layerId]: combinedAvailableDates, + }), + { ...serverAvailableDates }, + ); + dispatch(updateLayersCapabilities(updatedCapabilities)); + dispatch(updateDateRange(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]); + // Handle URL updates when mounting/unmounting useEffect(() => { const layer = LayerDefinitions[actionType]; @@ -44,18 +77,16 @@ export function useAnticipatoryAction(actionType: AnticipatoryAction) { removeLayerFromUrl, ); } - const updatedUrl = appendLayerToUrl( getUrlKey(layer), selectedLayers, layer, ); updateHistory(getUrlKey(layer), updatedUrl); - dispatch(updateDateRange({ startDate: undefined })); } return () => { - if (!isWindowEmpty(AAData, 'Window 1')) { + if (isWindowEmpty(AAData, 'Window 1')) { toggleRemoveLayer( layer, map, @@ -65,6 +96,7 @@ export function useAnticipatoryAction(actionType: AnticipatoryAction) { ); } }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { diff --git a/frontend/src/components/MapView/LeftPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/index.tsx index 8dad59cd6..ae9c0e84e 100644 --- a/frontend/src/components/MapView/LeftPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/index.tsx @@ -5,30 +5,14 @@ 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 } from 'context/mapStateSlice/selectors'; import AnalysisPanel from './AnalysisPanel'; import ChartsPanel from './ChartsPanel'; import TablesPanel from './TablesPanel'; @@ -42,7 +26,6 @@ import { isAnticipatoryActionDroughtAvailable, isAnticipatoryActionStormAvailable, } from './utils'; -import { toggleRemoveLayer } from './layersPanel/MenuItem/MenuSwitch/SwitchItem/utils'; import AlertsPanel from './AlertsPanel'; interface TabPanelProps { @@ -72,6 +55,11 @@ const TabPanel = memo(({ children, value, index, ...other }: TabPanelProps) => ( const LeftPanel = memo(() => { const dispatch = useDispatch(); const tabValue = useSelector(leftPanelTabValueSelector); + const selectedLayers = useSelector(layersSelector); + + const AALayerInUrl = selectedLayers.find(x => + AALayerIds.includes(x.id as AnticipatoryAction), + ); const classes = useStyles({ tabValue }); @@ -116,6 +104,17 @@ const LeftPanel = memo(() => { }, [tabValue, dispatch]); const renderedAnticipatoryActionPanel = React.useMemo(() => { + const shouldLoadAAPanel = + !isAnticipatoryActionLayer(tabValue) && AALayerInUrl !== undefined; + + if (shouldLoadAAPanel) { + if (AALayerInUrl.id === Panel.AnticipatoryActionDrought) { + dispatch(setTabValue(Panel.AnticipatoryActionDrought)); + } else if (AALayerInUrl.id === AnticipatoryAction.storm) { + dispatch(setTabValue(Panel.AnticipatoryActionStorm)); + } + } + if ( isAnticipatoryActionDroughtAvailable && tabValue === Panel.AnticipatoryActionDrought @@ -128,7 +127,7 @@ const LeftPanel = memo(() => { } if ( isAnticipatoryActionStormAvailable && - tabValue === Panel.AnticipatoryActionStorm + (tabValue === Panel.AnticipatoryActionStorm || shouldLoadAAPanel) ) { return ( @@ -137,7 +136,7 @@ const LeftPanel = memo(() => { ); } return null; - }, [tabValue]); + }, [tabValue, AALayerInUrl, dispatch]); return ( { 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], From bd3b1715ef6333ca6b2ddb8fc5a19d12774ef38c Mon Sep 17 00:00:00 2001 From: donia benharara Date: Thu, 19 Dec 2024 18:22:35 +0100 Subject: [PATCH 25/30] fix loading mechanism --- .../AnticipatoryActionDroughtPanel/index.tsx | 18 +++----- .../AnticipatoryActionStormPanel/index.tsx | 19 ++++---- .../useAnticipatoryAction.ts | 30 ++++++++++-- .../components/MapView/LeftPanel/index.tsx | 46 +++++++++++++++---- 4 files changed, 79 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx index dfd66dc4f..334321194 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, @@ -38,7 +36,7 @@ import { } 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 +44,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,17 +66,14 @@ function AnticipatoryActionDroughtPanel() { const classes = useStyles(); const dispatch = useDispatch(); const { t } = useSafeTranslation(); - // TODO: we should use useAnticipatoryAction hook - // const { AAData, AAConfig } = useAnticipatoryAction( - // AnticipatoryAction.drought, - // ); + const { AAData, AAAvailableDates } = 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); @@ -93,14 +89,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]); + }, [AAData, selectedDistrict]); const layerAvailableDates = AAAvailableDates !== undefined diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx index 36b58e88c..52885c14a 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionStormPanel/index.tsx @@ -11,12 +11,9 @@ import { } from '@material-ui/core'; import React from 'react'; import { useSafeTranslation } from 'i18n'; -import { useDispatch, useSelector } from 'react-redux'; -import { - AAAvailableDatesSelector, - setAAFilters, -} from 'context/anticipatoryAction/AAStormStateSlice'; -import { AnticipatoryAction, PanelSize } from 'config/types'; +import { useDispatch } from 'react-redux'; +import { setAAFilters } from 'context/anticipatoryAction/AAStormStateSlice'; +import { AnticipatoryAction, DateItem, PanelSize } from 'config/types'; import { updateDateRange } from 'context/mapStateSlice'; import { useDefaultDate } from 'utils/useDefaultDate'; import { getFormattedDate } from 'utils/date-utils'; @@ -31,8 +28,9 @@ function AnticipatoryActionStormPanel() { const classes = useStyles(); const dispatch = useDispatch(); const { t } = useSafeTranslation(); - const { AAData } = useAnticipatoryAction(AnticipatoryAction.storm); - const AAAvailableDates = useSelector(AAAvailableDatesSelector); + const { AAData, AAAvailableDates } = useAnticipatoryAction( + AnticipatoryAction.storm, + ); const [howToReadModalOpen, setHowToReadModalOpen] = React.useState(false); const selectedDate = useDefaultDate('anticipatory_action_storm'); @@ -41,7 +39,10 @@ function AnticipatoryActionStormPanel() { 'forecast', ); - const queryDate = getRequestDate(AAAvailableDates, selectedDate); + const queryDate = getRequestDate( + AAAvailableDates as DateItem[], + selectedDate, + ); const date = getFormattedDate(queryDate, DateFormat.MiddleEndian) as string; const hour = getFormattedDate(queryDate, DateFormat.TimeOnly) as string; diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts index 18690da1f..63214da5d 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { RootState } from 'context/store'; -import { AnticipatoryAction } from 'config/types'; +import { AnticipatoryAction, DateItem } from 'config/types'; import { getAAConfig } from 'context/anticipatoryAction/config'; import { layersSelector, mapSelector } from 'context/mapStateSlice/selectors'; import { updateDateRange } from 'context/mapStateSlice'; @@ -17,9 +17,27 @@ import { availableDatesSelector, updateLayersCapabilities, } from 'context/serverStateSlice'; +import { AnticipatoryActionData } from 'context/anticipatoryAction/AADroughtStateSlice/types'; +import { AAStormData } from 'context/anticipatoryAction/AAStormStateSlice/types'; import { toggleRemoveLayer } from '../layersPanel/MenuItem/MenuSwitch/SwitchItem/utils'; -export function useAnticipatoryAction(actionType: AnticipatoryAction) { +type AADataByAction = + 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); @@ -86,7 +104,10 @@ export function useAnticipatoryAction(actionType: AnticipatoryAction) { } return () => { - if (isWindowEmpty(AAData, 'Window 1')) { + if ( + isWindowEmpty(AAData, 'Window 1') || + Object.keys(AAData).length === 0 + ) { toggleRemoveLayer( layer, map, @@ -100,7 +121,8 @@ export function useAnticipatoryAction(actionType: AnticipatoryAction) { }, []); return { - AAData, + 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 ae9c0e84e..a20203076 100644 --- a/frontend/src/components/MapView/LeftPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/index.tsx @@ -12,7 +12,8 @@ import { isAnticipatoryActionLayer, } from 'config/utils'; import { setSelectedBoundaries } from 'context/mapSelectionLayerStateSlice'; -import { layersSelector } from 'context/mapStateSlice/selectors'; +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'; @@ -27,6 +28,7 @@ import { isAnticipatoryActionStormAvailable, } from './utils'; import AlertsPanel from './AlertsPanel'; +import { toggleRemoveLayer } from './layersPanel/MenuItem/MenuSwitch/SwitchItem/utils'; interface TabPanelProps { children?: React.ReactNode; @@ -56,6 +58,8 @@ const LeftPanel = memo(() => { const dispatch = useDispatch(); const tabValue = useSelector(leftPanelTabValueSelector); const selectedLayers = useSelector(layersSelector); + const map = useSelector(mapSelector); + const { removeLayerFromUrl } = useUrlHistory(); const AALayerInUrl = selectedLayers.find(x => AALayerIds.includes(x.id as AnticipatoryAction), @@ -96,6 +100,23 @@ 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, + ); + } + }, [AALayerInUrl, dispatch, map, removeLayerFromUrl, tabValue]); + // Reset selected boundaries when tab changes from Alerts React.useEffect(() => { if (tabValue !== Panel.Alerts) { @@ -103,21 +124,25 @@ const LeftPanel = memo(() => { } }, [tabValue, dispatch]); - const renderedAnticipatoryActionPanel = React.useMemo(() => { - const shouldLoadAAPanel = - !isAnticipatoryActionLayer(tabValue) && AALayerInUrl !== undefined; - - if (shouldLoadAAPanel) { - if (AALayerInUrl.id === Panel.AnticipatoryActionDrought) { + // 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 ( @@ -127,7 +152,8 @@ const LeftPanel = memo(() => { } if ( isAnticipatoryActionStormAvailable && - (tabValue === Panel.AnticipatoryActionStorm || shouldLoadAAPanel) + tabValue === Panel.AnticipatoryActionStorm && + shouldLoadAAPanel ) { return ( @@ -136,7 +162,7 @@ const LeftPanel = memo(() => { ); } return null; - }, [tabValue, AALayerInUrl, dispatch]); + }, [tabValue]); return ( Date: Fri, 20 Dec 2024 12:17:07 +0100 Subject: [PATCH 26/30] fix load apply custom date --- .../AnticipatoryActionStormLayer/index.tsx | 23 ++++++++--------- .../useAnticipatoryAction.ts | 25 ++----------------- .../components/MapView/LeftPanel/index.tsx | 3 ++- 3 files changed, 15 insertions(+), 36 deletions(-) diff --git a/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx b/frontend/src/components/MapView/Layers/AnticipatoryActionStormLayer/index.tsx index c419b7946..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; @@ -116,10 +116,9 @@ const AnticipatoryActionStormLayer = React.memo( return { ...timeSeriesRest, features: newFeatures }; } - // Replace all AAStormData references with stormData 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) { @@ -250,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; @@ -267,9 +266,9 @@ const AnticipatoryActionStormLayer = React.memo( }) .filter(Boolean), }; - }, [boundaryData, AAStormData]); + }, [boundaryData, stormData]); - if (!boundaryData || !AAStormData) { + if (!boundaryData || !stormData) { return null; } @@ -306,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/useAnticipatoryAction.ts b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts index 63214da5d..53cb70470 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts @@ -6,12 +6,7 @@ import { getAAConfig } from 'context/anticipatoryAction/config'; import { layersSelector, mapSelector } from 'context/mapStateSlice/selectors'; import { updateDateRange } from 'context/mapStateSlice'; import { getUrlKey, useUrlHistory } from 'utils/url-utils'; -import { - AALayerIds, - LayerDefinitions, - isWindowEmpty, - isWindowedDates, -} from 'config/utils'; +import { AALayerIds, LayerDefinitions, isWindowedDates } from 'config/utils'; import { getAAAvailableDatesCombined } from 'utils/server-utils'; import { availableDatesSelector, @@ -79,7 +74,7 @@ export function useAnticipatoryAction( } // 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]); + }, [AAAvailableDates]); // Handle URL updates when mounting/unmounting useEffect(() => { @@ -102,22 +97,6 @@ export function useAnticipatoryAction( ); updateHistory(getUrlKey(layer), updatedUrl); } - - return () => { - if ( - isWindowEmpty(AAData, 'Window 1') || - Object.keys(AAData).length === 0 - ) { - toggleRemoveLayer( - layer, - map, - getUrlKey(layer), - dispatch, - removeLayerFromUrl, - ); - } - }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { diff --git a/frontend/src/components/MapView/LeftPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/index.tsx index a20203076..00ad1ed55 100644 --- a/frontend/src/components/MapView/LeftPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/index.tsx @@ -115,7 +115,8 @@ const LeftPanel = memo(() => { removeLayerFromUrl, ); } - }, [AALayerInUrl, dispatch, map, removeLayerFromUrl, tabValue]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tabValue]); // Reset selected boundaries when tab changes from Alerts React.useEffect(() => { From 9577354f041240517fd56bd7f27eca24cb70f2a5 Mon Sep 17 00:00:00 2001 From: donia benharara Date: Fri, 20 Dec 2024 12:20:36 +0100 Subject: [PATCH 27/30] fix lint --- .../LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts index 53cb70470..d587d72c1 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts @@ -97,6 +97,7 @@ export function useAnticipatoryAction( ); updateHistory(getUrlKey(layer), updatedUrl); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { From bdc3ab781b7fceaa3b4659a5ffb43be9f15839ba Mon Sep 17 00:00:00 2001 From: donia benharara Date: Fri, 20 Dec 2024 17:18:57 +0100 Subject: [PATCH 28/30] fix glitch due to over rendering --- .../AnticipatoryActionDroughtPanel/index.tsx | 20 +--------------- .../AnticipatoryActionStormPanel/index.tsx | 16 ++++--------- .../useAnticipatoryAction.ts | 24 +++++++++++++++---- .../src/context/anticipatoryAction/config.ts | 4 ++++ 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx index 334321194..d030be0b2 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/AnticipatoryActionDroughtPanel/index.tsx @@ -30,12 +30,7 @@ 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 { AnticipatoryAction, PanelSize } from 'config/types'; import { StyledCheckboxLabel, StyledRadioLabel } from './utils'; import { StyledSelect } from '../utils'; @@ -66,9 +61,7 @@ function AnticipatoryActionDroughtPanel() { const classes = useStyles(); const dispatch = useDispatch(); const { t } = useSafeTranslation(); - const { AAData, AAAvailableDates } = useAnticipatoryAction( - AnticipatoryAction.drought, - ); + const { AAData } = useAnticipatoryAction(AnticipatoryAction.drought); const monitoredDistricts = useSelector(AAMonitoredDistrictsSelector); const selectedDistrict = useSelector(AASelectedDistrictSelector); const { categories: categoryFilters, selectedIndex } = @@ -98,17 +91,6 @@ function AnticipatoryActionDroughtPanel() { 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]); - return (
( 'forecast', @@ -47,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 (
= @@ -50,15 +59,17 @@ export function useAnticipatoryAction( 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()); - }, [dispatch, loadAAData]); + }, []); useEffect(() => { - if (AAAvailableDates) { + if (AAAvailableDates && selectedDate) { const combinedAvailableDates = isWindowedDates(AAAvailableDates) ? getAAAvailableDatesCombined(AAAvailableDates) : AAAvailableDates; @@ -69,8 +80,13 @@ export function useAnticipatoryAction( }), { ...serverAvailableDates }, ); + + const queryDate = getRequestDate(combinedAvailableDates, selectedDate); + const date = getFormattedDate(queryDate, DateFormat.Default) as string; + dispatch(updateLayersCapabilities(updatedCapabilities)); dispatch(updateDateRange(updatedCapabilities)); + dispatch(setFilters({ selectedDate: date })); } // To avoid an infinite loop, we only want to run this effect when AAAvailableDates changes. // eslint-disable-next-line react-hooks/exhaustive-deps 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, }, }; From 53a1f23f4b2b141e282f624a6a7c7283a79d25f8 Mon Sep 17 00:00:00 2001 From: donia benharara Date: Fri, 20 Dec 2024 17:47:14 +0100 Subject: [PATCH 29/30] fix lint --- .../LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts index c606baa6c..0c41284e3 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts @@ -66,6 +66,7 @@ export function useAnticipatoryAction( // Load data when component mounts useEffect(() => { dispatch(loadAAData()); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { From 81847b0138cced200f49e320d93fcb905720b554 Mon Sep 17 00:00:00 2001 From: donia benharara Date: Sat, 21 Dec 2024 13:48:37 +0100 Subject: [PATCH 30/30] fix dates load --- .../useAnticipatoryAction.ts | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts index 0c41284e3..b4ab9c0eb 100644 --- a/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts +++ b/frontend/src/components/MapView/LeftPanel/AnticipatoryActionPanel/useAnticipatoryAction.ts @@ -70,28 +70,31 @@ export function useAnticipatoryAction( }, []); useEffect(() => { - if (AAAvailableDates && selectedDate) { + if (AAAvailableDates) { const combinedAvailableDates = isWindowedDates(AAAvailableDates) ? getAAAvailableDatesCombined(AAAvailableDates) : AAAvailableDates; - const updatedCapabilities = AALayerIds.reduce( - (acc, layerId) => ({ - ...acc, - [layerId]: combinedAvailableDates, - }), - { ...serverAvailableDates }, - ); - const queryDate = getRequestDate(combinedAvailableDates, selectedDate); - const date = getFormattedDate(queryDate, DateFormat.Default) as string; + if (!selectedDate) { + const updatedCapabilities = AALayerIds.reduce( + (acc, layerId) => ({ + ...acc, + [layerId]: combinedAvailableDates, + }), + { ...serverAvailableDates }, + ); - dispatch(updateLayersCapabilities(updatedCapabilities)); - dispatch(updateDateRange(updatedCapabilities)); - dispatch(setFilters({ selectedDate: date })); + dispatch(updateLayersCapabilities(updatedCapabilities)); + dispatch(updateDateRange(updatedCapabilities)); + } else { + const queryDate = getRequestDate(combinedAvailableDates, selectedDate); + const date = getFormattedDate(queryDate, DateFormat.Default) as string; + dispatch(setFilters({ selectedDate: date })); + } } - // To avoid an infinite loop, we only want to run this effect when AAAvailableDates changes. + // eslint-disable-next-line react-hooks/exhaustive-deps - }, [AAAvailableDates]); + }, [AAAvailableDates, selectedDate]); // Handle URL updates when mounting/unmounting useEffect(() => { @@ -113,6 +116,7 @@ export function useAnticipatoryAction( layer, ); updateHistory(getUrlKey(layer), updatedUrl); + dispatch(updateDateRange({ startDate: undefined })); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []);