feat: add wind speed and a same-day hive-inspection verdict to weather

Temperature and rain were already tracked, but wind matters just as
much for whether it's safe/sensible to open a hive - bees get more
defensive and frames are harder to handle in wind. Adds
wind_speed_10m_max to the Open-Meteo request and a derived
inspectionSuitable flag per day (>=15C, <=1mm rain, <=25km/h wind;
null when a value is missing rather than guessing). The apiary weather
card now shows a clear "good/not good for a Durchsicht today" banner
plus wind alongside each day's forecast.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-05 17:54:31 +02:00
1 parent 1f67cd5548
commit c4f970f071
4 files changed
+150 -29

No files matched your search

@@ -1,6 +1,6 @@
"use client";
import { CloudSun } from "lucide-react";
import { CloudSun, Wind, CheckCircle2, XCircle } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { fetchApiaryWeather, fetchTrachtprognose } from "@/lib/weather/api";
import { TrachtprognoseRating } from "@/lib/weather/types";
@@ -73,6 +73,7 @@ export function WeatherCard({
const today = new Date().toISOString().slice(0, 10);
const forecastDays = (weather?.daily ?? []).filter((day) => day.date >= today).slice(0, 7);
const todaySuitability = forecastDays[0]?.inspectionSuitable ?? null;
return (
<Card>
@@ -104,26 +105,54 @@ export function WeatherCard({
{weatherLoading ? (
<p className="text-sm text-muted-foreground">Wetterdaten laden…</p>
) : forecastDays.length > 0 ? (
<div className="grid grid-cols-3 gap-2 border-t border-border/50 pt-3 sm:grid-cols-7">
{forecastDays.map((day) => (
<div key={day.date} className="flex flex-col items-center gap-0.5 text-center">
<span className="text-xs text-muted-foreground">
{formatDayLabel(day.date)}
<>
{todaySuitability !== null ? (
<div
className={`flex items-center gap-2 rounded-lg border p-2.5 text-sm ${
todaySuitability
? "border-primary/30 bg-primary/5 text-primary"
: "border-warning/40 bg-warning/10 text-warning-foreground"
}`}
>
{todaySuitability ? (
<CheckCircle2 className="size-4.5 shrink-0" />
) : (
<XCircle className="size-4.5 shrink-0" />
)}
<span>
{todaySuitability
? "Heute gut für eine Durchsicht geeignet."
: "Heute eher ungeeignet für eine Durchsicht (zu kalt, nass oder windig)."}
</span>
<span className="text-sm font-medium">
{day.tempMaxC !== null ? `${Math.round(day.tempMaxC)}°` : "–"}
</span>
<span className="text-xs text-muted-foreground">
{day.tempMinC !== null ? `${Math.round(day.tempMinC)}°` : "–"}
</span>
{day.precipitationMm !== null && day.precipitationMm > 0 ? (
<span className="text-xs text-primary">
{day.precipitationMm.toFixed(0)} mm
</span>
) : null}
</div>
))}
</div>
) : null}
<div className="grid grid-cols-3 gap-2 border-t border-border/50 pt-3 sm:grid-cols-7">
{forecastDays.map((day) => (
<div key={day.date} className="flex flex-col items-center gap-0.5 text-center">
<span className="text-xs text-muted-foreground">
{formatDayLabel(day.date)}
</span>
<span className="text-sm font-medium">
{day.tempMaxC !== null ? `${Math.round(day.tempMaxC)}°` : "–"}
</span>
<span className="text-xs text-muted-foreground">
{day.tempMinC !== null ? `${Math.round(day.tempMinC)}°` : "–"}
</span>
{day.precipitationMm !== null && day.precipitationMm > 0 ? (
<span className="text-xs text-primary">
{day.precipitationMm.toFixed(0)} mm
</span>
) : null}
{day.windSpeedMaxKmh !== null ? (
<span className="flex items-center gap-0.5 text-xs text-muted-foreground">
<Wind className="size-3" />
{Math.round(day.windSpeedMaxKmh)}
</span>
) : null}
</div>
))}
</div>
</>
) : null}
</CardContent>
</Card>
@@ -3,6 +3,8 @@ export interface DailyWeather {
tempMaxC: number | null;
tempMinC: number | null;
precipitationMm: number | null;
windSpeedMaxKmh: number | null;
inspectionSuitable: boolean | null;
}
export interface ApiaryWeather {