Files
smart-home-tablet/components/cards/SavingsCard.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

120 lines
3.4 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 { PiggyBank } from "lucide-react";
interface Saving {
id: number;
name: string;
current_amount: number;
target_amount: number;
color?: string;
icon?: string;
}
interface Props {
savings: Saving[];
}
function formatAmount(n: number): string {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
if (n >= 1_000) return `${Math.round(n / 1_000)}K`;
return String(n);
}
export default function SavingsCard({ savings }: Props) {
return (
<motion.div
className="glass-card p-5 h-full flex flex-col"
style={{
background: "rgba(99,102,241,0.04)",
border: "1px solid rgba(99,102,241,0.12)",
}}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
whileHover={{ scale: 1.01 }}
>
<div className="flex items-center gap-2 mb-4">
<PiggyBank size={18} color="#6366f1" />
<span
className="text-sm font-semibold"
style={{ color: "var(--text-primary)" }}
>
Накопления
</span>
</div>
<div className="flex-1 space-y-4">
{savings.map((s, i) => {
const pct = Math.min(
100,
Math.round((s.current_amount / s.target_amount) * 100)
);
const color = s.color || "#6366f1";
return (
<motion.div
key={s.id}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.1 }}
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
{s.icon && <span className="text-base">{s.icon}</span>}
<span
className="text-xs font-medium"
style={{ color: "var(--text-primary)" }}
>
{s.name}
</span>
</div>
<span
className="text-xs font-semibold"
style={{ color }}
>
{pct}%
</span>
</div>
<div className="progress-bar">
<motion.div
className="progress-fill"
style={{
background: `linear-gradient(90deg, ${color}aa, ${color})`,
width: `${pct}%`,
}}
initial={{ width: "0%" }}
animate={{ width: `${pct}%` }}
transition={{
duration: 1,
delay: i * 0.2,
ease: "easeOut",
}}
/>
</div>
<div
className="flex justify-between text-xs mt-1"
style={{ color: "var(--text-secondary)" }}
>
<span>{formatAmount(s.current_amount)} </span>
<span>цель: {formatAmount(s.target_amount)} </span>
</div>
</motion.div>
);
})}
{savings.length === 0 && (
<div
className="text-center py-6 text-sm"
style={{ color: "var(--text-secondary)" }}
>
Нет данных о накоплениях
</div>
)}
</div>
</motion.div>
);
}