141 lines
4.1 KiB
TypeScript
141 lines
4.1 KiB
TypeScript
"use client";
|
||
|
||
import { motion } from "framer-motion";
|
||
import { Target } 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)}М`;
|
||
if (n >= 1_000) return `${Math.round(n / 1_000)}К`;
|
||
return String(n);
|
||
}
|
||
|
||
const COLORS = ["#f59e0b", "#3b82f6", "#10b981", "#8b5cf6", "#f43f5e"];
|
||
|
||
export default function SavingsCard({ savings }: Props) {
|
||
return (
|
||
<motion.div
|
||
className="glass-card p-6 h-full flex flex-col"
|
||
style={{
|
||
background: "rgba(245,158,11,0.04)",
|
||
border: "1px solid rgba(245,158,11,0.15)",
|
||
}}
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.35, delay: 0.21 }}
|
||
whileHover={{ scale: 1.01 }}
|
||
>
|
||
{/* Header */}
|
||
<div className="flex items-center gap-3 mb-5">
|
||
<div
|
||
className="w-10 h-10 rounded-xl flex items-center justify-center"
|
||
style={{
|
||
background: "rgba(245,158,11,0.2)",
|
||
boxShadow: "0 0 16px rgba(245,158,11,0.3)",
|
||
}}
|
||
>
|
||
<Target size={20} color="#f59e0b" />
|
||
</div>
|
||
<div>
|
||
<div
|
||
className="text-base font-bold"
|
||
style={{ color: "var(--text-primary)" }}
|
||
>
|
||
Накопления
|
||
</div>
|
||
<div
|
||
className="text-xs"
|
||
style={{ color: "var(--text-secondary)" }}
|
||
>
|
||
{savings.length} {savings.length === 1 ? "цель" : "целей"}
|
||
</div>
|
||
</div>
|
||
</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 || COLORS[i % COLORS.length];
|
||
|
||
return (
|
||
<motion.div
|
||
key={s.id}
|
||
initial={{ opacity: 0, y: 10 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.1 + 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-sm font-semibold"
|
||
style={{ color: "var(--text-primary)" }}
|
||
>
|
||
{s.name}
|
||
</span>
|
||
</div>
|
||
<span
|
||
className="text-sm font-black"
|
||
style={{ color }}
|
||
>
|
||
{pct}%
|
||
</span>
|
||
</div>
|
||
|
||
{/* Progress bar with glow */}
|
||
<div className="progress-bar">
|
||
<motion.div
|
||
className="h-full rounded-full"
|
||
style={{
|
||
background: `linear-gradient(90deg, ${color}80, ${color})`,
|
||
boxShadow: `0 0 12px ${color}60`,
|
||
}}
|
||
initial={{ width: "0%" }}
|
||
animate={{ width: `${pct}%` }}
|
||
transition={{
|
||
duration: 1.2,
|
||
delay: 0.2 + i * 0.15,
|
||
ease: "easeOut",
|
||
}}
|
||
/>
|
||
</div>
|
||
|
||
<div
|
||
className="flex justify-between text-xs mt-1.5"
|
||
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>
|
||
);
|
||
}
|