From 4fe6a7fefe11d0e1d552120dbd1632c96cfb6094 Mon Sep 17 00:00:00 2001 From: furyhawk Date: Wed, 28 May 2025 19:56:50 +0800 Subject: [PATCH] refactor: Add view mode toggle and map display for TwoHourForecast component --- frontend/src/routes/_layout/weather-hub.tsx | 207 +++++++++++++++----- 1 file changed, 162 insertions(+), 45 deletions(-) diff --git a/frontend/src/routes/_layout/weather-hub.tsx b/frontend/src/routes/_layout/weather-hub.tsx index ce4fa54..a90473b 100644 --- a/frontend/src/routes/_layout/weather-hub.tsx +++ b/frontend/src/routes/_layout/weather-hub.tsx @@ -151,6 +151,7 @@ function getWeatherIcon(forecast: string): string { function TwoHourForecast() { const [selectedArea, setSelectedArea] = useState(["All Areas"]) + const [viewMode, setViewMode] = useState<'grid' | 'map'>('grid') const { data, isLoading, error } = useQuery>({ queryKey: ["weather", "two-hour-forecast"], @@ -167,7 +168,7 @@ function TwoHourForecast() { const forecasts = data.data.items[0]?.forecasts || [] const validPeriod = data.data.items[0]?.valid_period - // const areaMetadata = data.data.area_metadata || [] // Unused variable + const areaMetadata = data.data.area_metadata || [] // Get unique areas for dropdown const areaNames = forecasts.map((f: Forecast) => f.area) @@ -178,6 +179,56 @@ function TwoHourForecast() { ? forecasts : forecasts.filter((forecast: Forecast) => selectedArea.includes(forecast.area)) + // Create weather icon for map markers + const createWeatherIcon = (forecast: string, area: string) => { + let color = "#718096"; // Default gray color + if (forecast.includes("Fair")) color = "#ECC94B"; // yellow + if (forecast.includes("Cloud") || forecast.includes("Hazy")) color = "#A0AEC0"; // gray + if (forecast.includes("Rain") || forecast.includes("Shower")) color = "#4299E1"; // blue + if (forecast.includes("Thunder")) color = "#9F7AEA"; // purple + if (forecast.includes("Wind")) color = "#38B2AC"; // teal + + const iconHtml = ` +
+
${getWeatherIcon(forecast)}
+
${area}
+
${forecast}
+
+ `; + + return L.divIcon({ + html: iconHtml, + className: 'custom-weather-marker', + iconSize: [80, 60], + iconAnchor: [40, 60], + popupAnchor: [0, -60] + }); + }; + + // Filter forecasts with location data for map view + const forecastsWithLocation = forecasts.map(forecast => { + const metadata = areaMetadata.find(area => area.name === forecast.area); + return { + ...forecast, + location: metadata?.label_location + }; + }).filter(f => f.location && (selectedArea.includes("All Areas") || selectedArea.includes(f.area))); + return ( {validPeriod && ( @@ -185,53 +236,119 @@ function TwoHourForecast() { Valid: {new Date(validPeriod.start).toLocaleTimeString()} - {new Date(validPeriod.end).toLocaleTimeString()} - ({ value: area, label: area })) - })} - onValueChange={(details) => { - // Handle array of values for multi-select - if (Array.isArray(details)) { - setSelectedArea(details); - } else if (typeof details === 'string') { - setSelectedArea([details]); - } else if (details && typeof (details as any).value === 'string') { - setSelectedArea([(details as any).value]); - } - }} - width="200px" - > - - - - - - - {uniqueAreas.map(area => ( - - {area} - - ))} - - - + + {/* View Mode Toggle */} + + setViewMode('grid')} + px={3} + py={1} + > + Grid + + setViewMode('map')} + px={3} + py={1} + > + Map + + + + ({ value: area, label: area })) + })} + onValueChange={(details) => { + // Handle array of values for multi-select + if (Array.isArray(details)) { + setSelectedArea(details); + } else if (typeof details === 'string') { + setSelectedArea([details]); + } else if (details && typeof (details as any).value === 'string') { + setSelectedArea([(details as any).value]); + } + }} + width="200px" + > + + + + + + {uniqueAreas.map(area => ( + + {area} + + ))} + + + + )} - {/* Changed spacing to gap */} - {filteredForecasts.map((forecast: Forecast) => ( - - - {/* Changed spacing to gap */} - {getWeatherIcon(forecast.forecast)} - {forecast.area} - - {forecast.forecast} - - - ))} - + {viewMode === 'grid' && ( + + {filteredForecasts.map((forecast: Forecast) => ( + + + + {getWeatherIcon(forecast.forecast)} + {forecast.area} + + {forecast.forecast} + + + ))} + + )} + + {viewMode === 'map' && ( + + {forecastsWithLocation.length > 0 ? ( + + + {forecastsWithLocation.map((forecast, index) => ( + + ))} + + ) : ( + + + No location data available for map view + + Area metadata is required to display forecasts on the map + + + + )} + + )} ) }