mirror of
https://github.com/furyhawk/home_stack.git
synced 2026-07-21 02:06:47 +00:00
refactor: Add WeatherMap component with Leaflet integration for displaying air temperature readings
This commit is contained in:
@@ -20,6 +20,8 @@ import {
|
|||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { createListCollection } from "@ark-ui/react"
|
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"
|
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="© <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
|
// Define tab values for namespaced Tabs
|
||||||
const tabValues = {
|
const tabValues = {
|
||||||
twoHour: "twoHourForecast",
|
twoHour: "twoHourForecast",
|
||||||
@@ -509,6 +562,7 @@ const tabValues = {
|
|||||||
fourDay: "fourDayOutlook",
|
fourDay: "fourDayOutlook",
|
||||||
stats: "weatherStatistics",
|
stats: "weatherStatistics",
|
||||||
windDir: "windDirection",
|
windDir: "windDirection",
|
||||||
|
weatherMap: "weatherMap",
|
||||||
}
|
}
|
||||||
|
|
||||||
function WeatherHub() {
|
function WeatherHub() {
|
||||||
@@ -529,6 +583,7 @@ function WeatherHub() {
|
|||||||
<ChakraTabs.Trigger value={tabValues.fourDay}>4-Day Outlook</ChakraTabs.Trigger>
|
<ChakraTabs.Trigger value={tabValues.fourDay}>4-Day Outlook</ChakraTabs.Trigger>
|
||||||
<ChakraTabs.Trigger value={tabValues.stats}>Statistics</ChakraTabs.Trigger>
|
<ChakraTabs.Trigger value={tabValues.stats}>Statistics</ChakraTabs.Trigger>
|
||||||
<ChakraTabs.Trigger value={tabValues.windDir}>Wind Direction</ChakraTabs.Trigger>
|
<ChakraTabs.Trigger value={tabValues.windDir}>Wind Direction</ChakraTabs.Trigger>
|
||||||
|
<ChakraTabs.Trigger value={tabValues.weatherMap}>Weather Map</ChakraTabs.Trigger>
|
||||||
</ChakraTabs.List>
|
</ChakraTabs.List>
|
||||||
|
|
||||||
<ChakraTabs.ContentGroup>
|
<ChakraTabs.ContentGroup>
|
||||||
@@ -547,6 +602,9 @@ function WeatherHub() {
|
|||||||
<ChakraTabs.Content value={tabValues.windDir}>
|
<ChakraTabs.Content value={tabValues.windDir}>
|
||||||
<WindDirection />
|
<WindDirection />
|
||||||
</ChakraTabs.Content>
|
</ChakraTabs.Content>
|
||||||
|
<ChakraTabs.Content value={tabValues.weatherMap}>
|
||||||
|
<WeatherMap />
|
||||||
|
</ChakraTabs.Content>
|
||||||
</ChakraTabs.ContentGroup>
|
</ChakraTabs.ContentGroup>
|
||||||
</ChakraTabs.Root>
|
</ChakraTabs.Root>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
Generated
+74
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
|
"react-leaflet": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user