Personal Finance

Personal Finance Dashboard with budget tracking, expense analytics, savings goals, and transaction management micro UI components.

Folder Structure

budget-progress.tsx
dashboard-layout.tsx
Header.tsx
IncomeExpenseChart.tsx
MonthlySpendingChart.tsx
RecentTransactions.tsx
SavingsGoals.tsx
Footer.tsx
StatsCards.tsx
index.tsx

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-1

Install required dependencies

npm install recharts lucide-react next-theme

or

yarn add recharts lucide-react next-theme

Copy 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.