Files
smart-home-tablet/components/cards/WeatherCard.tsx
Cosmo 9044869fa4
All checks were successful
Deploy to Coolify / deploy (push) Successful in 44s
feat: initial smart home dashboard
- Next.js 14 + TypeScript + Tailwind CSS
- Glassmorphism design with ambient orbs
- Cards: Light x2, Temperature, AirPurifier, Tasks, Weather, Savings
- Home Assistant integration (demo mode if no token)
- Vikunja tasks API
- Pulse savings API
- wttr.in weather
- Framer Motion animations
- Dark/light theme toggle
- Bottom navigation
- Dockerfile for deployment
2026-04-22 10:00:41 +00:00

141 lines
4.3 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 { motion } from "framer-motion";
import { Droplets, Wind } from "lucide-react";
function getWeatherEmoji(code: string): string {
const c = parseInt(code);
if (c === 113) return "☀️";
if (c === 116) return "⛅";
if (c === 119 || c === 122) return "☁️";
if (c >= 176 && c <= 182) return "🌦️";
if (c >= 185 && c <= 200) return "🌧️";
if (c >= 200 && c <= 210) return "⛈️";
if (c >= 210 && c <= 260) return "❄️";
if (c >= 260 && c <= 300) return "🌨️";
if (c >= 300 && c <= 400) return "🌧️";
return "🌤️";
}
function formatDate(dateStr: string): string {
const d = new Date(dateStr);
return d.toLocaleDateString("ru-RU", { weekday: "short", day: "numeric" });
}
interface Props {
weather: any;
}
export default function WeatherCard({ weather }: Props) {
if (!weather) {
return (
<motion.div
className="glass-card p-5 h-full flex items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<div
className="text-sm"
style={{ color: "var(--text-secondary)" }}
>
Загрузка погоды...
</div>
</motion.div>
);
}
return (
<motion.div
className="glass-card p-5 h-full flex flex-col"
style={{
background: "rgba(6,182,212,0.04)",
border: "1px solid rgba(6,182,212,0.12)",
}}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
whileHover={{ scale: 1.01 }}
>
<div className="flex items-start justify-between mb-4">
<div>
<div
className="text-sm font-semibold"
style={{ color: "var(--text-primary)" }}
>
🌍 Санкт-Петербург
</div>
<div className="flex items-center gap-3 mt-2">
<span className="text-3xl font-bold" style={{ color: "var(--text-primary)" }}>
{getWeatherEmoji(weather.weatherCode)}
</span>
<div>
<div
className="text-2xl font-bold"
style={{ color: "var(--text-primary)" }}
>
{weather.temp}°C
</div>
<div
className="text-xs"
style={{ color: "var(--text-secondary)" }}
>
{weather.desc}
</div>
</div>
</div>
</div>
<div className="text-right space-y-1">
<div className="flex items-center gap-1 justify-end">
<Droplets size={12} color="#06b6d4" />
<span className="text-xs" style={{ color: "var(--text-secondary)" }}>
{weather.humidity}%
</span>
</div>
<div className="flex items-center gap-1 justify-end">
<Wind size={12} color="#8b5cf6" />
<span className="text-xs" style={{ color: "var(--text-secondary)" }}>
{weather.windSpeed} км/ч
</span>
</div>
</div>
</div>
{/* Forecast */}
<div className="flex gap-2 mt-auto">
{(weather.forecast || []).map((day: any, i: number) => (
<motion.div
key={day.date}
className="flex-1 rounded-xl p-3 text-center"
style={{
background: i === 0
? "rgba(99,102,241,0.12)"
: "rgba(255,255,255,0.04)",
border: i === 0
? "1px solid rgba(99,102,241,0.25)"
: "1px solid rgba(255,255,255,0.06)",
}}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.1 }}
>
<div
className="text-xs mb-1 font-medium"
style={{ color: "var(--text-secondary)" }}
>
{i === 0 ? "Сегодня" : formatDate(day.date)}
</div>
<div className="text-lg mb-1">
{getWeatherEmoji(day.weatherCode)}
</div>
<div
className="text-xs font-semibold"
style={{ color: "var(--text-primary)" }}
>
{day.maxTemp}° / {day.minTemp}°
</div>
</motion.div>
))}
</div>
</motion.div>
);
}