Files
digital-home-dashboard/src/components/widgets/WeatherWidget.tsx

116 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import { RefreshCw, Wind, Droplets, Thermometer } from "lucide-react";
interface CurrentWeather {
temp: number; feelsLike: number; humidity: number; windKmh: number; desc: string; icon: string;
}
interface DayForecast {
date: string; maxTemp: number; minTemp: number; desc: string; icon: string; precipProb: number;
}
interface WeatherData {
current: CurrentWeather;
forecast: DayForecast[];
}
const DAY_NAMES = ["Вс","Пн","Вт","Ср","Чт","Пт","Сб"];
const MONTH_NAMES = ["янв","фев","мар","апр","май","июн","июл","авг","сен","окт","ноя","дек"];
export function WeatherWidget() {
const [data, setData] = useState<WeatherData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [selected, setSelected] = useState(0);
const fetchData = async () => {
setLoading(true); setError(false);
try {
const res = await fetch("/api/weather");
if (!res.ok) throw new Error();
setData(await res.json());
} catch { setError(true); }
finally { setLoading(false); }
};
useEffect(() => { fetchData(); }, []);
const c = data?.current;
const day = data?.forecast?.[selected];
const formatDate = (dateStr: string) => {
const d = new Date(dateStr + "T12:00:00");
return `${DAY_NAMES[d.getDay()]}, ${d.getDate()} ${MONTH_NAMES[d.getMonth()]}`;
};
return (
<div className="glass-card p-5 flex flex-col gap-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-slate-300">Погода · Санкт-Петербург</span>
<button onClick={fetchData} className="text-slate-500 hover:text-slate-300 transition-colors">
<RefreshCw className={`w-3.5 h-3.5 ${loading ? "animate-spin" : ""}`} />
</button>
</div>
{error ? (
<div className="text-slate-500 text-sm text-center py-4">Нет данных</div>
) : loading ? (
<div className="space-y-3 animate-pulse">
<div className="h-12 bg-slate-700/50 rounded w-32" />
<div className="h-4 bg-slate-700/50 rounded w-48" />
<div className="flex gap-2">{[...Array(7)].map((_,i)=><div key={i} className="h-20 bg-slate-700/50 rounded flex-1"/>)}</div>
</div>
) : c && day ? (
<>
<div className="flex items-center gap-4">
<span className="text-5xl">{selected === 0 ? c.icon : day.icon}</span>
<div>
<div className="flex items-baseline gap-1">
<span className="text-4xl font-bold text-white">
{selected === 0 ? c.temp : day.maxTemp}°
</span>
{selected !== 0 && (
<span className="text-slate-400 text-lg">{day.minTemp}°</span>
)}
</div>
<div className="text-slate-300 text-sm">{selected === 0 ? c.desc : day.desc}</div>
<div className="text-xs text-slate-500">{selected === 0 ? "Сейчас" : formatDate(day.date)}</div>
</div>
{selected === 0 && (
<div className="ml-auto flex flex-col gap-1 text-xs text-slate-400">
<span className="flex items-center gap-1"><Thermometer className="w-3 h-3 text-orange-400"/>Ощущ. {c.feelsLike}°</span>
<span className="flex items-center gap-1"><Droplets className="w-3 h-3 text-blue-400"/>{c.humidity}%</span>
<span className="flex items-center gap-1"><Wind className="w-3 h-3 text-teal-400"/>{c.windKmh} км/ч</span>
</div>
)}
</div>
<div className="flex gap-1.5 overflow-x-auto pb-1">
{data.forecast.map((f, i) => {
const d = new Date(f.date + "T12:00:00");
const isToday = i === 0;
return (
<button
key={f.date}
onClick={() => setSelected(i)}
className={`flex-1 min-w-[52px] flex flex-col items-center gap-1 p-2 rounded-xl transition-all text-center ${
selected === i
? "bg-indigo-600/60 ring-1 ring-indigo-500"
: "bg-slate-800/40 hover:bg-slate-700/50"
}`}
>
<span className="text-xs text-slate-400">{isToday ? "Сег" : DAY_NAMES[d.getDay()]}</span>
<span className="text-lg">{f.icon}</span>
<span className="text-xs font-medium text-white">{f.maxTemp}°</span>
<span className="text-xs text-slate-500">{f.minTemp}°</span>
{f.precipProb > 20 && (
<span className="text-[10px] text-blue-400">{f.precipProb}%</span>
)}
</button>
);
})}
</div>
</>
) : null}
</div>
);
}