feat: add temperature unit preference

This commit is contained in:
zv
2026-06-13 21:05:01 +02:00
parent 66ead03f49
commit cb113714f9
24 changed files with 150 additions and 51 deletions

View File

@@ -9,16 +9,17 @@ import { useWeatherStore } from "@/lib/store";
export function CurrentConditionsCard({ station }: { station: SynopStation }) {
const { language, t } = useI18n();
const temperatureUnit = useWeatherStore((state) => state.temperatureUnit);
const windSpeedUnit = useWeatherStore((state) => state.windSpeedUnit);
const feelsLike = calculateFeelsLike(station.temperature, station.humidity, station.windSpeed);
const metrics = [
{ icon: Thermometer, label: t("weather.feelsLike"), value: formatTemperature(feelsLike, language), detail: t("weather.feelsLikeDetail") },
{ icon: Thermometer, label: t("weather.feelsLike"), value: formatTemperature(feelsLike, language, temperatureUnit), detail: t("weather.feelsLikeDetail") },
{ icon: Droplets, label: t("weather.humidity"), value: formatHumidity(station.humidity, language), detail: t("weather.humidityDetail") },
{ icon: Gauge, label: t("weather.pressure"), value: formatPressure(station.pressure, language), detail: t("weather.pressureDetail") },
{ icon: Wind, label: t("weather.windSpeed"), value: formatWind(station.windSpeed, null, language, windSpeedUnit), detail: t("weather.windSpeedDetail") },
{ icon: Navigation, label: t("weather.windDirection"), value: station.windDirection === null ? t("common.noData") : `${station.windDirection}° ${getWindDirection(station.windDirection)}`, detail: t("weather.windDirectionDetail") },
{ icon: Umbrella, label: t("weather.rainfallTotal"), value: formatRainfall(station.rainfall, language), detail: t("weather.rainfallDetail") },
{ icon: CloudSun, label: t("weather.airTemperature"), value: formatTemperature(station.temperature, language), detail: t("weather.temperatureDetail") },
{ icon: CloudSun, label: t("weather.airTemperature"), value: formatTemperature(station.temperature, language, temperatureUnit), detail: t("weather.temperatureDetail") },
];
return (
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">

View File

@@ -14,6 +14,7 @@ export function FeaturedStationsSection({ stations }: { stations: SynopStation[]
const { language, t } = useI18n();
const selectedStationId = useWeatherStore((state) => state.selectedStationId);
const selectStation = useWeatherStore((state) => state.selectStation);
const temperatureUnit = useWeatherStore((state) => state.temperatureUnit);
const featured = featuredNames.flatMap((name) => stations.find((station) => station.name === name) ?? []);
return (
<section className="space-y-3">
@@ -33,7 +34,7 @@ export function FeaturedStationsSection({ stations }: { stations: SynopStation[]
>
<span className="min-w-0">
<span className="block truncate text-xs font-medium text-muted">{station.name}</span>
<span className="mt-1 block text-xl font-semibold tracking-tight">{formatTemperature(station.temperature, language)}</span>
<span className="mt-1 block text-xl font-semibold tracking-tight">{formatTemperature(station.temperature, language, temperatureUnit)}</span>
</span>
<WeatherIcon mood={getWeatherMoodFromData(station)} className="size-6 shrink-0 text-accent" />
</button>

View File

@@ -17,6 +17,7 @@ export function StationCard({ station, index = 0 }: { station: SynopStation; ind
const favorites = useWeatherStore((state) => state.favorites);
const toggleFavorite = useWeatherStore((state) => state.toggleFavorite);
const selectStation = useWeatherStore((state) => state.selectStation);
const temperatureUnit = useWeatherStore((state) => state.temperatureUnit);
const windSpeedUnit = useWeatherStore((state) => state.windSpeedUnit);
const favorite = favorites.includes(station.id);
const mood = getWeatherMoodFromData(station);
@@ -27,7 +28,7 @@ export function StationCard({ station, index = 0 }: { station: SynopStation; ind
<div className="flex items-start justify-between gap-2">
<Link href={`/station/${station.id}`} onClick={() => selectStation(station.id)} className="min-w-0 flex-1 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent">
<p className="truncate text-sm font-semibold">{station.name}</p>
<p className="mt-3 text-4xl font-light tracking-[-0.08em]">{formatTemperature(station.temperature, language)}</p>
<p className="mt-3 text-4xl font-light tracking-[-0.08em]">{formatTemperature(station.temperature, language, temperatureUnit)}</p>
</Link>
<div className="flex flex-col items-end gap-2">
<WeatherIcon mood={mood} className="size-9 text-accent" />

View File

@@ -44,6 +44,7 @@ function readDevWeatherEffectOverride(): DevWeatherEffectOverride | null {
export function WeatherHero({ station, currentWeather, currentWeatherLoading = false, locationName, distanceKm }: { station: SynopStation; currentWeather?: ImgwCurrentWeather | null; currentWeatherLoading?: boolean; locationName?: string; distanceKm?: number }) {
const { language, t } = useI18n();
const temperatureUnit = useWeatherStore((state) => state.temperatureUnit);
const windSpeedUnit = useWeatherStore((state) => state.windSpeedUnit);
const [devEffectOverride, setDevEffectOverride] = useState<DevWeatherEffectOverride | null>(null);
const displayedLocationName = locationName ?? station.name;
@@ -121,10 +122,10 @@ export function WeatherHero({ station, currentWeather, currentWeatherLoading = f
<div className="mt-8 flex items-end justify-between gap-3 sm:mt-10">
<div>
<div className="text-[5.8rem] font-semibold leading-[0.85] tracking-[-0.1em] sm:text-[8rem]">
{formatTemperature(displayedStation.temperature, language)}
{formatTemperature(displayedStation.temperature, language, temperatureUnit)}
</div>
<p className="mt-5 text-xl font-medium tracking-tight sm:text-2xl">{getWeatherDescription(displayedStation, language, currentWeather?.condition)}</p>
<p className="mt-1 text-sm text-muted">{t("weather.feelsLike")} {formatTemperature(feelsLike, language)} · {t("weather.measurement")} {formatDateTime(displayedStation.measuredAt, language)}</p>
<p className="mt-1 text-sm text-muted">{t("weather.feelsLike")} {formatTemperature(feelsLike, language, temperatureUnit)} · {t("weather.measurement")} {formatDateTime(displayedStation.measuredAt, language)}</p>
</div>
<WeatherIcon mood={mood} condition={currentWeather?.condition} className="mb-4 size-20 text-accent sm:size-28" />
</div>