chore: add prettier formatting
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m56s
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m56s
This commit is contained in:
@@ -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 })}`}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user