feat: add Polish and English language switcher
This commit is contained in:
@@ -6,13 +6,15 @@ import type { WeatherWarning } from "@/types/imgw";
|
||||
import { formatDateTime } from "@/lib/weather-utils";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
|
||||
export function WarningCard({ warning, index = 0 }: { warning: WeatherWarning; index?: number }) {
|
||||
const { language, t } = useI18n();
|
||||
const Icon = warning.kind === "hydro" ? Waves : CloudLightning;
|
||||
const level = warning.level;
|
||||
const levelLabel = level === -1 ? "Susza hydrologiczna" : level === null ? "Poziom nieokreślony" : `Stopień ${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(", ")} i ${warning.areas.length - 8} więcej`
|
||||
? `${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 }}>
|
||||
@@ -21,14 +23,14 @@ export function WarningCard({ warning, index = 0 }: { warning: WeatherWarning; i
|
||||
<div className="rounded-2xl bg-amber-500/15 p-2.5 text-amber-700 dark:text-amber-300"><Icon className="size-5" /></div>
|
||||
<span className={cn("rounded-full border px-2.5 py-1 text-xs font-semibold", level === -1 ? "border-orange-300/40 bg-orange-400/15 text-orange-800 dark:text-orange-200" : "border-amber-300/40 bg-amber-400/15 text-amber-800 dark:text-amber-200")}>{levelLabel}</span>
|
||||
</div>
|
||||
<p className="mt-5 text-xs font-semibold uppercase tracking-[0.15em] text-slate-500 dark:text-slate-400">{warning.kind === "hydro" ? "Hydrologiczne" : "Meteorologiczne"}</p>
|
||||
<h2 className="mt-2 text-lg font-semibold tracking-tight">{warning.title}</h2>
|
||||
<p className="mt-5 text-xs font-semibold uppercase tracking-[0.15em] text-slate-500 dark:text-slate-400">{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-slate-600 dark:text-slate-300">{warning.description}</p>}
|
||||
<div className="mt-5 space-y-2 text-xs text-slate-500 dark:text-slate-400">
|
||||
<p className="flex items-start gap-2"><CalendarClock className="mt-0.5 size-3.5 shrink-0" />{formatDateTime(warning.validFrom)} — {warning.validTo ? formatDateTime(warning.validTo) : "do odwołania"}</p>
|
||||
<p className="flex items-start gap-2"><MapPinned className="mt-0.5 size-3.5 shrink-0" />{areasLabel || "Obszar nieokreślony"}</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-slate-600 dark:text-slate-300">Prawdopodobieństwo: {warning.probability}%</p>}
|
||||
{warning.probability !== null && <p className="mt-4 text-xs font-medium text-slate-600 dark:text-slate-300">{t("warnings.probability", { value: warning.probability })}</p>}
|
||||
</Card>
|
||||
</motion.article>
|
||||
);
|
||||
|
||||
18
components/warnings/warnings-page-content.tsx
Normal file
18
components/warnings/warnings-page-content.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { WarningsPanel } from "@/components/warnings/warnings-panel";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
|
||||
export function WarningsPageContent() {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-sky-700 dark:text-sky-300">{t("warnings.section")}</p>
|
||||
<h1 className="mt-2 text-3xl font-semibold tracking-tight">{t("warnings.title")}</h1>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-slate-600 dark:text-slate-300">{t("warnings.description")}</p>
|
||||
</div>
|
||||
<WarningsPanel />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,11 +5,13 @@ import { WarningCard } from "@/components/warnings/warning-card";
|
||||
import { PageLoadingSkeleton } from "@/components/states/loading-skeleton";
|
||||
import { EmptyState } from "@/components/states/empty-state";
|
||||
import { ErrorState } from "@/components/states/error-state";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
|
||||
export function WarningsPanel() {
|
||||
const { t } = useI18n();
|
||||
const { data: warnings, isPending, isError, refetch } = useWarnings();
|
||||
if (isPending) return <PageLoadingSkeleton />;
|
||||
if (isError) return <ErrorState onRetry={() => refetch()} description="Nie udało się pobrać ostrzeżeń meteorologicznych ani hydrologicznych." />;
|
||||
if (!warnings?.length) return <EmptyState title="Brak aktywnych ostrzeżeń" description="IMGW nie publikuje obecnie ostrzeżeń meteorologicznych ani hydrologicznych." />;
|
||||
if (isError) return <ErrorState onRetry={() => refetch()} description={t("warnings.error")} />;
|
||||
if (!warnings?.length) return <EmptyState title={t("warnings.emptyTitle")} description={t("warnings.emptyDescription")} />;
|
||||
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} />)}</div>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user