refactor: Add WeatherMap component with Leaflet integration for displaying air temperature readings

This commit is contained in:
2025-05-28 19:31:14 +08:00
parent a7279dad1f
commit a89174e360
3 changed files with 138 additions and 0 deletions
@@ -20,6 +20,8 @@ import {
import { createFileRoute } from "@tanstack/react-router"
import { useQuery } from "@tanstack/react-query"
import { createListCollection } from "@ark-ui/react"
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import { WeatherService } from "@/client/sdk.gen"
@@ -502,6 +504,57 @@ function WindDirection() {
)
}
function WeatherMap() {
const { data, isLoading, error } = useQuery<ApiNestedResponse<AirTemperaturePayload>>({
queryKey: ["weather", "air-temperature"],
queryFn: () => WeatherService.getAirTemperature() as Promise<ApiNestedResponse<AirTemperaturePayload>>,
refetchInterval: 1000 * 60 * 30, // Refetch every 30 minutes
});
if (isLoading) return <Spinner />;
if (error || !data?.data) return <Text color="red.500">Error loading map data</Text>;
const { stations = [], readings = [] } = data.data;
if (stations.length === 0 || readings.length === 0) {
return <Text>No map data available</Text>;
}
// Get the latest reading
const latestReading = readings[0];
if (!latestReading || !latestReading.data) {
return <Text>No map data available</Text>;
}
// Create a map of station IDs to station details
const stationMap = new Map(stations.map(station => [station.id, station]));
return (
<MapContainer center={[1.3521, 103.8198]} zoom={11} style={{ height: "500px", width: "100%" }}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"
/>
{latestReading.data.map((reading) => {
const station = stationMap.get(reading.stationId);
if (!station) return null;
return (
<Marker
key={reading.stationId}
position={[station.location.latitude, station.location.longitude]}
>
<Popup>
<Text fontWeight="bold">{station.name}</Text>
<Text>Temperature: {reading.value?.toFixed(1)}°C</Text>
</Popup>
</Marker>
);
})}
</MapContainer>
);
}
// Define tab values for namespaced Tabs
const tabValues = {
twoHour: "twoHourForecast",
@@ -509,6 +562,7 @@ const tabValues = {
fourDay: "fourDayOutlook",
stats: "weatherStatistics",
windDir: "windDirection",
weatherMap: "weatherMap",
}
function WeatherHub() {
@@ -529,6 +583,7 @@ function WeatherHub() {
<ChakraTabs.Trigger value={tabValues.fourDay}>4-Day Outlook</ChakraTabs.Trigger>
<ChakraTabs.Trigger value={tabValues.stats}>Statistics</ChakraTabs.Trigger>
<ChakraTabs.Trigger value={tabValues.windDir}>Wind Direction</ChakraTabs.Trigger>
<ChakraTabs.Trigger value={tabValues.weatherMap}>Weather Map</ChakraTabs.Trigger>
</ChakraTabs.List>
<ChakraTabs.ContentGroup>
@@ -547,6 +602,9 @@ function WeatherHub() {
<ChakraTabs.Content value={tabValues.windDir}>
<WindDirection />
</ChakraTabs.Content>
<ChakraTabs.Content value={tabValues.weatherMap}>
<WeatherMap />
</ChakraTabs.Content>
</ChakraTabs.ContentGroup>
</ChakraTabs.Root>
</Container>
+74
View File
@@ -0,0 +1,74 @@
{
"name": "home_stack",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"leaflet": "^1.9.4",
"react-leaflet": "^5.0.0"
}
},
"node_modules/@react-leaflet/core": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz",
"integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==",
"license": "Hippocratic-2.1",
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
},
"node_modules/leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause"
},
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
"peerDependencies": {
"react": "^19.1.0"
}
},
"node_modules/react-leaflet": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz",
"integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==",
"license": "Hippocratic-2.1",
"dependencies": {
"@react-leaflet/core": "^3.0.0"
},
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
},
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
"license": "MIT",
"peer": true
}
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"dependencies": {
"leaflet": "^1.9.4",
"react-leaflet": "^5.0.0"
}
}