Refactor: Global Theme System and Finance Modules Redesign
This commit is contained in:
@@ -1,197 +1,81 @@
|
||||
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';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Plus, Settings } from 'lucide-react';
|
||||
|
||||
// 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' },
|
||||
]
|
||||
};
|
||||
// Import New Modular Budget Components
|
||||
import BudgetSummaryCards from '@/components/budget/BudgetSummaryCards';
|
||||
import BudgetOverviewList from '@/components/budget/BudgetOverviewList';
|
||||
import RecentTransactionsCard from '@/components/budget/RecentTransactionsCard';
|
||||
import BudgetCategoryGrid from '@/components/budget/BudgetCategoryGrid';
|
||||
import FullExpensesTable from '@/components/budget/FullExpensesTable';
|
||||
import AddExpenseForm from '@/components/budget/AddExpenseForm';
|
||||
import SetBudgetForm from '@/components/budget/SetBudgetForm';
|
||||
|
||||
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 className="space-y-6 animate-fade-in text-slate-800 pb-10">
|
||||
|
||||
{/* Header Section */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
|
||||
<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>
|
||||
<h1 className="text-3xl font-light tracking-tight text-slate-900">
|
||||
Budget <span className="font-semibold bg-clip-text text-transparent bg-gradient-to-r from-green-600 to-teal-600">Manager</span>
|
||||
</h1>
|
||||
<p className="text-slate-500 mt-1">Track every dirham with precision.</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 className="flex gap-3">
|
||||
<Button variant="outline" className="border-slate-200 text-slate-700 bg-white hover:bg-slate-50">
|
||||
<Plus className="w-4 h-4 mr-2" /> Add Expense
|
||||
</Button>
|
||||
<Button className="bg-black text-white hover:bg-slate-800 shadow-md">
|
||||
<Settings className="w-4 h-4 mr-2" /> Set Budget
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Summary Cards */}
|
||||
<BudgetSummaryCards />
|
||||
|
||||
{/* Main Content Tabs */}
|
||||
<Tabs defaultValue="overview" className="space-y-6 w-full">
|
||||
<TabsList className="bg-white/60 p-1.5 rounded-2xl backdrop-blur-md border border-white/40 inline-flex w-auto flex-wrap lg:flex-nowrap shadow-sm h-auto">
|
||||
<TabsTrigger value="overview" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Overview</TabsTrigger>
|
||||
<TabsTrigger value="budgets" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Budgets</TabsTrigger>
|
||||
<TabsTrigger value="expenses" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Expenses</TabsTrigger>
|
||||
<TabsTrigger value="add-expense" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Add Expense</TabsTrigger>
|
||||
<TabsTrigger value="set-budget" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Set Budget</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Tab 1: Overview */}
|
||||
<TabsContent value="overview" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-auto lg:h-[450px]">
|
||||
<BudgetOverviewList />
|
||||
<RecentTransactionsCard />
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 2: Budgets */}
|
||||
<TabsContent value="budgets" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<BudgetCategoryGrid />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 3: Expenses */}
|
||||
<TabsContent value="expenses" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<FullExpensesTable />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 4: Add Expense */}
|
||||
<TabsContent value="add-expense" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<AddExpenseForm />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 5: Set Budget */}
|
||||
<TabsContent value="set-budget" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<SetBudgetForm />
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user