Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
390 changes: 376 additions & 14 deletions src/components/TrackExpenses.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,382 @@
import React from "react";
import { Link } from "react-router-dom";
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { useTransactions } from './TransactionContext';
import { useCurrency } from './CurrencyContext';
import { motion } from 'framer-motion';
import {
Plus,
TrendingUp,
TrendingDown,
Calendar,
Filter,
Search,
ArrowLeft,
BarChart3,
PieChart,
DollarSign,
Receipt,
Target,
Sparkles
} from 'lucide-react';
import AddTransactionModal from './AddTransactionModal';

const TrackExpenses = () => {
const { transactions, income, expense } = useTransactions();
const { currency, locale } = useCurrency();
const [showAddModal, setShowAddModal] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [filterType, setFilterType] = useState('All');
const [filterCategory, setFilterCategory] = useState('All');
const [sortBy, setSortBy] = useState('date');

const getCurrencySymbol = (currency, locale) => {
return (0).toLocaleString(locale, {
style: "currency",
currency,
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).replace(/\d/g, "").trim();
};

const formatAmount = (amount) => {
return amount.toLocaleString(locale, {
style: 'currency',
currency,
minimumFractionDigits: 0,
maximumFractionDigits: 2,
});
};

const getCategoryIcon = (category) => {
const icons = {
'Food': 'πŸ•',
'Transport': 'πŸš—',
'Groceries': 'πŸ›’',
'Entertainment': '🎬',
'Bills': 'πŸ“„',
'Shopping': 'πŸ›οΈ',
'Rent': '🏠',
'Utilities': '⚑',
'EMI': 'πŸ’³',
'Income': 'πŸ’°',
'Others': 'πŸ“¦'
};
return icons[category] || 'πŸ“Š';
};

const getCategoryColor = (category) => {
const colors = {
'Food': 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300',
'Transport': 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300',
'Groceries': 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300',
'Entertainment': 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300',
'Bills': 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300',
'Shopping': 'bg-pink-100 text-pink-800 dark:bg-pink-900/30 dark:text-pink-300',
'Rent': 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900/30 dark:text-indigo-300',
'Utilities': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300',
'EMI': 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300',
'Income': 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-300',
'Others': 'bg-slate-100 text-slate-800 dark:bg-slate-900/30 dark:text-slate-300'
};
return colors[category] || 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300';
};

const filteredTransactions = transactions
.filter(transaction => {
const matchesSearch = transaction.note.toLowerCase().includes(searchTerm.toLowerCase()) ||
transaction.category.toLowerCase().includes(searchTerm.toLowerCase());
const matchesType = filterType === 'All' || transaction.type === filterType;
const matchesCategory = filterCategory === 'All' || transaction.category === filterCategory;
return matchesSearch && matchesType && matchesCategory;
})
.sort((a, b) => {
switch (sortBy) {
case 'date':
return new Date(b.date.split('/').reverse().join('-')) - new Date(a.date.split('/').reverse().join('-'));
case 'amount':
return b.amount - a.amount;
case 'category':
return a.category.localeCompare(b.category);
default:
return 0;
}
});

const categories = [...new Set(transactions.map(t => t.category))].filter(Boolean);
const recentTransactions = filteredTransactions.slice(0, 5);
const totalBalance = income - expense;

const cardVariants = {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
hover: { y: -5, transition: { type: "spring", stiffness: 300 } }
};

return (
<div style={{
textAlign: "center",
marginTop: "300px",
fontSize: "32px",
color: "hotpink",
fontWeight: "bolder"
}} className="TrackExpenses">
Track Expenses Page Coming Soon .....
</div>


)
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-purple-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
{/* Header */}
<div className="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center space-x-4">
<Link
to="/"
className="flex items-center space-x-2 text-gray-600 dark:text-gray-300 hover:text-purple-600 dark:hover:text-purple-400 transition-colors"
>
<ArrowLeft className="w-5 h-5" />
<span>Back to Home</span>
</Link>
<div className="h-6 w-px bg-gray-300 dark:bg-gray-600"></div>
<div className="flex items-center space-x-2">
<Sparkles className="w-6 h-6 text-purple-500" />
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Track Expenses</h1>
</div>
</div>

<button
onClick={() => setShowAddModal(true)}
className="bg-gradient-to-r from-purple-500 to-pink-500 text-white px-6 py-2 rounded-full font-semibold hover:shadow-lg hover:scale-105 transition-all duration-200 flex items-center space-x-2"
>
<Plus className="w-5 h-5" />
<span>Add Transaction</span>
</button>
</div>
</div>
</div>

<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<motion.div
variants={cardVariants}
initial="initial"
animate="animate"
whileHover="hover"
className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-lg border border-gray-200 dark:border-gray-700"
>
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">Total Balance</p>
<p className={`text-2xl font-bold ${totalBalance >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>
{formatAmount(totalBalance)}
</p>
</div>
<div className={`p-3 rounded-full ${totalBalance >= 0 ? 'bg-green-100 dark:bg-green-900/30' : 'bg-red-100 dark:bg-red-900/30'}`}>
<DollarSign className={`w-6 h-6 ${totalBalance >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`} />
</div>
</div>
</motion.div>

<motion.div
variants={cardVariants}
initial="initial"
animate="animate"
whileHover="hover"
transition={{ delay: 0.1 }}
className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-lg border border-gray-200 dark:border-gray-700"
>
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">Total Income</p>
<p className="text-2xl font-bold text-green-600 dark:text-green-400">
{formatAmount(income)}
</p>
</div>
<div className="p-3 rounded-full bg-green-100 dark:bg-green-900/30">
<TrendingUp className="w-6 h-6 text-green-600 dark:text-green-400" />
</div>
</div>
</motion.div>

<motion.div
variants={cardVariants}
initial="initial"
animate="animate"
whileHover="hover"
transition={{ delay: 0.2 }}
className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-lg border border-gray-200 dark:border-gray-700"
>
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">Total Expenses</p>
<p className="text-2xl font-bold text-red-600 dark:text-red-400">
{formatAmount(expense)}
</p>
</div>
<div className="p-3 rounded-full bg-red-100 dark:bg-red-900/30">
<TrendingDown className="w-6 h-6 text-red-600 dark:text-red-400" />
</div>
</div>
</motion.div>
</div>

{/* Filters and Search */}
<div className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-lg border border-gray-200 dark:border-gray-700 mb-8">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
placeholder="Search transactions..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>

<select
value={filterType}
onChange={(e) => setFilterType(e.target.value)}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="All">All Types</option>
<option value="Income">Income</option>
<option value="Expense">Expense</option>
</select>

<select
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="All">All Categories</option>
{categories.map(category => (
<option key={category} value={category}>{category}</option>
))}
</select>

<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="date">Sort by Date</option>
<option value="amount">Sort by Amount</option>
<option value="category">Sort by Category</option>
</select>
</div>
</div>

{/* Recent Transactions */}
<div className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-lg border border-gray-200 dark:border-gray-700 mb-8">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold text-gray-900 dark:text-white flex items-center space-x-2">
<Receipt className="w-5 h-5 text-purple-500" />
<span>Recent Transactions</span>
</h2>
<Link
to="/expense-history"
className="text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 font-medium"
>
View All
</Link>
</div>

{recentTransactions.length === 0 ? (
<div className="text-center py-12">
<div className="w-16 h-16 bg-gray-100 dark:bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-4">
<Receipt className="w-8 h-8 text-gray-400" />
</div>
<p className="text-gray-500 dark:text-gray-400">No transactions yet</p>
<p className="text-sm text-gray-400 dark:text-gray-500">Add your first transaction to get started</p>
</div>
) : (
<div className="space-y-4">
{recentTransactions.map((transaction) => (
<motion.div
key={transaction.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors"
>
<div className="flex items-center space-x-4">
<div className="text-2xl">{getCategoryIcon(transaction.category)}</div>
<div>
<p className="font-medium text-gray-900 dark:text-white">{transaction.note}</p>
<div className="flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400">
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getCategoryColor(transaction.category)}`}>
{transaction.category}
</span>
<span className="flex items-center space-x-1">
<Calendar className="w-3 h-3" />
<span>{transaction.date}</span>
</span>
</div>
</div>
</div>
<div className={`text-right ${transaction.type === 'Income' ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>
<p className="font-bold text-lg">
{transaction.type === 'Income' ? '+' : '-'}{formatAmount(transaction.amount)}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">{transaction.type}</p>
</div>
</motion.div>
))}
</div>
)}
</div>

{/* Quick Actions */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<motion.div
variants={cardVariants}
initial="initial"
animate="animate"
whileHover="hover"
className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-lg border border-gray-200 dark:border-gray-700"
>
<div className="flex items-center space-x-4">
<div className="p-3 bg-blue-100 dark:bg-blue-900/30 rounded-full">
<BarChart3 className="w-6 h-6 text-blue-600 dark:text-blue-400" />
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Smart Analytics</h3>
<p className="text-gray-600 dark:text-gray-400">Get insights into your spending patterns</p>
<Link
to="/smart-analytics"
className="inline-block mt-2 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium"
>
View Analytics β†’
</Link>
</div>
</div>
</motion.div>

<motion.div
variants={cardVariants}
initial="initial"
animate="animate"
whileHover="hover"
transition={{ delay: 0.1 }}
className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-lg border border-gray-200 dark:border-gray-700"
>
<div className="flex items-center space-x-4">
<div className="p-3 bg-purple-100 dark:bg-purple-900/30 rounded-full">
<Target className="w-6 h-6 text-purple-600 dark:text-purple-400" />
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Budget Goals</h3>
<p className="text-gray-600 dark:text-gray-400">Set and track your financial goals</p>
<Link
to="/budget-goals"
className="inline-block mt-2 text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 font-medium"
>
Manage Goals β†’
</Link>
</div>
</div>
</motion.div>
</div>
</div>

{/* Add Transaction Modal */}
<AddTransactionModal
showModal={showAddModal}
setShowModal={setShowAddModal}
darkMode={false}
/>
</div>
);
};

export default TrackExpenses;