diff --git a/src/components/BudgetTabContent.tsx b/src/components/BudgetTabContent.tsx index 6f41c42..43d19af 100644 --- a/src/components/BudgetTabContent.tsx +++ b/src/components/BudgetTabContent.tsx @@ -1,3 +1,4 @@ + import React, { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Check, ChevronDown, ChevronUp, Calculator } from 'lucide-react'; @@ -6,17 +7,20 @@ import BudgetProgress from './BudgetProgress'; import CategoryBudgetInputs from './CategoryBudgetInputs'; import { toast } from '@/components/ui/use-toast'; import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons'; + interface BudgetData { targetAmount: number; spentAmount: number; remainingAmount: number; } + interface BudgetTabContentProps { data: BudgetData; formatCurrency: (amount: number) => string; calculatePercentage: (spent: number, target: number) => number; onSaveBudget: (amount: number, categoryBudgets?: Record) => void; } + const BudgetTabContent: React.FC = ({ data, formatCurrency, @@ -34,12 +38,15 @@ const BudgetTabContent: React.FC = ({ acc[category] = defaultCategoryAmount; return acc; }, {} as Record); + const [categoryBudgets, setCategoryBudgets] = useState>(initialCategoryBudgets); + const handleInputChange = (value: string) => { // Remove all non-numeric characters const numericValue = value.replace(/[^0-9]/g, ''); setBudgetInput(numericValue); }; + const handleCategoryInputChange = (value: string, category: string) => { // Remove all non-numeric characters const numericValue = value.replace(/[^0-9]/g, ''); @@ -48,6 +55,7 @@ const BudgetTabContent: React.FC = ({ [category]: parseInt(numericValue) || 0 })); }; + const handleSave = () => { // Calculate total from all categories const totalAmount = Object.values(categoryBudgets).reduce((sum, value) => sum + value, 0); @@ -65,8 +73,15 @@ const BudgetTabContent: React.FC = ({ const formatWithCommas = (amount: string) => { return amount.replace(/\B(?=(\d{3})+(?!\d))/g, ','); }; - return
- + + return ( +
+
남은 예산 @@ -81,7 +96,10 @@ const BudgetTabContent: React.FC = ({ - +
@@ -102,6 +120,8 @@ const BudgetTabContent: React.FC = ({
-
; +
+ ); }; -export default BudgetTabContent; \ No newline at end of file + +export default BudgetTabContent; diff --git a/src/contexts/budget/budgetUtils.ts b/src/contexts/budget/budgetUtils.ts index 69fa799..e1a4bab 100644 --- a/src/contexts/budget/budgetUtils.ts +++ b/src/contexts/budget/budgetUtils.ts @@ -67,14 +67,48 @@ export const calculateUpdatedBudgetData = ( remainingAmount: Math.max(0, amount - prevBudgetData.monthly.spentAmount) } }; - } else { - const remainingAmount = Math.max(0, amount - prevBudgetData[type].spentAmount); + } else if (type === 'weekly') { + // 주간 예산이 설정되면 월간 예산도 자동 계산 + const monthlyAmount = Math.round(amount * 4.3); + const dailyAmount = Math.round(amount / 7); + return { - ...prevBudgetData, - [type]: { - ...prevBudgetData[type], + daily: { + targetAmount: dailyAmount, + spentAmount: prevBudgetData.daily.spentAmount, + remainingAmount: Math.max(0, dailyAmount - prevBudgetData.daily.spentAmount) + }, + weekly: { targetAmount: amount, - remainingAmount: remainingAmount + spentAmount: prevBudgetData.weekly.spentAmount, + remainingAmount: Math.max(0, amount - prevBudgetData.weekly.spentAmount) + }, + monthly: { + targetAmount: monthlyAmount, + spentAmount: prevBudgetData.monthly.spentAmount, + remainingAmount: Math.max(0, monthlyAmount - prevBudgetData.monthly.spentAmount) + } + }; + } else { + // 일일 예산이 설정되면 주간/월간 예산도 자동 계산 + const weeklyAmount = Math.round(amount * 7); + const monthlyAmount = Math.round(amount * 30); + + return { + daily: { + targetAmount: amount, + spentAmount: prevBudgetData.daily.spentAmount, + remainingAmount: Math.max(0, amount - prevBudgetData.daily.spentAmount) + }, + weekly: { + targetAmount: weeklyAmount, + spentAmount: prevBudgetData.weekly.spentAmount, + remainingAmount: Math.max(0, weeklyAmount - prevBudgetData.weekly.spentAmount) + }, + monthly: { + targetAmount: monthlyAmount, + spentAmount: prevBudgetData.monthly.spentAmount, + remainingAmount: Math.max(0, monthlyAmount - prevBudgetData.monthly.spentAmount) } }; } diff --git a/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts b/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts index 065a83e..089a130 100644 --- a/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts +++ b/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts @@ -1,47 +1,42 @@ import { useCallback } from 'react'; import { BudgetData, BudgetPeriod } from '../types'; +import { calculateUpdatedBudgetData } from '../budgetUtils'; -/** - * 확장된 예산 업데이트 기능을 제공하는 훅 - */ +// 확장된 예산 업데이트 로직을 제공하는 훅 export const useExtendedBudgetUpdate = ( budgetData: BudgetData, categoryBudgets: Record, - handleBudgetGoalUpdate: (type: BudgetPeriod, amount: number, newCategoryBudgets?: Record) => void, - updateCategoryBudgets: (newCategoryBudgets: Record) => void + handleBudgetGoalUpdate: (type: BudgetPeriod, amount: number) => void, + updateCategoryBudgets: (budgets: Record) => void ) => { - /** - * 확장된 예산 업데이트 함수 - * 월간 예산 및 카테고리 예산을 함께 업데이트 - */ + // 확장된 예산 업데이트 로직 const extendedBudgetGoalUpdate = useCallback(( - type: BudgetPeriod, - amount: number, + type: BudgetPeriod, + amount: number, newCategoryBudgets?: Record ) => { - console.log(`확장된 예산 업데이트: 타입=${type}, 금액=${amount}, 카테고리 예산 포함=${!!newCategoryBudgets}`); + console.log(`확장된 예산 목표 업데이트: ${type}, 금액: ${amount}`, newCategoryBudgets); - // 기본 예산 업데이트 - handleBudgetGoalUpdate(type, amount); - - // 카테고리 예산도 함께 업데이트 (있는 경우) + // 카테고리 예산이 제공된 경우 업데이트 if (newCategoryBudgets) { - console.log('카테고리 예산 업데이트:', newCategoryBudgets); updateCategoryBudgets(newCategoryBudgets); + + // 총액 계산 + const totalAmount = Object.values(newCategoryBudgets).reduce((sum, val) => sum + val, 0); + console.log('카테고리 총액:', totalAmount); + + // 일/주/월 모든 예산 업데이트를 위해 monthly로 처리 + // (monthly 타입은 모든 예산을 계산해 줌) + const updatedBudgetData = calculateUpdatedBudgetData(budgetData, 'monthly', totalAmount); + + // 각 기간별 예산 업데이트 + handleBudgetGoalUpdate('monthly', updatedBudgetData.monthly.targetAmount); + } else { + // 카테고리 예산이 없는 경우, 기존 로직 사용 + handleBudgetGoalUpdate(type, amount); } - - // 데이터 저장 시간 기록 - 동일 세션의 다른 컴포넌트에서 변경 감지 용도 - localStorage.setItem('lastBudgetUpdateTime', new Date().toISOString()); - - // 이벤트 발생 - try { - window.dispatchEvent(new Event('budgetDataUpdated')); - window.dispatchEvent(new Event('categoryBudgetsUpdated')); - } catch (error) { - console.error('이벤트 발생 오류:', error); - } - }, [handleBudgetGoalUpdate, updateCategoryBudgets]); - + }, [budgetData, categoryBudgets, handleBudgetGoalUpdate, updateCategoryBudgets]); + return { extendedBudgetGoalUpdate }; }; diff --git a/src/hooks/useTransactions.ts b/src/hooks/useTransactions.ts index 8f028ae..38f1fd7 100644 --- a/src/hooks/useTransactions.ts +++ b/src/hooks/useTransactions.ts @@ -7,8 +7,7 @@ import { isSyncEnabled } from '@/utils/syncUtils'; import { MONTHS_KR, getCurrentMonth, getPrevMonth, getNextMonth } from '@/utils/dateUtils'; import { loadTransactionsFromStorage, - saveTransactionsToStorage, - loadBudgetFromStorage + saveTransactionsToStorage } from '@/utils/storageUtils'; import { filterTransactionsByMonth, @@ -32,7 +31,7 @@ export const useTransactions = () => { const [searchQuery, setSearchQuery] = useState(''); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - const [totalBudget, setTotalBudget] = useState(1000000); // 기본 예산 + const [totalBudget, setTotalBudget] = useState(0); const { user } = useAuth(); // 월 변경 처리 @@ -69,8 +68,16 @@ export const useTransactions = () => { setTransactions(filteredData); // 예산 가져오기 - const budget = loadBudgetFromStorage(); - setTotalBudget(budget); + const budgetDataStr = localStorage.getItem('budgetData'); + if (budgetDataStr) { + try { + const budgetData = JSON.parse(budgetDataStr); + setTotalBudget(budgetData.monthly.targetAmount); + } catch (e) { + console.error('예산 데이터 파싱 오류:', e); + setTotalBudget(0); + } + } } catch (err) { console.error('트랜잭션 로드 중 오류:', err); setError('데이터를 불러오는 중 문제가 발생했습니다.'); @@ -113,6 +120,31 @@ export const useTransactions = () => { }; syncWithSupabase(); + + // 예산 데이터 변경 이벤트 리스너 + const handleBudgetUpdate = () => { + const budgetDataStr = localStorage.getItem('budgetData'); + if (budgetDataStr) { + try { + const budgetData = JSON.parse(budgetDataStr); + setTotalBudget(budgetData.monthly.targetAmount); + } catch (e) { + console.error('예산 데이터 파싱 오류:', e); + } + } + }; + + window.addEventListener('budgetDataUpdated', handleBudgetUpdate); + window.addEventListener('storage', (e) => { + if (e.key === 'budgetData') { + handleBudgetUpdate(); + } + }); + + return () => { + window.removeEventListener('budgetDataUpdated', handleBudgetUpdate); + window.removeEventListener('storage', () => {}); + }; }, [user]); // 트랜잭션 업데이트