Files
shimmer-finance-ai-companion/src/components/features/BudgetManager.tsx

197 lines
8.5 KiB
TypeScript
Raw Normal View History

import React from 'react';
import { motion } from 'framer-motion';
import { GlassCard } from '@/components/ui/GlassCard';
import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
import {
Wallet,
TrendingUp,
Home,
Car,
Utensils,
ShoppingCart,
Target,
ArrowRight,
AlertCircle
} from 'lucide-react';
import { cn, formatCurrency } from '@/lib/utils';
// Mock Data
const budgetData = {
limit: 15000,
spent: 12500,
categories: [
{ name: 'Housing', icon: Home, spent: 6500, limit: 7000, color: 'bg-blue-500' },
{ name: 'Food & Dining', icon: Utensils, spent: 2800, limit: 3000, color: 'bg-green-500' },
{ name: 'Transportation', icon: Car, spent: 1200, limit: 1500, color: 'bg-orange-500' },
{ name: 'Shopping', icon: ShoppingCart, spent: 2000, limit: 1500, color: 'bg-pink-500' }, // Over budget
],
goals: [
{ name: 'Buy a House', target: 800000, current: 200000, color: 'bg-purple-500' },
{ name: 'Retirement', target: 2000000, current: 150000, color: 'bg-indigo-500' },
]
};
export default function BudgetManager() {
const percentage = Math.min(100, (budgetData.spent / budgetData.limit) * 100);
const circumference = 2 * Math.PI * 120; // Radius 120
const strokeDashoffset = circumference - (percentage / 100) * circumference;
return (
<div className="max-w-6xl mx-auto space-y-8 pb-20 animate-fade-in">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-slate-900">Monthly Budget & Goals</h1>
<p className="text-sm sm:text-base text-slate-500">Track your spending and save for the future.</p>
</div>
<Button className="w-full sm:w-auto gap-2 bg-slate-900 text-white hover:bg-slate-800 rounded-full">
<Wallet className="w-4 h-4" /> Edit Budget
</Button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 sm:gap-8">
{/* Main Budget Ring */}
<GlassCard className="col-span-1 lg:col-span-1 flex flex-col items-center justify-center p-6 sm:p-8 relative overflow-hidden">
<div className="relative w-64 h-64 sm:w-72 sm:h-72 flex items-center justify-center">
{/* Background Circle */}
<svg className="w-full h-full transform -rotate-90">
<circle
cx="50%"
cy="50%"
r="45%"
stroke="currentColor"
strokeWidth="24"
fill="transparent"
className="text-slate-100"
/>
{/* Progress Circle */}
<motion.circle
initial={{ strokeDashoffset: circumference }}
animate={{ strokeDashoffset }}
transition={{ duration: 1.5, ease: "easeOut" }}
cx="50%"
cy="50%"
r="45%"
stroke="currentColor"
strokeWidth="24"
fill="transparent"
strokeDasharray={circumference}
strokeLinecap="round"
className={cn(
"text-blue-500 drop-shadow-lg",
percentage > 90 ? "text-red-500" : percentage > 75 ? "text-orange-500" : "text-blue-500"
)}
/>
</svg>
<div className="absolute flex flex-col items-center">
<span className="text-xs sm:text-sm text-slate-500 font-medium uppercase tracking-wider">Total Spent</span>
<span className="text-3xl sm:text-4xl font-bold text-slate-900 mt-1">
{formatCurrency(budgetData.spent)}
</span>
<span className="text-xs sm:text-sm text-slate-400 mt-1">
of {formatCurrency(budgetData.limit)}
</span>
</div>
</div>
{percentage > 90 && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-6 flex items-center gap-2 text-red-600 bg-red-50 px-4 py-2 rounded-full border border-red-100"
>
<AlertCircle className="w-4 h-4" />
<span className="text-sm font-medium">You've used {percentage.toFixed(0)}% of your budget!</span>
</motion.div>
)}
</GlassCard>
{/* Categories & Goals */}
<div className="col-span-1 lg:col-span-2 space-y-8">
{/* Categories Breakdown */}
<GlassCard className="p-6">
<h3 className="text-lg font-semibold mb-6 flex items-center gap-2">
<Utensils className="w-5 h-5 text-slate-500" /> Category Breakdown
</h3>
<div className="space-y-6">
{budgetData.categories.map((cat, index) => (
<motion.div
key={cat.name}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.1 }}
className="space-y-2"
>
<div className="flex justify-between items-center text-sm">
<div className="flex items-center gap-2">
<div className={cn("p-1.5 rounded-lg text-white", cat.color)}>
<cat.icon className="w-3.5 h-3.5" />
</div>
<span className="font-medium text-slate-700">{cat.name}</span>
</div>
<div className="flex items-center gap-2">
<span className="font-semibold">{formatCurrency(cat.spent)}</span>
<span className="text-slate-400">/ {formatCurrency(cat.limit)}</span>
</div>
</div>
<div className="h-2.5 bg-slate-100 rounded-full overflow-hidden">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${Math.min(100, (cat.spent / cat.limit) * 100)}%` }}
transition={{ duration: 1, delay: 0.5 + (index * 0.1) }}
className={cn("h-full rounded-full", cat.color)}
/>
</div>
</motion.div>
))}
</div>
</GlassCard>
{/* Long-term Goals */}
<div>
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<Target className="w-5 h-5 text-slate-500" /> Long-term Planning
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{budgetData.goals.map((goal, index) => (
<GlassCard key={goal.name} className="p-5 relative overflow-hidden group">
<div className="relative z-10">
<div className="flex justify-between items-start mb-4">
<div>
<h4 className="font-semibold text-slate-900">{goal.name}</h4>
<div className="bg-green-100 text-green-700 text-xs px-2 py-0.5 rounded-full inline-block mt-1 font-medium">
On Track
</div>
</div>
<div className={cn("p-2 rounded-xl text-white opacity-80", goal.color)}>
<Target className="w-5 h-5" />
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-slate-500">Progress</span>
<span className="font-bold text-slate-900">{Math.round((goal.current / goal.target) * 100)}%</span>
</div>
<Progress value={(goal.current / goal.target) * 100} className="h-2" indicatorClassName={goal.color} />
<div className="flex justify-between text-xs text-slate-400 pt-1">
<span>{formatCurrency(goal.current)}</span>
<span>{formatCurrency(goal.target)}</span>
</div>
</div>
</div>
{/* Hover Effect Background */}
<div className={cn(
"absolute inset-0 opacity-0 group-hover:opacity-10 transition-opacity duration-500",
goal.color
)} />
</GlassCard>
))}
</div>
</div>
</div>
</div>
</div>
);
}