88 lines
3.7 KiB
TypeScript
88 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { motion } from "framer-motion";
|
|
import { Droplets, Gauge, Heart, Wind } from "lucide-react";
|
|
import { useWeatherStore } from "@/lib/store";
|
|
import {
|
|
formatHumidity,
|
|
formatPressure,
|
|
formatTemperature,
|
|
formatWind,
|
|
getWeatherMoodFromData,
|
|
} from "@/lib/weather-utils";
|
|
import type { SynopStation } from "@/types/imgw";
|
|
import { Card } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { WeatherIcon } from "@/components/weather/weather-icon";
|
|
import { cn } from "@/lib/utils";
|
|
import { useI18n } from "@/lib/i18n";
|
|
|
|
export function StationCard({ station, index = 0 }: { station: SynopStation; index?: number }) {
|
|
const { language, t } = useI18n();
|
|
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 pressureUnit = useWeatherStore((state) => state.pressureUnit);
|
|
const favorite = favorites.includes(station.id);
|
|
const mood = getWeatherMoodFromData(station);
|
|
const compactWind = station.windSpeed === null ? "—" : formatWind(station.windSpeed, null, language, windSpeedUnit);
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: Math.min(index * 0.025, 0.3), duration: 0.3 }}
|
|
>
|
|
<Card className="group relative h-full overflow-hidden p-4 transition duration-300 hover:-translate-y-1 hover:bg-surface-raised/90">
|
|
<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, temperatureUnit)}
|
|
</p>
|
|
</Link>
|
|
<div className="flex flex-col items-end gap-2">
|
|
<WeatherIcon mood={mood} className="size-9 text-accent" />
|
|
<Button
|
|
variant="ghost"
|
|
className="size-8 p-0"
|
|
aria-label={
|
|
favorite
|
|
? t("favorites.removeStation", { name: station.name })
|
|
: t("favorites.addStation", { name: station.name })
|
|
}
|
|
onClick={() => toggleFavorite(station.id)}
|
|
>
|
|
<Heart className={cn("size-4", favorite && "fill-rose-500 text-rose-500")} />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<Link
|
|
href={`/station/${station.id}`}
|
|
onClick={() => selectStation(station.id)}
|
|
className="mt-4 grid grid-cols-3 gap-2 rounded-lg text-[0.68rem] text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent"
|
|
>
|
|
<span className="flex items-center gap-1">
|
|
<Droplets className="size-3" />
|
|
{formatHumidity(station.humidity, language)}
|
|
</span>
|
|
<span className="flex items-center gap-1">
|
|
<Wind className="size-3" />
|
|
{compactWind}
|
|
</span>
|
|
<span className="flex items-center gap-1">
|
|
<Gauge className="size-3" />
|
|
{station.pressure === null ? "—" : formatPressure(station.pressure, language, pressureUnit)}
|
|
</span>
|
|
</Link>
|
|
</Card>
|
|
</motion.div>
|
|
);
|
|
}
|