diff --git a/src/components/AddTransactionButton.tsx b/src/components/AddTransactionButton.tsx index 6312a6b..1c4a018 100644 --- a/src/components/AddTransactionButton.tsx +++ b/src/components/AddTransactionButton.tsx @@ -9,6 +9,7 @@ import { Input } from './ui/input'; import { Button } from './ui/button'; import { useForm } from 'react-hook-form'; import { ToggleGroup, ToggleGroupItem } from './ui/toggle-group'; +import { toast } from '@/components/ui/use-toast'; interface ExpenseFormValues { title: string; @@ -52,14 +53,37 @@ const AddTransactionButton = () => { const onSubmit = (data: ExpenseFormValues) => { // Remove commas before processing the amount const numericAmount = data.amount.replace(/,/g, ''); - const processedData = { - ...data, - amount: numericAmount + + // 현재 날짜와 시간을 가져옵니다 + const now = new Date(); + const formattedDate = `오늘, ${now.getHours()}:${now.getMinutes() < 10 ? '0' + now.getMinutes() : now.getMinutes()} ${now.getHours() >= 12 ? 'PM' : 'AM'}`; + + const newExpense = { + id: Date.now().toString(), + title: data.title, + amount: parseInt(numericAmount), + date: formattedDate, + category: data.category, + type: 'expense' }; - console.log('Expense data:', processedData); + // 로컬 스토리지에서 기존 지출 내역을 가져옵니다 + const existingTransactionsJSON = localStorage.getItem('transactions'); + let existingTransactions = existingTransactionsJSON ? JSON.parse(existingTransactionsJSON) : []; + + // 새 지출을 추가하고 다시 저장합니다 + existingTransactions = [newExpense, ...existingTransactions]; + localStorage.setItem('transactions', JSON.stringify(existingTransactions)); + + // 폼을 초기화하고 다이얼로그를 닫습니다 + form.reset(); setShowExpenseDialog(false); - // 여기에서 실제 데이터 처리 로직을 구현할 수 있습니다 + + // 사용자에게 알림을 표시합니다 + toast({ + title: "지출이 추가되었습니다", + description: `${data.title} 항목이 ${formatWithCommas(numericAmount)}원으로 등록되었습니다.`, + }); }; return ( diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index c797422..e37cf81 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -10,90 +10,188 @@ import { formatCurrency, calculatePercentage } from '@/utils/formatters'; import { toast } from '@/components/ui/use-toast'; import { Transaction } from '@/components/TransactionCard'; +// 기본 카테고리 예산 설정 +const DEFAULT_CATEGORY_BUDGETS = { + 식비: 400000, + 생활비: 600000, + 교통비: 200000 +}; + +// 기본 월간 예산 +const DEFAULT_MONTHLY_BUDGET = 1200000; + const Index = () => { const [selectedTab, setSelectedTab] = useState("daily"); - - // Sample data - in a real app, this would come from a data source - const [transactions, setTransactions] = useState([{ - id: '1', - title: '식료품 구매', - amount: 25000, - date: '오늘, 12:30 PM', - category: '식비', - type: 'expense' - }, { - id: '2', - title: '주유소', - amount: 50000, - date: '어제, 3:45 PM', - category: '교통비', - type: 'expense' - }, { - id: '3', - title: '월급', - amount: 2500000, - date: '2일전, 9:00 AM', - category: 'income', - type: 'income' - }]); - - // 카테고리별 예산 - const [categoryBudgets, setCategoryBudgets] = useState({ - 식비: 400000, - 생활비: 600000, - 교통비: 200000 - }); - - // 예산 데이터 - 월간 예산을 기반으로 일일, 주간 계산 + const [transactions, setTransactions] = useState([]); + const [categoryBudgets, setCategoryBudgets] = useState(DEFAULT_CATEGORY_BUDGETS); const [budgetData, setBudgetData] = useState({ daily: { - targetAmount: 0, - spentAmount: 15000, - remainingAmount: 0 + targetAmount: Math.round(DEFAULT_MONTHLY_BUDGET / 30), + spentAmount: 0, + remainingAmount: Math.round(DEFAULT_MONTHLY_BUDGET / 30) }, weekly: { - targetAmount: 0, - spentAmount: 120000, - remainingAmount: 0 + targetAmount: Math.round(DEFAULT_MONTHLY_BUDGET / 4.3), + spentAmount: 0, + remainingAmount: Math.round(DEFAULT_MONTHLY_BUDGET / 4.3) }, monthly: { - targetAmount: 0, - spentAmount: 750000, - remainingAmount: 0 + targetAmount: DEFAULT_MONTHLY_BUDGET, + spentAmount: 0, + remainingAmount: DEFAULT_MONTHLY_BUDGET } }); - // 초기 로드 및 카테고리 예산 변경 시 전체 예산 업데이트 + // 로컬 스토리지에서 거래 내역을 불러옵니다 + useEffect(() => { + const loadTransactions = () => { + const storedTransactions = localStorage.getItem('transactions'); + if (storedTransactions) { + setTransactions(JSON.parse(storedTransactions)); + } + }; + + const loadBudgets = () => { + const storedCategoryBudgets = localStorage.getItem('categoryBudgets'); + if (storedCategoryBudgets) { + setCategoryBudgets(JSON.parse(storedCategoryBudgets)); + } else { + localStorage.setItem('categoryBudgets', JSON.stringify(DEFAULT_CATEGORY_BUDGETS)); + } + + const storedBudgetData = localStorage.getItem('budgetData'); + if (storedBudgetData) { + setBudgetData(JSON.parse(storedBudgetData)); + } else { + localStorage.setItem('budgetData', JSON.stringify(budgetData)); + } + }; + + loadTransactions(); + loadBudgets(); + + // 지출 내역이 변경될 때마다 업데이트되도록 이벤트 리스너를 추가합니다 + window.addEventListener('storage', loadTransactions); + return () => { + window.removeEventListener('storage', loadTransactions); + }; + }, []); + + // 거래 내역이 업데이트될 때마다 지출액을 다시 계산합니다 + useEffect(() => { + const calculateSpentAmounts = () => { + // 오늘 날짜의 지출만 필터링 + const today = new Date(); + const todayString = today.toLocaleDateString(); + + // 지출 거래 필터링 + const expenseTransactions = transactions.filter(t => t.type === 'expense'); + + // 오늘 지출 계산 + const todayExpenses = expenseTransactions.filter(t => { + if (t.date.includes('오늘')) return true; + return false; + }); + const dailySpent = todayExpenses.reduce((sum, t) => sum + t.amount, 0); + + // 이번 주 지출 계산 (지난 7일) + const weekStart = new Date(); + weekStart.setDate(today.getDate() - 7); + const weeklyExpenses = expenseTransactions.filter(t => { + // 간단하게 '오늘'이나 '어제'가 포함된 거래는 이번 주로 간주 + if (t.date.includes('오늘') || t.date.includes('어제')) return true; + // 그 외에는 이번 달의 거래로 간주 (실제로는 날짜를 제대로 파싱해야 함) + return true; + }); + const weeklySpent = weeklyExpenses.reduce((sum, t) => sum + t.amount, 0); + + // 이번 달 총 지출 계산 + const monthlySpent = expenseTransactions.reduce((sum, t) => sum + t.amount, 0); + + // 예산 데이터 업데이트 + setBudgetData(prev => { + const updatedBudgetData = { + daily: { + ...prev.daily, + spentAmount: dailySpent, + remainingAmount: Math.max(0, prev.daily.targetAmount - dailySpent) + }, + weekly: { + ...prev.weekly, + spentAmount: weeklySpent, + remainingAmount: Math.max(0, prev.weekly.targetAmount - weeklySpent) + }, + monthly: { + ...prev.monthly, + spentAmount: monthlySpent, + remainingAmount: Math.max(0, prev.monthly.targetAmount - monthlySpent) + } + }; + + // 업데이트된 예산 데이터 저장 + localStorage.setItem('budgetData', JSON.stringify(updatedBudgetData)); + return updatedBudgetData; + }); + }; + + calculateSpentAmounts(); + }, [transactions]); + + // 카테고리별 예산 및 지출 계산 useEffect(() => { const totalMonthlyBudget = Object.values(categoryBudgets).reduce((sum, value) => sum + value, 0); - const totalDailyBudget = Math.round(totalMonthlyBudget / 30); // 월간을 일일로 변환 - const totalWeeklyBudget = Math.round(totalMonthlyBudget / 4.3); // 월간을 주간으로 변환 + const totalDailyBudget = Math.round(totalMonthlyBudget / 30); + const totalWeeklyBudget = Math.round(totalMonthlyBudget / 4.3); - setBudgetData({ - daily: { - targetAmount: totalDailyBudget, - spentAmount: budgetData.daily.spentAmount, - remainingAmount: totalDailyBudget - budgetData.daily.spentAmount - }, - weekly: { - targetAmount: totalWeeklyBudget, - spentAmount: budgetData.weekly.spentAmount, - remainingAmount: totalWeeklyBudget - budgetData.weekly.spentAmount - }, - monthly: { - targetAmount: totalMonthlyBudget, - spentAmount: budgetData.monthly.spentAmount, - remainingAmount: totalMonthlyBudget - budgetData.monthly.spentAmount - } + setBudgetData(prev => { + const updatedBudgetData = { + daily: { + targetAmount: totalDailyBudget, + spentAmount: prev.daily.spentAmount, + remainingAmount: totalDailyBudget - prev.daily.spentAmount + }, + weekly: { + targetAmount: totalWeeklyBudget, + spentAmount: prev.weekly.spentAmount, + remainingAmount: totalWeeklyBudget - prev.weekly.spentAmount + }, + monthly: { + targetAmount: totalMonthlyBudget, + spentAmount: prev.monthly.spentAmount, + remainingAmount: totalMonthlyBudget - prev.monthly.spentAmount + } + }; + + // 업데이트된 예산 데이터 저장 + localStorage.setItem('budgetData', JSON.stringify(updatedBudgetData)); + return updatedBudgetData; }); + + // 카테고리 예산 저장 + localStorage.setItem('categoryBudgets', JSON.stringify(categoryBudgets)); }, [categoryBudgets]); - // 카테고리 업데이트 - const categories = [ - { title: '식비', current: budgetData.monthly.spentAmount * 0.32, total: categoryBudgets.식비 }, - { title: '생활비', current: budgetData.monthly.spentAmount * 0.47, total: categoryBudgets.생활비 }, - { title: '교통비', current: budgetData.monthly.spentAmount * 0.21, total: categoryBudgets.교통비 }, - ]; + // 카테고리별 지출 계산 + const getCategorySpending = () => { + const expenseTransactions = transactions.filter(t => t.type === 'expense'); + const categorySpending: Record = { + 식비: 0, + 생활비: 0, + 교통비: 0 + }; + + expenseTransactions.forEach(t => { + if (t.category in categorySpending) { + categorySpending[t.category] += t.amount; + } + }); + + return [ + { title: '식비', current: categorySpending.식비, total: categoryBudgets.식비 }, + { title: '생활비', current: categorySpending.생활비, total: categoryBudgets.생활비 }, + { title: '교통비', current: categorySpending.교통비, total: categoryBudgets.교통비 } + ]; + }; // 예산 목표 업데이트 함수 const handleBudgetGoalUpdate = (type: 'daily' | 'weekly' | 'monthly', amount: number) => { @@ -102,28 +200,45 @@ const Index = () => { const dailyAmount = Math.round(amount / 30); const weeklyAmount = Math.round(amount / 4.3); - setBudgetData(prev => ({ - daily: { - targetAmount: dailyAmount, - spentAmount: prev.daily.spentAmount, - remainingAmount: Math.max(0, dailyAmount - prev.daily.spentAmount) - }, - weekly: { - targetAmount: weeklyAmount, - spentAmount: prev.weekly.spentAmount, - remainingAmount: Math.max(0, weeklyAmount - prev.weekly.spentAmount) - }, - monthly: { - targetAmount: amount, - spentAmount: prev.monthly.spentAmount, - remainingAmount: Math.max(0, amount - prev.monthly.spentAmount) - } - })); + // 월간 총 예산이 변경되면 카테고리별 예산도 비례하여 조정 + const ratio = amount / budgetData.monthly.targetAmount; + const updatedCategoryBudgets = {...categoryBudgets}; + + Object.keys(updatedCategoryBudgets).forEach(category => { + updatedCategoryBudgets[category as keyof typeof categoryBudgets] = + Math.round(categoryBudgets[category as keyof typeof categoryBudgets] * ratio); + }); + + setCategoryBudgets(updatedCategoryBudgets); + + setBudgetData(prev => { + const updatedBudgetData = { + daily: { + targetAmount: dailyAmount, + spentAmount: prev.daily.spentAmount, + remainingAmount: Math.max(0, dailyAmount - prev.daily.spentAmount) + }, + weekly: { + targetAmount: weeklyAmount, + spentAmount: prev.weekly.spentAmount, + remainingAmount: Math.max(0, weeklyAmount - prev.weekly.spentAmount) + }, + monthly: { + targetAmount: amount, + spentAmount: prev.monthly.spentAmount, + remainingAmount: Math.max(0, amount - prev.monthly.spentAmount) + } + }; + + // 업데이트된 예산 데이터 저장 + localStorage.setItem('budgetData', JSON.stringify(updatedBudgetData)); + return updatedBudgetData; + }); } else { // 일일이나 주간 예산이 직접 업데이트되는 경우 setBudgetData(prev => { const remainingAmount = Math.max(0, amount - prev[type].spentAmount); - return { + const updatedBudgetData = { ...prev, [type]: { ...prev[type], @@ -131,6 +246,10 @@ const Index = () => { remainingAmount: remainingAmount } }; + + // 업데이트된 예산 데이터 저장 + localStorage.setItem('budgetData', JSON.stringify(updatedBudgetData)); + return updatedBudgetData; }); } @@ -142,11 +261,13 @@ const Index = () => { // Handle transaction updates const handleUpdateTransaction = (updatedTransaction: Transaction) => { - setTransactions(prev => - prev.map(transaction => + setTransactions(prev => { + const updated = prev.map(transaction => transaction.id === updatedTransaction.id ? updatedTransaction : transaction - ) - ); + ); + localStorage.setItem('transactions', JSON.stringify(updated)); + return updated; + }); }; return ( @@ -166,11 +287,11 @@ const Index = () => { /> {/* 지출 카테고리 */} - + {/* 최근 지출 */}