Files
wtr/components/charts/snapshot-chart.tsx
zv 91acdb39b8
Some checks failed
CI / Lint, test, typecheck and build (push) Has been cancelled
feat: add weather unit preferences
2026-07-04 20:16:11 +02:00

99 lines
3.6 KiB
TypeScript

"use client";
import { Bar, BarChart, Cell, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
import type { SynopStation } from "@/types/imgw";
import { ChartTooltip } from "@/components/charts/chart-tooltip";
import { Card } from "@/components/ui/card";
import { useI18n } from "@/lib/i18n";
import { useWeatherStore } from "@/lib/store";
import {
convertPrecipitation,
convertWindSpeed,
convertWindSpeedToBeaufort,
getPrecipitationUnitLabel,
getWindSpeedUnitLabel,
} from "@/lib/weather-utils";
const INITIAL_CHART_DIMENSION = { width: 1, height: 1 };
const SNAPSHOT_COLORS = ["hsl(var(--chart-temperature))", "hsl(var(--chart-feels-like))", "hsl(var(--chart-rainfall))"];
export function SnapshotChart({ station }: { station: SynopStation }) {
const { t } = useI18n();
const windSpeedUnit = useWeatherStore((state) => state.windSpeedUnit);
const precipitationUnit = useWeatherStore((state) => state.precipitationUnit);
const windDigits = windSpeedUnit === "ms" ? 1 : 0;
const windSpeed =
station.windSpeed === null
? null
: windSpeedUnit === "bft"
? convertWindSpeedToBeaufort(station.windSpeed)
: Number(convertWindSpeed(station.windSpeed, windSpeedUnit).toFixed(windDigits));
const windMax = windSpeedUnit === "ms" ? 20 : windSpeedUnit === "kmh" ? 72 : windSpeedUnit === "bft" ? 12 : 45;
const rainfall =
station.rainfall === null ? null : Number(convertPrecipitation(station.rainfall, precipitationUnit).toFixed(2));
const rows = [
{ name: t("weather.humidity"), value: station.humidity, unit: "%", max: 100, color: SNAPSHOT_COLORS[0] },
{
name: t("weather.wind"),
value: windSpeed,
unit: getWindSpeedUnitLabel(windSpeedUnit),
max: windMax,
color: SNAPSHOT_COLORS[1],
},
{
name: t("weather.rainfall"),
value: rainfall,
unit: getPrecipitationUnitLabel(precipitationUnit),
max: convertPrecipitation(30, precipitationUnit),
color: SNAPSHOT_COLORS[2],
},
]
.filter((row) => row.value !== null)
.map((row) => ({ ...row, normalized: Math.min(100, ((row.value ?? 0) / row.max) * 100) }));
return (
<Card className="p-5">
<p className="section-kicker">{t("snapshot.label")}</p>
<h2 className="mt-2 text-xl font-semibold tracking-tight">{t("snapshot.title")}</h2>
<p className="mt-1 text-sm leading-6 text-muted">{t("snapshot.description")}</p>
<div className="mt-5 h-52 min-w-0">
<ResponsiveContainer
width="100%"
height="100%"
minWidth={0}
minHeight={0}
initialDimension={INITIAL_CHART_DIMENSION}
>
<BarChart data={rows} layout="vertical" margin={{ left: 0, right: 16 }}>
<XAxis type="number" hide domain={[0, 100]} />
<YAxis
type="category"
dataKey="name"
width={86}
axisLine={false}
tickLine={false}
tick={{ fill: "currentColor", fontSize: 12 }}
/>
<Tooltip
cursor={{ fill: "hsl(var(--border) / 0.22)" }}
content={
<ChartTooltip
valueFormatter={(entry) => {
const row = entry.payload;
return `${row?.value ?? "—"} ${row?.unit ?? ""}`.trim();
}}
/>
}
/>
<Bar dataKey="normalized" radius={[0, 8, 8, 0]} barSize={14}>
{rows.map((row) => (
<Cell fill={row.color} key={row.name} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</Card>
);
}