import React, { useState, useEffect } from 'react'; import { BarChart, LineChart, Line, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { PieChart, Pie, Cell } from 'recharts'; import { Upload, DollarSign, Calendar, TrendingUp, Menu, X, Plus, Edit2, Trash2, FileText, Save } from 'lucide-react'; export default function FinanceManager() { // State management const [transactions, setTransactions] = useState([]); const [categories, setCategories] = useState([ 'Housing', 'Food', 'Transportation', 'Utilities', 'Entertainment', 'Healthcare', 'Shopping', 'Personal', 'Debt', 'Income', 'Other' ]); const [budgets, setBudgets] = useState({}); const [activeTab, setActiveTab] = useState('dashboard'); const [isMenuOpen, setIsMenuOpen] = useState(false); const [isAddBudgetModalOpen, setIsAddBudgetModalOpen] = useState(false); const [newBudget, setNewBudget] = useState({ month: '', category: '', amount: '' }); const [forecastMonths, setForecastMonths] = useState(3); const [csvParseError, setCsvParseError] = useState(''); // Load data from localStorage on component mount useEffect(() => { const savedTransactions = localStorage.getItem('transactions'); const savedBudgets = localStorage.getItem('budgets'); if (savedTransactions) { setTransactions(JSON.parse(savedTransactions)); } if (savedBudgets) { setBudgets(JSON.parse(savedBudgets)); } }, []); // Save data to localStorage when it changes useEffect(() => { localStorage.setItem('transactions', JSON.stringify(transactions)); }, [transactions]); useEffect(() => { localStorage.setItem('budgets', JSON.stringify(budgets)); }, [budgets]); // CSV file handling const handleFileUpload = (event) => { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (e) => { try { const csvData = parseCSV(e.target.result); setTransactions([...transactions, ...csvData]); setCsvParseError(''); } catch (error) { setCsvParseError('Error parsing CSV file. Please check the format.'); console.error('CSV parsing error:', error); } }; reader.readAsText(file); } }; // Parse CSV data const parseCSV = (csvText) => { const lines = csvText.split('\n'); const headers = lines[0].split(',').map(header => header.trim()); // Check if required columns exist const dateIndex = headers.findIndex(h => h.toLowerCase().includes('date')); const amountIndex = headers.findIndex(h => h.toLowerCase().includes('amount')); const descriptionIndex = headers.findIndex(h => h.toLowerCase().includes('description')); if (dateIndex === -1 || amountIndex === -1 || descriptionIndex === -1) { throw new Error('CSV must contain date, amount, and description columns'); } const parsedData = []; for (let i = 1; i < lines.length; i++) { if (!lines[i].trim()) continue; const values = lines[i].split(',').map(value => value.trim()); if (values.length >= Math.max(dateIndex, amountIndex, descriptionIndex) + 1) { const transaction = { id: Date.now() + i, date: new Date(values[dateIndex]), amount: parseFloat(values[amountIndex].replace(/[^0-9.-]+/g, '')), description: values[descriptionIndex], category: guessCategory(values[descriptionIndex]) }; parsedData.push(transaction); } } return parsedData; }; // Guess category based on description const guessCategory = (description) => { description = description.toLowerCase(); if (description.includes('rent') || description.includes('mortgage')) return 'Housing'; if (description.includes('grocery') || description.includes('restaurant') || description.includes('food')) return 'Food'; if (description.includes('gas') || description.includes('uber') || description.includes('lyft') || description.includes('transit')) return 'Transportation'; if (description.includes('electric') || description.includes('water') || description.includes('internet') || description.includes('phone')) return 'Utilities'; if (description.includes('movie') || description.includes('netflix') || description.includes('spotify')) return 'Entertainment'; if (description.includes('doctor') || description.includes('pharmacy') || description.includes('medical')) return 'Healthcare'; if (description.includes('amazon') || description.includes('walmart') || description.includes('target')) return 'Shopping'; if (description.includes('loan') || description.includes('credit card')) return 'Debt'; if (description.includes('salary') || description.includes('paycheck') || description.includes('deposit')) return 'Income'; return 'Other'; }; // Update transaction category const updateTransactionCategory = (id, category) => { setTransactions(transactions.map(transaction => transaction.id === id ? { ...transaction, category } : transaction )); }; // Delete transaction const deleteTransaction = (id) => { setTransactions(transactions.filter(transaction => transaction.id !== id)); }; // Add or update budget const handleAddBudget = () => { if (!newBudget.month || !newBudget.category || !newBudget.amount) return; setBudgets({ ...budgets, [newBudget.month]: { ...(budgets[newBudget.month] || {}), [newBudget.category]: parseFloat(newBudget.amount) } }); setNewBudget({ month: '', category: '', amount: '' }); setIsAddBudgetModalOpen(false); }; // Calculate spending by category for a specific month const getSpendingByCategory = (month) => { const filteredTransactions = transactions.filter(transaction => { const transactionMonth = `${transaction.date.getFullYear()}-${String(transaction.date.getMonth() + 1).padStart(2, '0')}`; return transactionMonth === month && transaction.amount < 0; }); const spending = {}; categories.forEach(category => { spending[category] = 0; }); filteredTransactions.forEach(transaction => { spending[transaction.category] = (spending[transaction.category] || 0) + Math.abs(transaction.amount); }); return spending; }; // Calculate total income for a specific month const getTotalIncome = (month) => { const filteredTransactions = transactions.filter(transaction => { const transactionMonth = `${transaction.date.getFullYear()}-${String(transaction.date.getMonth() + 1).padStart(2, '0')}`; return transactionMonth === month && transaction.amount > 0; }); return filteredTransactions.reduce((sum, transaction) => sum + transaction.amount, 0); }; // Calculate total expenses for a specific month const getTotalExpenses = (month) => { const filteredTransactions = transactions.filter(transaction => { const transactionMonth = `${transaction.date.getFullYear()}-${String(transaction.date.getMonth() + 1).padStart(2, '0')}`; return transactionMonth === month && transaction.amount < 0; }); return Math.abs(filteredTransactions.reduce((sum, transaction) => sum + transaction.amount, 0)); }; // Get current month in YYYY-MM format const getCurrentMonth = () => { const now = new Date(); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; }; // Get list of months with transactions const getMonthsWithTransactions = () => { const months = new Set(); transactions.forEach(transaction => { const month = `${transaction.date.getFullYear()}-${String(transaction.date.getMonth() + 1).padStart(2, '0')}`; months.add(month); }); return Array.from(months).sort(); }; // Generate forecast data const generateForecast = () => { const months = getMonthsWithTransactions(); if (months.length === 0) return []; const forecastData = []; const historicalData = {}; // Calculate average spending by category categories.forEach(category => { let totalSpending = 0; let monthCount = 0; months.forEach(month => { const spending = getSpendingByCategory(month); if (spending[category] > 0) { totalSpending += spending[category]; monthCount++; } }); historicalData[category] = monthCount > 0 ? totalSpending / monthCount : 0; }); // Generate forecast for future months const lastMonth = months[months.length - 1]; const [lastYear, lastMonthNum] = lastMonth.split('-').map(num => parseInt(num)); for (let i = 1; i <= forecastMonths; i++) { let forecastMonth = lastMonthNum + i; let forecastYear = lastYear; if (forecastMonth > 12) { forecastMonth -= 12; forecastYear += 1; } const monthStr = `${forecastYear}-${String(forecastMonth).padStart(2, '0')}`; const forecastSpending = { ...historicalData }; forecastData.push({ month: monthStr, ...forecastSpending }); } return forecastData; }; // Format currency const formatCurrency = (amount) => { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount); }; // Format date const formatDate = (date) => { return new Date(date).toLocaleDateString(); }; // Dashboard data const currentMonth = getCurrentMonth(); const monthlySpending = getSpendingByCategory(currentMonth); const totalIncome = getTotalIncome(currentMonth); const totalExpenses = getTotalExpenses(currentMonth); const netSavings = totalIncome - totalExpenses; // Prepare data for charts const pieChartData = Object.entries(monthlySpending) .filter(([_, value]) => value > 0) .map(([name, value]) => ({ name, value })); const barChartData = getMonthsWithTransactions().map(month => ({ month, income: getTotalIncome(month), expenses: getTotalExpenses(month) })); const COLORS = [ '#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8', '#82CA9D', '#FF6B6B', '#6A7FDB', '#61DAFB', '#F06292', '#BA68C8' ]; // Budget progress calculation const getBudgetProgress = (category) => { const budget = budgets[currentMonth]?.[category] || 0; const spent = monthlySpending[category] || 0; if (budget === 0) return 0; return Math.min((spent / budget) * 100, 100); }; return (
Upload a CSV file with your transaction data
{csvParseError && ({csvParseError}
)}{formatCurrency(totalIncome)}
Current month
{formatCurrency(totalExpenses)}
Current month
= 0 ? 'text-teal-600' : 'text-red-500'}`}> {formatCurrency(netSavings)}
Current month
No budgets set for this month
)}| Date | Description | Amount | Category | Actions |
|---|---|---|---|---|
| {formatDate(transaction.date)} | {transaction.description} | = 0 ? 'text-green-600' : 'text-red-500'}`}> {formatCurrency(transaction.amount)} |
No transactions yet
No budgets set yet
Not enough data for forecasting. Please import transactions first.
| Month | {categories.map(category => ({category} | ))}Total |
|---|---|---|
| {forecast.month} | {categories.map(category => ({formatCurrency(forecast[category] || 0)} | ))}{formatCurrency( Object.entries(forecast) .filter(([key]) => key !== 'month') .reduce((sum, [_, value]) => sum + value, 0) )} |
Not enough data for detailed forecasting
)}