From ab86d9b5f90ffd0d1aa3b67ea5ce25d7139fab3d Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 22 Mar 2025 10:53:56 +0000 Subject: [PATCH] Investigate budget display issue Examine why budget data is not displaying correctly on the expense and analytics screens, despite being saved successfully. --- src/components/BudgetProgressCard.tsx | 41 ++++++++-- src/components/BudgetTabContent.tsx | 34 ++++++++- src/contexts/budget/budgetUtils.ts | 75 +++++++++++++++---- .../budget/hooks/useBudgetDataState.ts | 75 ++++++++++--------- .../budget/hooks/useExtendedBudgetUpdate.ts | 73 +++++++++++++----- 5 files changed, 223 insertions(+), 75 deletions(-) diff --git a/src/components/BudgetProgressCard.tsx b/src/components/BudgetProgressCard.tsx index f994398..5a73826 100644 --- a/src/components/BudgetProgressCard.tsx +++ b/src/components/BudgetProgressCard.tsx @@ -1,5 +1,5 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import BudgetTabContent from './BudgetTabContent'; import { BudgetPeriod } from '@/contexts/budget/BudgetContext'; @@ -31,21 +31,52 @@ const BudgetProgressCard: React.FC = ({ calculatePercentage, onSaveBudget }) => { - // 컴포넌트 마운트 시 예산 데이터 업데이트 이벤트 발생 + // 데이터 상태 추적 (불일치 감지를 위한 로컬 상태) + const [localBudgetData, setLocalBudgetData] = useState(budgetData); + + // 컴포넌트 마운트 및 budgetData 변경 시 업데이트 useEffect(() => { - console.log("BudgetProgressCard 마운트 - 예산 데이터:", budgetData); + console.log("BudgetProgressCard 데이터 업데이트 - 예산 데이터:", budgetData); + setLocalBudgetData(budgetData); // 지연 작업으로 이벤트 발생 (컴포넌트 마운트 후 데이터 갱신) const timeoutId = setTimeout(() => { window.dispatchEvent(new Event('budgetDataUpdated')); - }, 500); + }, 300); return () => clearTimeout(timeoutId); + }, [budgetData]); + + // budgetDataUpdated 이벤트 감지 + useEffect(() => { + const handleBudgetDataUpdated = () => { + console.log("BudgetProgressCard: 예산 데이터 업데이트 이벤트 감지"); + }; + + window.addEventListener('budgetDataUpdated', handleBudgetDataUpdated); + return () => window.removeEventListener('budgetDataUpdated', handleBudgetDataUpdated); }, []); + // 탭 변경 처리 - 예산 데이터 확인 + const handleTabChange = (value: string) => { + console.log(`탭 변경: ${value}, 현재 예산 데이터:`, { + daily: budgetData.daily.targetAmount, + weekly: budgetData.weekly.targetAmount, + monthly: budgetData.monthly.targetAmount + }); + setSelectedTab(value); + }; + + // 각 탭에 대한 현재 예산 설정 여부 계산 + const isDailyBudgetSet = budgetData.daily.targetAmount > 0; + const isWeeklyBudgetSet = budgetData.weekly.targetAmount > 0; + const isMonthlyBudgetSet = budgetData.monthly.targetAmount > 0; + + console.log(`BudgetProgressCard 상태: 일=${isDailyBudgetSet}, 주=${isWeeklyBudgetSet}, 월=${isMonthlyBudgetSet}`); + return (
- + = ({ const spentAmount = data.spentAmount; const targetAmount = data.targetAmount; - // 예산 설정 여부 확인 (수정: 0보다 큰지만 확인) + // 로그 추가 - 받은 데이터 확인 + useEffect(() => { + console.log(`BudgetTabContent 수신 데이터:`, data); + }, [data]); + + // 전역 예산 데이터가 변경되었을 때 로컬 상태 갱신 + useEffect(() => { + const handleBudgetDataUpdated = () => { + console.log(`BudgetTabContent: 전역 예산 데이터 이벤트 감지, 현재 targetAmount=${targetAmount}`); + // 입력 폼이 열려있고 예산이 설정된 경우 폼 닫기 + if (showBudgetInput && targetAmount > 0) { + console.log('예산이 설정되어 입력 폼을 닫습니다.'); + setShowBudgetInput(false); + } + }; + + window.addEventListener('budgetDataUpdated', handleBudgetDataUpdated); + return () => window.removeEventListener('budgetDataUpdated', handleBudgetDataUpdated); + }, [showBudgetInput, targetAmount]); + + // 예산 설정 여부 확인 - 데이터 targetAmount가 실제로 0보다 큰지 확인 const isBudgetSet = targetAmount > 0; - // 디버깅을 위한 로그 추가 useEffect(() => { - console.log(`BudgetTabContent 렌더링: targetAmount=${targetAmount}, isBudgetSet=${isBudgetSet}`); - }, [targetAmount, isBudgetSet]); + if (isBudgetSet && showBudgetInput) { + console.log('예산이 설정되었으므로 입력 폼을 닫습니다.'); + setShowBudgetInput(false); + } + }, [isBudgetSet, showBudgetInput]); // 실제 백분율 계산 (초과해도 실제 퍼센트로 표시) const actualPercentage = targetAmount > 0 ? Math.round((spentAmount / targetAmount) * 100) : 0; @@ -116,12 +138,16 @@ const BudgetTabContent: React.FC = ({ // 예산 버튼 클릭 핸들러 - 토글 기능 추가 const toggleBudgetInput = () => { + console.log('예산 입력 폼 토글. 현재 상태:', showBudgetInput, '예산 설정 여부:', isBudgetSet); setShowBudgetInput(prev => !prev); }; // 예산 여부에 따른 텍스트 결정 const budgetButtonText = isBudgetSet ? "예산 수정하기" : "예산 입력하기"; + // 화면에 표시할 내용 - 디버깅을 위한 로그 추가 + console.log(`BudgetTabContent 렌더링: targetAmount=${targetAmount}, isBudgetSet=${isBudgetSet}, 표시될 화면:`, isBudgetSet ? "예산 진행 상황" : "예산 입력하기 버튼"); + return (
{isBudgetSet ? ( diff --git a/src/contexts/budget/budgetUtils.ts b/src/contexts/budget/budgetUtils.ts index 1121af4..fb7858f 100644 --- a/src/contexts/budget/budgetUtils.ts +++ b/src/contexts/budget/budgetUtils.ts @@ -63,7 +63,13 @@ export const calculateUpdatedBudgetData = ( type: BudgetPeriod, amount: number ): BudgetData => { - console.log(`예산 업데이트 계산: 타입=${type}, 금액=${amount}`); + console.log(`예산 업데이트 계산 시작: 타입=${type}, 금액=${amount}`); + + // 값이 없거나 유효하지 않은 경우 로깅 + if (!prevBudgetData) { + console.error('이전 예산 데이터가 없습니다. 기본값 사용.'); + prevBudgetData = getInitialBudgetData(); + } // 선택된 타입에 따라 다른 타입의 예산도 자동으로 계산 let monthlyAmount: number, weeklyAmount: number, dailyAmount: number; @@ -71,7 +77,9 @@ export const calculateUpdatedBudgetData = ( if (type === 'monthly') { // 월간 예산이 직접 입력된 경우 monthlyAmount = amount; + // 월 30일 기준 (실제 사용 시 값 확인) dailyAmount = Math.round(monthlyAmount / 30); + // 월 4.3주 기준 (실제 사용 시 값 확인) weeklyAmount = Math.round(monthlyAmount / 4.3); } else if (type === 'weekly') { // 주간 예산이 직접 입력된 경우 @@ -90,29 +98,38 @@ export const calculateUpdatedBudgetData = ( weeklyAmount = Math.max(0, weeklyAmount); dailyAmount = Math.max(0, dailyAmount); - console.log(`최종 예산 계산: 월간=${monthlyAmount}원, 주간=${weeklyAmount}원, 일일=${dailyAmount}원`); + console.log(`최종 예산 계산 결과: 월간=${monthlyAmount}원, 주간=${weeklyAmount}원, 일일=${dailyAmount}원`); - // prevBudgetData 출력하여 디버깅 + // 로그에 이전 예산 데이터 출력 console.log("이전 예산 데이터:", JSON.stringify(prevBudgetData)); + // 이전 지출 데이터 보존 + const dailySpent = prevBudgetData.daily?.spentAmount || 0; + const weeklySpent = prevBudgetData.weekly?.spentAmount || 0; + const monthlySpent = prevBudgetData.monthly?.spentAmount || 0; + // 새 예산 데이터 생성 (spentAmount는 이전 값 유지) - return { + const updatedBudgetData = { daily: { targetAmount: dailyAmount, - spentAmount: prevBudgetData.daily.spentAmount, - remainingAmount: Math.max(0, dailyAmount - prevBudgetData.daily.spentAmount) + spentAmount: dailySpent, + remainingAmount: Math.max(0, dailyAmount - dailySpent) }, weekly: { targetAmount: weeklyAmount, - spentAmount: prevBudgetData.weekly.spentAmount, - remainingAmount: Math.max(0, weeklyAmount - prevBudgetData.weekly.spentAmount) + spentAmount: weeklySpent, + remainingAmount: Math.max(0, weeklyAmount - weeklySpent) }, monthly: { targetAmount: monthlyAmount, - spentAmount: prevBudgetData.monthly.spentAmount, - remainingAmount: Math.max(0, monthlyAmount - prevBudgetData.monthly.spentAmount) + spentAmount: monthlySpent, + remainingAmount: Math.max(0, monthlyAmount - monthlySpent) } }; + + console.log("새 예산 데이터:", JSON.stringify(updatedBudgetData)); + + return updatedBudgetData; }; // 지출액 계산 (일일, 주간, 월간) - 문제 수정 @@ -120,6 +137,8 @@ export const calculateSpentAmounts = ( transactions: Transaction[], prevBudgetData: BudgetData ): BudgetData => { + console.log("지출액 계산 시작, 트랜잭션 수:", transactions.length); + // 지출 거래 필터링 const expenseTransactions = transactions.filter(t => t.type === 'expense'); @@ -140,13 +159,13 @@ export const calculateSpentAmounts = ( // 이번 달 총 지출 계산 const monthlySpent = expenseTransactions.reduce((sum, t) => sum + t.amount, 0); - // 기존 예산 목표 유지 - const dailyTarget = prevBudgetData.daily.targetAmount; - const weeklyTarget = prevBudgetData.weekly.targetAmount; - const monthlyTarget = prevBudgetData.monthly.targetAmount; + // 기존 예산 목표 유지 (없으면 기본값 0) + const dailyTarget = prevBudgetData?.daily?.targetAmount || 0; + const weeklyTarget = prevBudgetData?.weekly?.targetAmount || 0; + const monthlyTarget = prevBudgetData?.monthly?.targetAmount || 0; // 예산 데이터 업데이트 - return { + const updatedBudget = { daily: { targetAmount: dailyTarget, spentAmount: dailySpent, @@ -163,6 +182,10 @@ export const calculateSpentAmounts = ( remainingAmount: Math.max(0, monthlyTarget - monthlySpent) } }; + + console.log("지출액 계산 결과:", updatedBudget); + + return updatedBudget; }; // 초기 예산 데이터 생성 @@ -185,3 +208,25 @@ export const getInitialBudgetData = (): BudgetData => { } }; }; + +// 스토리지에서 안전하게 예산 데이터 가져오기 +export const safelyLoadBudgetData = (defaultData: BudgetData = getInitialBudgetData()): BudgetData => { + try { + const budgetDataStr = localStorage.getItem('budgetData'); + if (budgetDataStr) { + const parsed = JSON.parse(budgetDataStr); + + // 데이터 구조 검증 (daily, weekly, monthly 키 존재 확인) + if (parsed && parsed.daily && parsed.weekly && parsed.monthly) { + return parsed; + } else { + console.warn('저장된 예산 데이터 구조가 유효하지 않습니다. 기본값 사용.'); + } + } + } catch (error) { + console.error('예산 데이터 로드 오류:', error); + } + + // 오류 발생 또는 데이터 없음 시 기본값 반환 + return defaultData; +}; diff --git a/src/contexts/budget/hooks/useBudgetDataState.ts b/src/contexts/budget/hooks/useBudgetDataState.ts index b59d0d7..dc1d947 100644 --- a/src/contexts/budget/hooks/useBudgetDataState.ts +++ b/src/contexts/budget/hooks/useBudgetDataState.ts @@ -9,16 +9,19 @@ import { import { toast } from '@/components/ui/use-toast'; import { calculateUpdatedBudgetData, - calculateSpentAmounts + calculateSpentAmounts, + safelyLoadBudgetData } from '../budgetUtils'; import { Transaction } from '../types'; // 예산 데이터 상태 관리 훅 export const useBudgetDataState = (transactions: Transaction[]) => { - const [budgetData, setBudgetData] = useState(loadBudgetDataFromStorage()); + // 초기 데이터 로드 시 safelyLoadBudgetData 함수 사용 + const [budgetData, setBudgetData] = useState(safelyLoadBudgetData()); const [selectedTab, setSelectedTab] = useState("daily"); const [isInitialized, setIsInitialized] = useState(false); + const [lastUpdateTime, setLastUpdateTime] = useState(0); // 초기 로드 및 이벤트 리스너 설정 - 최적화된 버전 useEffect(() => { @@ -30,54 +33,51 @@ export const useBudgetDataState = (transactions: Transaction[]) => { // 비동기 작업을 마이크로태스크로 지연 await new Promise(resolve => queueMicrotask(() => resolve())); - const loadedData = loadBudgetDataFromStorage(); + // 안전하게 데이터 로드 + const loadedData = safelyLoadBudgetData(); console.log('로드된 예산 데이터:', JSON.stringify(loadedData)); // 새로 로드한 데이터와 현재 데이터가 다를 때만 업데이트 if (JSON.stringify(loadedData) !== JSON.stringify(budgetData)) { - // 상태 업데이트를 마이크로태스크로 지연 - queueMicrotask(() => { - setBudgetData(loadedData); - console.log('예산 데이터 업데이트 완료'); - }); + console.log('새 예산 데이터 감지됨, 상태 업데이트'); + setBudgetData(loadedData); + setLastUpdateTime(Date.now()); } // 초기화 상태 업데이트 if (!isInitialized) { - queueMicrotask(() => { - setIsInitialized(true); - }); + setIsInitialized(true); } } catch (error) { console.error('예산 데이터 로드 중 오류:', error); } }; - // 초기 로드 - 지연 시간 추가 - setTimeout(() => { - loadBudget(); - }, 100); // 지연된 초기 로드 + // 초기 로드 - 직접 호출 + loadBudget(); - // 필수 이벤트만 등록 - const handleBudgetUpdate = () => loadBudget(); + // 필수 이벤트 등록 + const handleBudgetUpdate = () => { + console.log('예산 데이터 업데이트 이벤트 감지'); + loadBudget(); + }; - // 필수 이벤트만 등록 - const budgetUpdateHandler = () => handleBudgetUpdate(); - window.addEventListener('budgetDataUpdated', budgetUpdateHandler); + window.addEventListener('budgetDataUpdated', handleBudgetUpdate); // 스토리지 이벤트도 함께 처리 (다른 탭/창에서의 업데이트 감지) const storageUpdateHandler = (e: StorageEvent) => { if (e.key === 'budgetData' || e.key === null) { + console.log('스토리지 이벤트 감지 - 예산 데이터 업데이트'); handleBudgetUpdate(); } }; window.addEventListener('storage', storageUpdateHandler); return () => { - window.removeEventListener('budgetDataUpdated', budgetUpdateHandler); + window.removeEventListener('budgetDataUpdated', handleBudgetUpdate); window.removeEventListener('storage', storageUpdateHandler); }; - }, [isInitialized, budgetData]); + }, [isInitialized]); // 트랜잭션 변경 시 지출 금액 업데이트 useEffect(() => { @@ -85,19 +85,18 @@ export const useBudgetDataState = (transactions: Transaction[]) => { console.log('트랜잭션 변경으로 인한 예산 데이터 업데이트. 트랜잭션 수:', transactions.length); try { // 현재 예산 데이터 다시 로드 (최신 상태 확보) - const currentBudgetData = loadBudgetDataFromStorage(); + const currentBudgetData = safelyLoadBudgetData(); // 지출 금액 업데이트 const updatedBudgetData = calculateSpentAmounts(transactions, currentBudgetData); // 변경이 있을 때만 저장 if (JSON.stringify(updatedBudgetData) !== JSON.stringify(currentBudgetData)) { + console.log('예산 지출액 업데이트 감지 - 데이터 저장'); // 상태 및 스토리지 모두 업데이트 setBudgetData(updatedBudgetData); saveBudgetDataToStorage(updatedBudgetData); - - // 저장 시간 업데이트 - localStorage.setItem('lastBudgetSaveTime', new Date().toISOString()); + setLastUpdateTime(Date.now()); // 기타 컴포넌트에 이벤트 알림 window.dispatchEvent(new Event('budgetDataUpdated')); @@ -128,7 +127,7 @@ export const useBudgetDataState = (transactions: Transaction[]) => { } // 현재 최신 예산 데이터 로드 (다른 곳에서 변경되었을 수 있음) - const currentBudgetData = loadBudgetDataFromStorage(); + const currentBudgetData = safelyLoadBudgetData(); // 예산 데이터 업데이트 - 일간, 주간, 월간 예산이 모두 자동으로 계산됨 const updatedBudgetData = calculateUpdatedBudgetData(currentBudgetData, type, amount); @@ -137,9 +136,7 @@ export const useBudgetDataState = (transactions: Transaction[]) => { // 상태 및 스토리지 둘 다 업데이트 setBudgetData(updatedBudgetData); saveBudgetDataToStorage(updatedBudgetData); - - // 저장 시간 업데이트 - localStorage.setItem('lastBudgetSaveTime', new Date().toISOString()); + setLastUpdateTime(Date.now()); // 이벤트 발생시켜 다른 컴포넌트에 알림 window.dispatchEvent(new Event('budgetDataUpdated')); @@ -151,7 +148,16 @@ export const useBudgetDataState = (transactions: Transaction[]) => { key: 'budgetData', newValue: JSON.stringify(updatedBudgetData) })); - }, 1000); + }, 500); + + // 2초 후 데이터 갱신 이벤트 한 번 더 발생 + setTimeout(() => { + window.dispatchEvent(new Event('budgetDataUpdated')); + window.dispatchEvent(new StorageEvent('storage', { + key: 'budgetData', + newValue: JSON.stringify(updatedBudgetData) + })); + }, 2000); console.log('예산 목표 업데이트 완료:', updatedBudgetData); @@ -178,8 +184,9 @@ export const useBudgetDataState = (transactions: Transaction[]) => { clearAllBudgetData(); // 로컬스토리지에서 다시 로드 - const freshData = loadBudgetDataFromStorage(); + const freshData = safelyLoadBudgetData(); setBudgetData(freshData); + setLastUpdateTime(Date.now()); // 이벤트 발생시켜 다른 컴포넌트에 알림 window.dispatchEvent(new Event('budgetDataUpdated')); @@ -195,8 +202,8 @@ export const useBudgetDataState = (transactions: Transaction[]) => { // 디버깅 로그 추가 useEffect(() => { - console.log('최신 예산 데이터:', JSON.stringify(budgetData)); - }, [budgetData]); + console.log('최신 예산 데이터:', JSON.stringify(budgetData), '마지막 업데이트:', new Date(lastUpdateTime).toISOString()); + }, [budgetData, lastUpdateTime]); return { budgetData, diff --git a/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts b/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts index ab7b33d..5b892b6 100644 --- a/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts +++ b/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts @@ -17,7 +17,7 @@ export const useExtendedBudgetUpdate = ( amount: number, newCategoryBudgets?: Record ) => { - console.log(`확장된 예산 목표 업데이트: ${type}, 금액: ${amount}, 카테고리 예산:`, newCategoryBudgets); + console.log(`확장된 예산 목표 업데이트 호출: ${type}, 금액: ${amount}, 카테고리 예산:`, newCategoryBudgets); // 카테고리 예산이 제공된 경우 업데이트 if (newCategoryBudgets) { @@ -48,7 +48,7 @@ export const useExtendedBudgetUpdate = ( // 총액 계산 (0 확인) const totalAmount = Object.values(updatedCategoryBudgets).reduce((sum, val) => sum + val, 0); - console.log('카테고리 총액:', totalAmount); + console.log('카테고리 예산 총합:', totalAmount, updatedCategoryBudgets); if (totalAmount <= 0) { toast({ @@ -71,19 +71,39 @@ export const useExtendedBudgetUpdate = ( description: `월간 총 예산이 ${totalAmount.toLocaleString()}원으로 설정되었습니다.` }); - // 여러 번의 이벤트 발생으로 UI 업데이트 보장 (타이밍 문제 해결) + // 다중 이벤트 발생으로 UI 업데이트 보장 + // 0.5초 후 1차 업데이트 setTimeout(() => { + console.log("예산 UI 업데이트 이벤트 발생 (1차)"); window.dispatchEvent(new Event('budgetDataUpdated')); - }, 300); + }, 500); + // 1.5초 후 2차 업데이트 setTimeout(() => { + console.log("예산 UI 업데이트 이벤트 발생 (2차)"); window.dispatchEvent(new Event('budgetDataUpdated')); // 스토리지 이벤트도 발생시켜 데이터 로드 보장 - window.dispatchEvent(new StorageEvent('storage', { - key: 'budgetData', - newValue: localStorage.getItem('budgetData') - })); - }, 1000); + const savedData = localStorage.getItem('budgetData'); + if (savedData) { + window.dispatchEvent(new StorageEvent('storage', { + key: 'budgetData', + newValue: savedData + })); + } + }, 1500); + + // 3초 후 3차 업데이트 (최종 보장) + setTimeout(() => { + console.log("예산 UI 업데이트 이벤트 발생 (3차/최종)"); + window.dispatchEvent(new Event('budgetDataUpdated')); + const savedData = localStorage.getItem('budgetData'); + if (savedData) { + window.dispatchEvent(new StorageEvent('storage', { + key: 'budgetData', + newValue: savedData + })); + } + }, 3000); } catch (error) { console.error('카테고리 예산 업데이트 오류:', error); toast({ @@ -132,19 +152,38 @@ export const useExtendedBudgetUpdate = ( description: `${periodText} 예산이 ${amount.toLocaleString()}원으로 설정되었습니다.` }); - // 데이터 표시 강제 갱신 (타이밍 문제 해결을 위한 여러 이벤트 발생) + // 다중 이벤트 발생 + // 0.5초 후 1차 업데이트 setTimeout(() => { + console.log("예산 UI 업데이트 이벤트 발생 (1차)"); window.dispatchEvent(new Event('budgetDataUpdated')); - }, 300); + }, 500); + // 1.5초 후 2차 업데이트 setTimeout(() => { + console.log("예산 UI 업데이트 이벤트 발생 (2차)"); window.dispatchEvent(new Event('budgetDataUpdated')); - // 스토리지 이벤트도 발생시켜 데이터 로드 보장 - window.dispatchEvent(new StorageEvent('storage', { - key: 'budgetData', - newValue: localStorage.getItem('budgetData') - })); - }, 1000); + const savedData = localStorage.getItem('budgetData'); + if (savedData) { + window.dispatchEvent(new StorageEvent('storage', { + key: 'budgetData', + newValue: savedData + })); + } + }, 1500); + + // 3초 후 3차 업데이트 (최종 보장) + setTimeout(() => { + console.log("예산 UI 업데이트 이벤트 발생 (3차/최종)"); + window.dispatchEvent(new Event('budgetDataUpdated')); + const savedData = localStorage.getItem('budgetData'); + if (savedData) { + window.dispatchEvent(new StorageEvent('storage', { + key: 'budgetData', + newValue: savedData + })); + } + }, 3000); } }, [categoryBudgets, handleBudgetGoalUpdate, updateCategoryBudgets]);