chore: add prettier formatting
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m56s

This commit is contained in:
zv
2026-06-14 20:26:56 +02:00
parent 8bbd9397a1
commit ee55521803
79 changed files with 2451 additions and 969 deletions

View File

@@ -53,17 +53,25 @@ export function DashboardWarnings() {
};
}, []);
const province = isGlobalLocation ? null : getProvinceForSelection(selectedLocation?.province, selectedStationId ?? DEFAULT_STATION_ID);
const regionLabel = selectedLocation?.countyTeryt ? selectedLocation.district ?? selectedLocation.name : province ? formatProvinceName(province, language) : null;
const province = isGlobalLocation
? null
: getProvinceForSelection(selectedLocation?.province, selectedStationId ?? DEFAULT_STATION_ID);
const regionLabel = selectedLocation?.countyTeryt
? (selectedLocation.district ?? selectedLocation.name)
: province
? formatProvinceName(province, language)
: null;
const relevantWarnings = useMemo(() => {
if (isGlobalLocation || !warnings || !province || now === null) return [];
return warnings
.filter((warning) => {
const validTo = getTimestamp(warning.validTo);
return warning.kind === "meteo"
&& warningMatchesLocalSelection(warning, province, selectedLocation)
&& (validTo === null || validTo > now);
return (
warning.kind === "meteo" &&
warningMatchesLocalSelection(warning, province, selectedLocation) &&
(validTo === null || validTo > now)
);
})
.sort((a, b) => compareDashboardWarnings(a, b, now));
}, [isGlobalLocation, now, province, selectedLocation, warnings]);
@@ -87,12 +95,8 @@ export function DashboardWarnings() {
<AlertTriangle className="size-4" aria-hidden="true" />
</div>
<div>
<p className="text-[0.68rem] font-semibold uppercase tracking-[0.2em] text-warning">
IMGW · {regionLabel}
</p>
<h2 className="mt-1 text-base font-semibold text-foreground">
{t("warnings.dashboard.title")}
</h2>
<p className="text-[0.68rem] font-semibold uppercase tracking-[0.2em] text-warning">IMGW · {regionLabel}</p>
<h2 className="mt-1 text-base font-semibold text-foreground">{t("warnings.dashboard.title")}</h2>
</div>
</div>
<Link
@@ -109,13 +113,11 @@ export function DashboardWarnings() {
const active = isWarningActive(warning, now);
const validityLabel = active
? warning.validTo && t("warnings.dashboard.validUntil", { date: formatDateTime(warning.validTo, language) })
: warning.validFrom && t("warnings.dashboard.validFrom", { date: formatDateTime(warning.validFrom, language) });
: warning.validFrom &&
t("warnings.dashboard.validFrom", { date: formatDateTime(warning.validFrom, language) });
return (
<article
key={warning.id}
className="rounded-card border border-warning/20 bg-surface px-3.5 py-3"
>
<article key={warning.id} className="rounded-card border border-warning/20 bg-surface px-3.5 py-3">
<p className="text-[0.68rem] font-semibold uppercase tracking-[0.16em] text-warning">
{t(active ? "warnings.dashboard.active" : "warnings.dashboard.upcoming")}
{warning.level !== null && ` · ${t("warnings.level", { level: warning.level })}`}

View File

@@ -12,25 +12,57 @@ export function WarningCard({ warning, index = 0 }: { warning: WeatherWarning; i
const { language, t } = useI18n();
const Icon = warning.kind === "hydro" ? Waves : CloudLightning;
const level = warning.level;
const levelLabel = level === -1 ? t("warnings.drought") : level === null ? t("warnings.levelUnknown") : t("warnings.level", { level });
const areasLabel = warning.areas.length > 8
? `${warning.areas.slice(0, 8).join(", ")} ${t("warnings.moreAreas", { count: warning.areas.length - 8 })}`
: warning.areas.join("; ");
const levelLabel =
level === -1 ? t("warnings.drought") : level === null ? t("warnings.levelUnknown") : t("warnings.level", { level });
const areasLabel =
warning.areas.length > 8
? `${warning.areas.slice(0, 8).join(", ")} ${t("warnings.moreAreas", { count: warning.areas.length - 8 })}`
: warning.areas.join("; ");
return (
<motion.article initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: Math.min(index * 0.04, 0.4), duration: 0.35 }}>
<motion.article
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: Math.min(index * 0.04, 0.4), duration: 0.35 }}
>
<Card className="h-full overflow-hidden p-5">
<div className="flex items-start justify-between gap-3">
<div className="rounded-card bg-warning/10 p-2.5 text-warning"><Icon className="size-5" /></div>
<span className={cn("rounded-control border px-2.5 py-1 text-xs font-semibold", level === -1 ? "border-warning/30 bg-warning/10 text-warning" : "border-warning/25 bg-warning/10 text-warning")}>{levelLabel}</span>
<div className="rounded-card bg-warning/10 p-2.5 text-warning">
<Icon className="size-5" />
</div>
<span
className={cn(
"rounded-control border px-2.5 py-1 text-xs font-semibold",
level === -1
? "border-warning/30 bg-warning/10 text-warning"
: "border-warning/25 bg-warning/10 text-warning",
)}
>
{levelLabel}
</span>
</div>
<p className="mt-5 text-xs font-semibold uppercase tracking-[0.15em] text-muted">{warning.kind === "hydro" ? t("warnings.hydro") : t("warnings.meteo")}</p>
<h2 className="mt-2 text-lg font-semibold tracking-tight">{warning.title || (warning.kind === "hydro" ? t("warnings.genericHydro") : t("warnings.genericMeteo"))}</h2>
<p className="mt-5 text-xs font-semibold uppercase tracking-[0.15em] text-muted">
{warning.kind === "hydro" ? t("warnings.hydro") : t("warnings.meteo")}
</p>
<h2 className="mt-2 text-lg font-semibold tracking-tight">
{warning.title || (warning.kind === "hydro" ? t("warnings.genericHydro") : t("warnings.genericMeteo"))}
</h2>
{warning.description && <p className="mt-3 line-clamp-5 text-sm leading-6 text-muted">{warning.description}</p>}
<div className="mt-5 space-y-2 text-xs text-muted">
<p className="flex items-start gap-2"><CalendarClock className="mt-0.5 size-3.5 shrink-0" />{formatDateTime(warning.validFrom, language)} {warning.validTo ? formatDateTime(warning.validTo, language) : t("warnings.untilCancelled")}</p>
<p className="flex items-start gap-2"><MapPinned className="mt-0.5 size-3.5 shrink-0" />{areasLabel || t("warnings.areaUnknown")}</p>
<p className="flex items-start gap-2">
<CalendarClock className="mt-0.5 size-3.5 shrink-0" />
{formatDateTime(warning.validFrom, language)} {" "}
{warning.validTo ? formatDateTime(warning.validTo, language) : t("warnings.untilCancelled")}
</p>
<p className="flex items-start gap-2">
<MapPinned className="mt-0.5 size-3.5 shrink-0" />
{areasLabel || t("warnings.areaUnknown")}
</p>
</div>
{warning.probability !== null && <p className="mt-4 text-xs font-medium text-muted">{t("warnings.probability", { value: warning.probability })}</p>}
{warning.probability !== null && (
<p className="mt-4 text-xs font-medium text-muted">
{t("warnings.probability", { value: warning.probability })}
</p>
)}
</Card>
</motion.article>
);

View File

@@ -16,7 +16,9 @@ import type { WeatherWarning } from "@/types/imgw";
function WarningGrid({ warnings, indexOffset = 0 }: { warnings: WeatherWarning[]; indexOffset?: number }) {
return (
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{warnings.map((warning, index) => <WarningCard key={warning.id} warning={warning} index={index + indexOffset} />)}
{warnings.map((warning, index) => (
<WarningCard key={warning.id} warning={warning} index={index + indexOffset} />
))}
</div>
);
}
@@ -27,37 +29,60 @@ export function WarningsPanel() {
const selectedStationId = useWeatherStore((state) => state.selectedStationId);
const selectedLocation = useWeatherStore((state) => state.selectedLocation);
if (selectedLocation?.region === "GLOBAL") {
return <EmptyState title={t("warnings.globalUnavailableTitle")} description={t("warnings.globalUnavailableDescription")} />;
return (
<EmptyState
title={t("warnings.globalUnavailableTitle")}
description={t("warnings.globalUnavailableDescription")}
/>
);
}
if (isPending) return <PageLoadingSkeleton />;
if (isError) return <ErrorState onRetry={() => refetch()} description={t("warnings.error")} />;
if (!warnings?.length) return <EmptyState title={t("warnings.emptyTitle")} description={t("warnings.emptyDescription")} />;
if (!warnings?.length)
return <EmptyState title={t("warnings.emptyTitle")} description={t("warnings.emptyDescription")} />;
const province = getProvinceForSelection(selectedLocation?.province, selectedStationId ?? DEFAULT_STATION_ID);
if (!province) return <WarningGrid warnings={warnings} />;
const provinceLabel = formatProvinceName(province, language);
const localLabel = selectedLocation?.countyTeryt ? selectedLocation.district ?? selectedLocation.name : provinceLabel;
const localLabel = selectedLocation?.countyTeryt
? (selectedLocation.district ?? selectedLocation.name)
: provinceLabel;
const localWarnings = warnings.filter((warning) => warningMatchesLocalSelection(warning, province, selectedLocation));
const otherWarnings = warnings.filter((warning) => !warningMatchesLocalSelection(warning, province, selectedLocation));
const otherWarnings = warnings.filter(
(warning) => !warningMatchesLocalSelection(warning, province, selectedLocation),
);
return (
<div className="space-y-9">
<section className="space-y-4">
<div>
<p className="section-kicker flex items-center gap-2"><MapPinned className="size-4" />{t("warnings.myProvince")}</p>
<p className="section-kicker flex items-center gap-2">
<MapPinned className="size-4" />
{t("warnings.myProvince")}
</p>
<h2 className="mt-2 text-2xl font-semibold capitalize tracking-tight">{localLabel}</h2>
<p className="mt-1 max-w-2xl text-sm leading-6 text-muted">{t("warnings.myProvinceDescription", { province: localLabel })}</p>
<p className="mt-1 max-w-2xl text-sm leading-6 text-muted">
{t("warnings.myProvinceDescription", { province: localLabel })}
</p>
</div>
{localWarnings.length
? <WarningGrid warnings={localWarnings} />
: <EmptyState title={t("warnings.myProvinceEmptyTitle")} description={t("warnings.myProvinceEmptyDescription", { province: localLabel })} />}
{localWarnings.length ? (
<WarningGrid warnings={localWarnings} />
) : (
<EmptyState
title={t("warnings.myProvinceEmptyTitle")}
description={t("warnings.myProvinceEmptyDescription", { province: localLabel })}
/>
)}
</section>
{otherWarnings.length > 0 && (
<section className="space-y-4">
<div>
<p className="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.16em] text-muted"><Map className="size-4" />{t("warnings.otherRegions")}</p>
<p className="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.16em] text-muted">
<Map className="size-4" />
{t("warnings.otherRegions")}
</p>
<p className="mt-1 max-w-2xl text-sm leading-6 text-muted">{t("warnings.otherRegionsDescription")}</p>
</div>
<WarningGrid warnings={otherWarnings} indexOffset={localWarnings.length} />