Personal Finance
Personal Finance Dashboard with budget tracking, expense analytics, savings goals, and transaction management micro UI components.
Folder Structure
Installation and Setup
To set up the Personal Finance Dashboard, follow these steps
Install the necessary components for the dashboard
Run the following command to install all finance dashboard components
npx mvpblocks add personal-finance-dashboard-1Install required dependencies
npm install recharts lucide-react next-themeor
yarn add recharts lucide-react next-themeCopy the following components and paste them into your project
Add index.tsx => Copy the code from the above on clicking the "Code" tab which is present corresponding to "Preview tab" after the component title.
Add dashboard-layout.tsx
'use client';
import { ReactNode, useState } from 'react';
import PersonalSidebar from './personal-sidebar';
import PersonalHeader from './personal-header';
import PersonalFooter from './personal-footer';
interface DashboardLayoutProps {
children: ReactNode;
}
export default function DashboardLayout({ children }: DashboardLayoutProps) {
const [collapsed, setCollapsed] = useState<boolean>(false);
return (
<div className="min-h-screen w-full overflow-hidden">
<div
className={`mt-16 grid min-h-screen grid-cols-1 transition-all duration-500 ${
collapsed ? 'ml-20 lg:grid-cols-[1fr]' : 'lg:grid-cols-[16rem_1fr]'
}`}
>
<div>
<PersonalSidebar collapsed={collapsed} setCollapsed={setCollapsed} />
</div>
<div className="flex w-full flex-col transition-all duration-500">
<PersonalHeader />
<main className="flex-1 overflow-y-auto p-3 md:p-6">{children}</main>
<PersonalFooter />
</div>
</div>
</div>
);
}
Add personal-sidebar.tsx
'use client';
import { Dispatch, SetStateAction, useState } from 'react';
import {
LayoutDashboard,
Wallet,
Target,
BarChart3,
Settings,
PiggyBank,
ChevronRight,
Bell,
User,
Menu,
DollarSign,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
interface SidebarProps {
collapsed: boolean;
setCollapsed: Dispatch<SetStateAction<boolean>>;
}
const navigation = [
{ name: 'Dashboard', href: '#', icon: LayoutDashboard, current: true },
{ name: 'Transactions', href: '#', icon: Wallet, current: false },
{ name: 'Budget', href: '#', icon: BarChart3, current: false },
{ name: 'Goals', href: '#', icon: Target, current: false },
{ name: 'Investments', href: '#', icon: PiggyBank, current: false },
{ name: 'Settings', href: '#', icon: Settings, current: false },
];
export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
const [mobileOpen, setMobileOpen] = useState(false);
return (
<>
<div className="fixed top-[0.8rem] left-3 z-60 md:hidden">
<Button
size="icon"
onClick={() => setMobileOpen(!mobileOpen)}
className="!bg-primary hover:!bg-primary rounded-sm text-white"
aria-label="Toggle sidebar"
>
<Menu className="size-5" />
</Button>
</div>
{/* Sidebar */}
<div
className={cn(
'bg-background fixed inset-y-0 top-14 left-0 z-50 transform overflow-hidden border-r transition-all duration-500 ease-in-out',
// On mobile, slide sidebar in/out based on mobileOpen
'md:translate-x-0',
mobileOpen ? 'w-64 translate-x-0' : 'w-64 -translate-x-full',
collapsed && 'w-16 md:w-20',
)}
>
<div className="flex h-full flex-col">
{/* Header */}
<div className="hidden items-center justify-between border-b md:flex">
<button
onClick={() => setCollapsed(!collapsed)}
className="hover:bg-muted flex w-full cursor-pointer items-center justify-between p-3.5 transition-all duration-500"
>
<div
className={cn(
'flex w-full items-center gap-3',
collapsed ? 'm-auto justify-center' : 'justify-start',
)}
>
<DollarSign className="text-primary size-8" />
{!collapsed && (
<h1 className="text-2xl font-medium">FinDash Pro</h1>
)}
</div>
<ChevronRight
className={cn(
'size-5 transition-all duration-500',
collapsed ? 'hidden' : 'block',
)}
/>
</button>
</div>
{/* Navigation */}
<nav className="flex-1 space-y-2 p-2 pt-4 md:p-4">
{navigation.map((item) => (
<Link
key={item.name}
href={item.href}
className={cn(
'group flex items-center rounded-sm border border-transparent p-3 text-sm font-medium transition-all duration-300 hover:scale-105',
item.current
? 'border-primary/30 from-primary/10 text-primary bg-gradient-to-br'
: 'text-muted-foreground from-primary/10 hover:border-primary/30 hover:text-primary hover:bg-gradient-to-br',
)}
onClick={() => setMobileOpen(false)}
>
<item.icon
className={cn(
item.current
? 'text-primary'
: 'text-muted-foreground group-hover:text-primary',
collapsed ? 'mx-auto size-5' : 'mr-3 size-5',
)}
/>
{!collapsed && item.name}
</Link>
))}
</nav>
{/* Mobile user & notification buttons */}
<nav className="flex flex-col space-y-2 p-2 md:hidden md:p-4">
<button
className={cn(
'group flex items-center rounded-sm border border-transparent px-3 py-3 text-sm font-medium transition-all duration-300 hover:scale-105',
'border-primary/30 from-primary/10 text-primary bg-gradient-to-br',
)}
aria-label="Notifications"
onClick={() => setMobileOpen(false)}
>
<Bell
className={cn(
'text-primary',
collapsed ? 'mx-auto size-5' : 'mr-3 size-5',
)}
/>
{!collapsed && 'Notifications'}
</button>
<button
className={cn(
'group flex items-center rounded-sm border border-transparent px-3 py-3 text-sm font-medium transition-all duration-300 hover:scale-105',
'border-primary/30 from-primary/10 text-primary bg-gradient-to-br',
)}
aria-label="Profile"
onClick={() => setMobileOpen(false)}
>
<User
className={cn(
'text-primary',
collapsed ? 'mx-auto size-5' : 'mr-3 size-5',
)}
/>
{!collapsed && 'Profile'}
</button>
</nav>
{/* Footer */}
{!collapsed && (
<div className="h-20 w-full border-t p-5">
<div className="border-primary/30 from-primary/20 text-primary w-full rounded-sm border bg-gradient-to-b p-3 text-center text-xs">
š° Save more this month !
</div>
</div>
)}
</div>
</div>
</>
);
}
Add personal-header.tsx
'use client';
import { useEffect, useState } from 'react';
import { Bell, User, Calendar, Moon, Sun, DollarSign } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { cn } from '@/lib/utils';
export default function Header() {
const [currentDate, setCurrentDate] = useState<string>('');
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
const formattedDate = new Date().toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
});
setCurrentDate(formattedDate);
}, []);
return (
<header className="bg-background/80 fixed top-0 left-0 z-50 w-full border-b backdrop-blur-md">
<div className="flex w-full items-center justify-between px-3 py-3 md:px-6">
{/* Desktop and tablet layout */}
<div className="m-auto flex w-full items-center justify-center text-center">
{currentDate && (
<div className="text-muted-foreground hidden w-full items-center gap-2 text-xs md:flex md:text-sm">
<Calendar className="size-4" />
<span>{currentDate}</span>
</div>
)}
<div
className={cn(
'm-auto flex w-full items-center justify-center gap-1.5 text-center md:hidden',
)}
>
<DollarSign className="text-primary" />
<h1 className="text-xl font-medium">FinDash Pro</h1>
</div>
<div className="block w-auto md:hidden">
<Button
size="icon"
className="!bg-primary rounded-sm text-white hover:!bg-rose-600"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
aria-label="Toggle theme"
>
{mounted &&
(theme === 'dark' ? (
<Sun className="size-5" />
) : (
<Moon className="size-5" />
))}
</Button>
</div>
</div>
<div className="ml-auto hidden w-auto items-center justify-end gap-2 md:flex md:w-full md:max-w-xs">
<Button variant="outline" size="icon" className="relative rounded-sm">
<Bell className="size-5" />
<span className="bg-primary absolute -top-1 -right-1 size-3 rounded-full border border-white" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
className="relative rounded-sm"
>
<User className="size-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="rounded-sm">
<DropdownMenuItem className="hover:!bg-primary rounded hover:!text-white">
Profile
</DropdownMenuItem>
<DropdownMenuItem className="hover:!bg-primary rounded hover:!text-white">
Settings
</DropdownMenuItem>
<DropdownMenuItem className="hover:!bg-primary rounded hover:!text-white">
Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
variant="outline"
size="icon"
className="rounded-sm"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
aria-label="Toggle theme"
>
{mounted &&
(theme === 'dark' ? (
<Sun className="size-5" />
) : (
<Moon className="size-5" />
))}
</Button>
</div>
</div>
</header>
);
}
Add personal-footer.tsx
import React from 'react';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Twitter, Facebook, Linkedin } from 'lucide-react';
export default function Footer() {
const currentYear = new Date().getFullYear();
return (
<footer className="mt-8 flex h-20 w-full flex-col items-center justify-between gap-1 border-t p-5 text-center md:flex-row">
<span className="text-muted-foreground text-xs">
Ā© {currentYear} FinDash Pro. All rights reserved.
</span>
<div className="flex justify-center gap-1">
<Button
variant="ghost"
size="icon"
asChild
className="text-muted-foreground hover:!bg-primary rounded transition-all duration-500 hover:scale-105 hover:text-white"
>
<Link
href="https://twitter.com"
target="_blank"
rel="noopener noreferrer"
aria-label="Twitter"
>
<Twitter className="size-4" />
</Link>
</Button>
<Button
variant="ghost"
size="icon"
asChild
className="text-muted-foreground hover:!bg-primary rounded transition-all duration-500 hover:scale-105 hover:text-white"
>
<Link
href="https://facebook.com"
target="_blank"
rel="noopener noreferrer"
aria-label="Facebook"
>
<Facebook className="size-4" />
</Link>
</Button>
<Button
variant="ghost"
size="icon"
asChild
className="text-muted-foreground hover:!bg-primary rounded transition-all duration-500 hover:scale-105 hover:text-white"
>
<Link
href="https://linkedin.com"
target="_blank"
rel="noopener noreferrer"
aria-label="LinkedIn"
>
<Linkedin className="size-4" />
</Link>
</Button>
</div>
</footer>
);
}
Add stats-cards.tsx
'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
ArrowUpIcon,
ArrowDownIcon,
Wallet,
Target,
TrendingUp,
} from 'lucide-react';
import { Badge } from '@/components/ui/badge';
const stats = [
{
title: 'Total Balance',
value: '$12,456',
change: '+12.4%',
trend: 'up',
icon: Wallet,
description: 'From last month',
gradient: 'from-green-500 to-emerald-500',
},
{
title: 'Monthly Income',
value: '$4,200',
change: '+8.2%',
trend: 'up',
icon: ArrowUpIcon,
description: 'This month',
gradient: 'from-blue-500 to-cyan-500',
},
{
title: 'Monthly Expenses',
value: '$2,843',
change: '-3.1%',
trend: 'down',
icon: ArrowDownIcon,
description: 'This month',
gradient: 'from-orange-500 to-red-500',
},
{
title: 'Savings Rate',
value: '32.4%',
change: '+5.7%',
trend: 'up',
icon: Target,
description: 'Of total income',
gradient: 'from-purple-500 to-pink-500',
},
];
export default function StatsCards() {
return (
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 md:gap-6 xl:grid-cols-4">
{stats.map((stat) => (
<Card
key={stat.title}
className="from-secondary/30 gap-2 overflow-hidden rounded-lg bg-gradient-to-br shadow-none transition-all duration-500 hover:scale-105"
>
<CardHeader className="flex w-full flex-row items-center justify-between">
<CardTitle className="text-lg font-medium">{stat.title}</CardTitle>
<div
className={`rounded-sm bg-gradient-to-br p-2 ${stat.gradient}`}
>
<stat.icon className="size-5 text-white" />
</div>
</CardHeader>
<CardContent>
<div className="mb-3 text-2xl font-bold">{stat.value}</div>
<div className="flex items-center justify-between">
<Badge
variant="secondary"
className="rounded px-3 py-1.5 text-xs"
>
<TrendingUp className="mr-1 size-3" />
{stat.change}
</Badge>
<span className="text-muted-foreground text-xs">
{stat.description}
</span>
</div>
</CardContent>
</Card>
))}
</div>
);
}
Add budget-progress.tsx
'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { AlertTriangle, CheckCircle2, FileText } from 'lucide-react';
const categories = [
{
name: 'Food & Dining',
spent: 320,
budget: 500,
color: 'bg-blue-500',
icon: 'š',
},
{
name: 'Transportation',
spent: 180,
budget: 300,
color: 'bg-green-500',
icon: 'š',
},
{
name: 'Entertainment',
spent: 120,
budget: 200,
color: 'bg-purple-500',
icon: 'š¬',
},
{
name: 'Shopping',
spent: 450,
budget: 400,
color: 'bg-red-500',
icon: 'šļø',
},
{
name: 'Utilities',
spent: 220,
budget: 250,
color: 'bg-yellow-500',
icon: 'š”',
},
{
name: 'Healthcare',
spent: 150,
budget: 200,
color: 'bg-pink-500',
icon: 'š„',
},
];
export default function BudgetProgress() {
const totalSpent = categories.reduce((sum, cat) => sum + cat.spent, 0);
const totalBudget = categories.reduce((sum, cat) => sum + cat.budget, 0);
return (
<Card className="bg-background max-h-[30rem] gap-0 overflow-hidden rounded-lg p-0 shadow-none">
<CardHeader className="bg-background m-auto flex h-16 w-full items-center justify-center border-b p-3 !pb-2 text-center">
<CardTitle className="m-auto flex h-full w-full items-center justify-between text-center">
<div className="flex items-center gap-2">
<FileText className="text-primary size-6" />
<span>Budget Tracking</span>
</div>
<span className="text-base font-medium md:text-lg">
${totalSpent} / ${totalBudget}
</span>
</CardTitle>
</CardHeader>
<CardContent className="bg-background h-full space-y-3 overflow-auto p-3">
{categories.map((category) => {
const percentage = (category.spent / category.budget) * 100;
const isOverBudget = category.spent > category.budget;
const isCloseToBudget = percentage > 80 && !isOverBudget;
return (
<div
key={category.name}
className="from-secondary/30 space-y-3 rounded-md border bg-gradient-to-tl p-5"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 font-medium">
<span>{category.icon}</span>
<span>{category.name}</span>
</div>
<div className="flex items-center space-x-2">
{isOverBudget && (
<AlertTriangle className="size-5 text-red-500" />
)}
{!isOverBudget && percentage >= 100 && (
<CheckCircle2 className="size-5 text-green-500" />
)}
<span
className={`text-sm font-semibold ${
isOverBudget ? 'text-red-500' : ''
}`}
>
${category.spent} / ${category.budget}
</span>
</div>
</div>
<Progress
value={percentage > 100 ? 100 : percentage}
className={`h-2 ${
isOverBudget
? 'bg-red-500/30 [&>div]:bg-red-500'
: isCloseToBudget
? 'bg-yellow-400/30 [&>div]:bg-yellow-400'
: 'bg-green-500/30 [&>div]:bg-green-500'
}`}
/>
<div className="flex justify-between text-xs">
<span>{percentage.toFixed(1)}% of budget</span>
{isOverBudget && (
<span className="font-semibold text-red-500">
Over by ${category.spent - category.budget}
</span>
)}
{isCloseToBudget && !isOverBudget && (
<span className="font-semibold text-yellow-400">
Close to limit
</span>
)}
</div>
</div>
);
})}
</CardContent>
</Card>
);
}
Add income-expense-chart.tsx
'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { BarChart, Bar, CartesianGrid, XAxis, YAxis } from 'recharts';
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from '@/components/ui/chart';
const data = [
{ month: 'Jan', income: 4200, expenses: 2843, net: 1357 },
{ month: 'Feb', income: 3800, expenses: 2950, net: 850 },
{ month: 'Mar', income: 5200, expenses: 3100, net: 2100 },
{ month: 'Apr', income: 4800, expenses: 2650, net: 2150 },
{ month: 'May', income: 5100, expenses: 2900, net: 2200 },
{ month: 'Jun', income: 4700, expenses: 2750, net: 1950 },
{ month: 'Jul', income: 5300, expenses: 3000, net: 2300 },
];
export default function IncomeExpenseChart() {
return (
<Card className="from-secondary/30 rounded-lg bg-gradient-to-t shadow-none">
<CardHeader className="flex flex-col items-center justify-between gap-3 md:flex-row">
<CardTitle>Income vs Expenses</CardTitle>
<div className="flex items-center justify-center gap-2">
<div className="flex items-center gap-2">
<div className="size-3 rounded-full bg-green-500"></div>
<span>Income</span>
</div>
<div className="flex items-center gap-2">
<div className="size-3 rounded-full bg-red-500"></div>
<span>Expenses</span>
</div>
<div className="flex items-center gap-2">
<div className="size-3 rounded-full bg-blue-500"></div>
<span>Net</span>
</div>
</div>
</CardHeader>
<CardContent className="px-0">
<ChartContainer
config={{
income: { label: 'Income', color: '#10b981' },
expenses: { label: 'Expenses', color: '#ef4444' },
net: { label: 'Net', color: '#3b82f6' },
}}
className="h-[15rem] w-full md:h-[20rem]"
>
<BarChart data={data}>
<CartesianGrid vertical={true} />
<XAxis dataKey="month" />
<YAxis />
<ChartTooltip content={<ChartTooltipContent />} />
<Bar
dataKey="income"
fill="var(--color-income)"
radius={[4, 4, 0, 0]}
/>
<Bar
dataKey="expenses"
fill="var(--color-expenses)"
radius={[4, 4, 0, 0]}
/>
<Bar dataKey="net" fill="var(--color-net)" radius={[4, 4, 0, 0]} />
</BarChart>
</ChartContainer>
</CardContent>
</Card>
);
}
Add monthly-spending-chart.tsx
'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
PieChart,
Pie,
Cell,
ResponsiveContainer,
Tooltip,
Legend,
} from 'recharts';
const data = [
{ name: 'Food & Dining', value: 320, color: '#3b82f6' },
{ name: 'Transportation', value: 180, color: '#10b981' },
{ name: 'Entertainment', value: 120, color: '#8b5cf6' },
{ name: 'Shopping', value: 450, color: '#ef4444' },
{ name: 'Utilities', value: 220, color: '#f59e0b' },
{ name: 'Healthcare', value: 150, color: '#ec4899' },
];
interface CustomTooltipProps {
active?: boolean;
payload?: Array<{
name: string;
value: number;
color?: string;
}>;
label?: string;
}
const CustomTooltip = ({ active, payload }: CustomTooltipProps) => {
if (active && payload && payload.length) {
const data = payload[0]; // directly access payload[0] instead of payload[0].payload
return (
<div className="bg-background rounded-md border border-green-500/70 p-3 shadow-xl">
<p className="font-medium">{data.name}</p>
<p className="text-sm" style={{ color: data.color }}>
Amount : <span className="font-bold">${data.value}</span>
</p>
<p className="text-sm">
Percentage : {((data.value / 1440) * 100).toFixed(1)}%
</p>
</div>
);
}
return null;
};
export default function MonthlySpendingChart() {
return (
<Card className="from-secondary/30 rounded-lg bg-gradient-to-t shadow-none">
<CardHeader>
<CardTitle>Monthly Spending by Category</CardTitle>
</CardHeader>
<CardContent>
<div className="h-40 md:h-60">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={data}
cx="50%"
cy="50%"
innerRadius="50%" // relative to chart size
outerRadius="100%"
paddingAngle={2}
dataKey="value"
>
{data.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.color}
className="ml-10"
/>
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
<Legend layout="vertical" verticalAlign="middle" align="right" />
</PieChart>
</ResponsiveContainer>
</div>
<div className="mt-4 grid grid-cols-2 gap-4 border-t pt-4">
<div className="text-center">
<p className="text-2xl font-bold">$1,440</p>
<p className="text-muted-foreground text-sm">Total Spent</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-green-500">$760</p>
<p className="text-muted-foreground text-sm">Remaining Budget</p>
</div>
</div>
</CardContent>
</Card>
);
}
Add recent-transactions.tsx
'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
ArrowUpIcon,
ArrowDownIcon,
Plus,
MoreVertical,
History,
} from 'lucide-react';
const transactions = [
{
id: 1,
description: 'Grocery Store',
amount: -85.2,
category: 'Food & Dining',
date: '2024-01-15',
type: 'expense',
merchant: 'Whole Foods',
},
{
id: 2,
description: 'Salary Deposit',
amount: 3200.0,
category: 'Income',
date: '2024-01-14',
type: 'income',
merchant: 'Company Inc',
},
{
id: 3,
description: 'Netflix Subscription',
amount: -15.99,
category: 'Entertainment',
date: '2024-01-13',
type: 'expense',
merchant: 'Netflix',
},
{
id: 4,
description: 'Gas Station',
amount: -45.0,
category: 'Transportation',
date: '2024-01-12',
type: 'expense',
merchant: 'Shell',
},
{
id: 5,
description: 'Freelance Work',
amount: 850.0,
category: 'Income',
date: '2024-01-11',
type: 'income',
merchant: 'Client Co',
},
{
id: 6,
description: 'Coffee Shop',
amount: -12.5,
category: 'Food & Dining',
date: '2024-01-10',
type: 'expense',
merchant: 'Starbucks',
},
{
id: 7,
description: 'Stock Dividend',
amount: 120.0,
category: 'Income',
date: '2024-01-09',
type: 'income',
merchant: 'Investments LLC',
},
{
id: 8,
description: 'Gym Membership',
amount: -35.0,
category: 'Health & Fitness',
date: '2024-01-08',
type: 'expense',
merchant: 'Gym Co',
},
{
id: 9,
description: 'Dining Out',
amount: -60.0,
category: 'Food & Dining',
date: '2024-01-07',
type: 'expense',
merchant: 'Olive Garden',
},
{
id: 10,
description: 'Project Bonus',
amount: 500.0,
category: 'Income',
date: '2024-01-06',
type: 'income',
merchant: 'Client Inc',
},
];
export default function RecentTransactions() {
return (
<>
<Card className="bg-background gap-0 overflow-hidden rounded-lg p-0 shadow-none">
<CardHeader className="bg-background flex flex-col items-center justify-between gap-2 border-b !p-3 md:flex-row">
<div className="flex items-center gap-2">
<History className="size-10" />
<div className="flex flex-col items-start gap-1">
<CardTitle>
<span>Recent 10 Transactions</span>
</CardTitle>
<span className="text-muted-foreground text-xs italic">
<span className="text-green-500">Green</span> means gain,{' '}
<span className="text-red-500">Red</span> means spend - track
your flow easily
</span>
</div>
</div>
<Button className="bg-primary hover:bg-primary flex w-full items-center gap-2 rounded-sm text-white md:w-auto">
<Plus className="size-4" />
<span>Add New</span>
</Button>
</CardHeader>
<CardContent className="p-0">
<div className="max-h-[25rem] space-y-3 overflow-auto p-3">
{transactions.map((transaction) => (
<div
key={transaction.id}
className="group from-secondary/30 hover:border-primary/50 flex items-center justify-between rounded-md border bg-gradient-to-r p-2"
>
<div className="flex items-center gap-3">
<div
className={`rounded-sm p-3 text-white ${
transaction.type === 'income'
? 'bg-green-500'
: 'bg-red-500'
}`}
>
{transaction.type === 'income' ? (
<ArrowUpIcon className="size-5" />
) : (
<ArrowDownIcon className="size-5" />
)}
</div>
<div>
<p className="text-sm font-medium md:text-base">
{transaction.description}
</p>
<div className="flex flex-col gap-0.5 opacity-60 md:flex-row md:items-center md:gap-2">
<p className="text-[0.65rem] md:text-xs">
{transaction.merchant}
</p>
<span className="hidden md:block">ā¢</span>
<p className="text-[0.65rem] md:text-xs">
{transaction.date}
</p>
</div>
</div>
</div>
<div className="ml-auto flex items-center justify-end gap-1 text-end">
<div className="flex flex-col items-center md:flex-row md:gap-4">
<span
className={`w-full text-sm font-bold md:text-lg ${
transaction.type === 'income'
? 'text-green-500'
: 'text-primary'
}`}
>
{transaction.type === 'income' ? '+' : '-'}$
{Math.abs(transaction.amount).toFixed(2)}
</span>
<Badge
variant="outline"
className="mt-1 ml-auto rounded px-1.5 py-0 text-[0.6rem] md:mt-0 md:px-3 md:py-1.5 md:text-xs"
>
{transaction.category}
</Badge>
</div>
<button>
<MoreVertical className="size-4" />
</button>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</>
);
}
Add savings-goals.tsx
'use client';
import { useEffect, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { Button } from '@/components/ui/button';
import { Target, Calendar, Plus } from 'lucide-react';
const goals = [
{
name: 'Emergency Fund',
target: 10000,
current: 7500,
deadline: '2026-10-31',
color: 'from-blue-500 to-cyan-500',
icon: 'š”ļø',
},
{
name: 'New Car',
target: 25000,
current: 12000,
deadline: '2025-01-30',
color: 'from-green-500 to-emerald-500',
icon: 'š',
},
{
name: 'Vacation',
target: 5000,
current: 3200,
deadline: '2025-12-15',
color: 'from-purple-500 to-pink-500',
icon: 'šļø',
},
];
export default function SavingsGoals() {
const [formattedDates, setFormattedDates] = useState<{
[key: string]: string;
}>({});
useEffect(() => {
const newFormattedDates: { [key: string]: string } = {};
goals.forEach((goal) => {
newFormattedDates[goal.name] = new Date(goal.deadline).toLocaleDateString(
'en-GB',
{
day: '2-digit',
month: 'short',
year: 'numeric',
},
);
});
setFormattedDates(newFormattedDates);
}, []);
return (
<Card className="bg-background max-h-[30rem] gap-0 overflow-hidden rounded-lg p-0 shadow-none">
<CardHeader className="bg-background m-auto flex h-16 w-full flex-row items-center justify-between border-b p-3 !pb-3">
<CardTitle className="m-auto flex h-full w-full items-center gap-2 text-center">
<Target className="text-primary size-6" />
<span>Savings Goals</span>
</CardTitle>
<Button className="flex items-center gap-2 rounded-sm">
<Plus className="size-4" />
<span>Add Goal</span>
</Button>
</CardHeader>
<CardContent className="bg-background h-full space-y-3 overflow-auto p-3">
{goals.map((goal) => {
const percentage = (goal.current / goal.target) * 100;
const monthsLeft = Math.ceil(
(new Date(goal.deadline).getTime() - new Date().getTime()) /
(30 * 24 * 60 * 60 * 1000),
);
return (
<div
key={goal.name}
className="from-secondary/30 space-y-3 rounded-md border bg-gradient-to-tl p-5"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-2xl">{goal.icon}</span>
<div>
<h4 className="font-semibold">{goal.name}</h4>
<p className="text-sm">
Target : ${goal.target.toLocaleString()}
</p>
</div>
</div>
<div className="text-right">
<p className="text-lg font-bold">
${goal.current.toLocaleString()}
</p>
<p className="text-sm">{percentage.toFixed(1)}%</p>
</div>
</div>
<div className="space-y-2">
<Progress
value={percentage}
className="h-2 bg-green-500/20 [&>div]:bg-green-500"
/>
<div className="flex justify-between text-xs">
<span>Saved : ${goal.current.toLocaleString()}</span>
<span>
Left : ${(goal.target - goal.current).toLocaleString()}
</span>
</div>
</div>
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1 text-sm">
<Calendar className="size-4" />
<span>Due : {formattedDates[goal.name] || ''}</span>
</div>
<span
className={`rounded-full border px-3 py-1.5 text-xs font-medium ${
monthsLeft < 3
? 'border-red-500/70 bg-red-500/5 text-red-500'
: monthsLeft < 6
? 'border-yellow-400/70 bg-yellow-400/5 text-yellow-400'
: 'border-green-500/70 bg-green-500/5 text-green-500'
}`}
>
{monthsLeft} months left
</span>
</div>
</div>
);
})}
</CardContent>
</Card>
);
}
Import and use the dashboard in your application
import PersonalFinanceDashboard from '@/components/dashboards/personal-finance-dashboard-1/index';
export default function FinancePage() {
return <PersonalFinanceDashboard />;
}Customization
Adding New Categories
Update the categories array in BudgetProgress.tsx:
const categories = [
{
name: 'Food & Dining',
spent: 320,
budget: 500,
color: 'bg-blue-500',
icon: 'š',
},
// ...add more like this
];Adding New Monthly Spending Data
Update the data array in MonthlySpendingChart.tsx:
const data = [
{ name: 'Food & Dining', value: 320, color: '#3b82f6' },
{ name: 'Transportation', value: 180, color: '#10b981' },
{ name: 'Entertainment', value: 120, color: '#8b5cf6' },
// ...add more like this
];Like this do changes in the data or add new data in particular files.
Admin DashboardNew
A comprehensive admin dashboard with real-time analytics, user management, and system monitoring. Features custom components with minimal shadcn usage.
User Dashboard
User dashboards provide insights into user activity, preferences, and interactions. They help users manage their profiles, settings, and other personal information.
