fix: show rain icon for likely hourly precipitation
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m55s
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m55s
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
formatForecastRainfall,
|
formatForecastRainfall,
|
||||||
formatForecastTemperature,
|
formatForecastTemperature,
|
||||||
formatForecastWind,
|
formatForecastWind,
|
||||||
|
getEffectiveHourlyWeatherCode,
|
||||||
getForecastCondition,
|
getForecastCondition,
|
||||||
isForecastNight,
|
isForecastNight,
|
||||||
} from "@/lib/forecast-utils";
|
} from "@/lib/forecast-utils";
|
||||||
@@ -150,21 +151,24 @@ export function DayForecastModal({
|
|||||||
<h3 className="text-sm font-semibold">{t("forecast.hourlyForDay")}</h3>
|
<h3 className="text-sm font-semibold">{t("forecast.hourlyForDay")}</h3>
|
||||||
<div className="weather-scrollbar -mx-4 mt-4 overflow-x-auto px-4 pb-2 sm:-mx-5 sm:px-5">
|
<div className="weather-scrollbar -mx-4 mt-4 overflow-x-auto px-4 pb-2 sm:-mx-5 sm:px-5">
|
||||||
<ul className="flex min-w-max gap-2">
|
<ul className="flex min-w-max gap-2">
|
||||||
{hours.map((hour) => (
|
{hours.map((hour) => {
|
||||||
<li
|
const weatherCode = getEffectiveHourlyWeatherCode(hour);
|
||||||
key={hour.time}
|
return (
|
||||||
className="w-[5.2rem] rounded-card border border-border/60 bg-surface-muted/55 px-2 py-3 text-center"
|
<li
|
||||||
title={getForecastCondition(hour.weatherCode, language)}
|
key={hour.time}
|
||||||
>
|
className="w-[5.2rem] rounded-card border border-border/60 bg-surface-muted/55 px-2 py-3 text-center"
|
||||||
<p className="text-xs font-medium text-muted">{formatHour(hour.time)}</p>
|
title={getForecastCondition(weatherCode, language)}
|
||||||
<ForecastIcon code={hour.weatherCode} isNight={isForecastNight(hour.time, day)} className="mx-auto my-3 size-6 text-accent" />
|
>
|
||||||
<p className="text-lg font-semibold tracking-tight">{formatForecastTemperature(hour.temperature, language, temperatureUnit)}</p>
|
<p className="text-xs font-medium text-muted">{formatHour(hour.time)}</p>
|
||||||
<p className="mt-2 flex items-center justify-center gap-1 text-[0.66rem] text-accent">
|
<ForecastIcon code={weatherCode} isNight={isForecastNight(hour.time, day)} className="mx-auto my-3 size-6 text-accent" />
|
||||||
<Droplets className="size-3" />
|
<p className="text-lg font-semibold tracking-tight">{formatForecastTemperature(hour.temperature, language, temperatureUnit)}</p>
|
||||||
{hour.precipitationProbability === null ? "—" : `${hour.precipitationProbability}%`}
|
<p className="mt-2 flex items-center justify-center gap-1 text-[0.66rem] text-accent">
|
||||||
</p>
|
<Droplets className="size-3" />
|
||||||
</li>
|
{hour.precipitationProbability === null ? "—" : `${hour.precipitationProbability}%`}
|
||||||
))}
|
</p>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
formatForecastTemperature,
|
formatForecastTemperature,
|
||||||
formatForecastWind,
|
formatForecastWind,
|
||||||
getDailyForecastForHour,
|
getDailyForecastForHour,
|
||||||
|
getEffectiveHourlyWeatherCode,
|
||||||
getForecastCondition,
|
getForecastCondition,
|
||||||
getHourlyForecastForDay,
|
getHourlyForecastForDay,
|
||||||
getUpcomingHourlyForecast,
|
getUpcomingHourlyForecast,
|
||||||
@@ -157,6 +158,7 @@ export function ForecastPanel({ latitude, longitude, locationName }: { latitude?
|
|||||||
<ul className="flex min-w-max gap-2">
|
<ul className="flex min-w-max gap-2">
|
||||||
{upcomingHours.map((hour, index) => {
|
{upcomingHours.map((hour, index) => {
|
||||||
const day = getDailyForecastForHour(forecast.daily, hour.time);
|
const day = getDailyForecastForHour(forecast.daily, hour.time);
|
||||||
|
const weatherCode = getEffectiveHourlyWeatherCode(hour);
|
||||||
return (
|
return (
|
||||||
<motion.li
|
<motion.li
|
||||||
key={hour.time}
|
key={hour.time}
|
||||||
@@ -164,10 +166,10 @@ export function ForecastPanel({ latitude, longitude, locationName }: { latitude?
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: Math.min(index * 0.018, 0.3), duration: 0.25 }}
|
transition={{ delay: Math.min(index * 0.018, 0.3), duration: 0.25 }}
|
||||||
className="w-[4.6rem] rounded-card border border-border/60 bg-surface-muted/55 px-2 py-3 text-center lg:w-[5.5rem] lg:py-4"
|
className="w-[4.6rem] rounded-card border border-border/60 bg-surface-muted/55 px-2 py-3 text-center lg:w-[5.5rem] lg:py-4"
|
||||||
title={getForecastCondition(hour.weatherCode, language)}
|
title={getForecastCondition(weatherCode, language)}
|
||||||
>
|
>
|
||||||
<p className="text-xs font-medium text-muted">{formatHour(hour.time)}</p>
|
<p className="text-xs font-medium text-muted">{formatHour(hour.time)}</p>
|
||||||
<ForecastIcon code={hour.weatherCode} isNight={isForecastNight(hour.time, day)} className="mx-auto my-3 size-6 text-accent" />
|
<ForecastIcon code={weatherCode} isNight={isForecastNight(hour.time, day)} className="mx-auto my-3 size-6 text-accent" />
|
||||||
<p className="text-lg font-semibold tracking-tight">{formatForecastTemperature(hour.temperature, language, temperatureUnit)}</p>
|
<p className="text-lg font-semibold tracking-tight">{formatForecastTemperature(hour.temperature, language, temperatureUnit)}</p>
|
||||||
<p className="mt-2 flex items-center justify-center gap-1 text-[0.66rem] text-accent"><Droplets className="size-3" />{hour.precipitationProbability === null ? "—" : `${hour.precipitationProbability}%`}</p>
|
<p className="mt-2 flex items-center justify-center gap-1 text-[0.66rem] text-accent"><Droplets className="size-3" />{hour.precipitationProbability === null ? "—" : `${hour.precipitationProbability}%`}</p>
|
||||||
<div className="mt-3 hidden space-y-1.5 border-t border-border/65 pt-3 text-[0.66rem] text-muted lg:block">
|
<div className="mt-3 hidden space-y-1.5 border-t border-border/65 pt-3 text-[0.66rem] text-muted lg:block">
|
||||||
|
|||||||
@@ -20,6 +20,17 @@ export function getForecastCondition(code: number | null, language: Language) {
|
|||||||
return translate(language, getForecastConditionKey(code));
|
return translate(language, getForecastConditionKey(code));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDryWeatherCode(code: number | null) {
|
||||||
|
return code === null || (code >= 0 && code <= 3) || code === 45 || code === 48;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEffectiveHourlyWeatherCode(hour: Pick<HourlyForecast, "weatherCode" | "precipitation" | "precipitationProbability">) {
|
||||||
|
const hasMeasuredPrecipitation = (hour.precipitation ?? 0) > 0;
|
||||||
|
const hasLikelyPrecipitation = (hour.precipitationProbability ?? 0) >= 70;
|
||||||
|
if (isDryWeatherCode(hour.weatherCode) && (hasMeasuredPrecipitation || hasLikelyPrecipitation)) return 61;
|
||||||
|
return hour.weatherCode;
|
||||||
|
}
|
||||||
|
|
||||||
export function formatForecastTemperature(value: number | null, language: Language, unit: TemperatureUnit = DEFAULT_TEMPERATURE_UNIT) {
|
export function formatForecastTemperature(value: number | null, language: Language, unit: TemperatureUnit = DEFAULT_TEMPERATURE_UNIT) {
|
||||||
if (value === null) return "—";
|
if (value === null) return "—";
|
||||||
return formatTemperature(value, language, unit);
|
return formatTemperature(value, language, unit);
|
||||||
|
|||||||
Reference in New Issue
Block a user