From c92d41e8f0ed5bcf4630c59d300d92ea20c8ded0 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 08:26:29 +0000 Subject: [PATCH] Address unresolved issues This commit addresses previously reported issues that remain unresolved. Further investigation is required. --- src/components/BudgetProgressCard.tsx | 14 +- src/components/BudgetTabContent.tsx | 7 +- src/contexts/budget/budgetUtils.ts | 2 +- .../budget/hooks/useBudgetDataState.ts | 32 ++- .../budget/hooks/useExtendedBudgetUpdate.ts | 45 +++- src/utils/storageUtils.ts | 230 ++++++------------ 6 files changed, 162 insertions(+), 168 deletions(-) diff --git a/src/components/BudgetProgressCard.tsx b/src/components/BudgetProgressCard.tsx index f25265b..f994398 100644 --- a/src/components/BudgetProgressCard.tsx +++ b/src/components/BudgetProgressCard.tsx @@ -1,5 +1,5 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import BudgetTabContent from './BudgetTabContent'; import { BudgetPeriod } from '@/contexts/budget/BudgetContext'; @@ -31,6 +31,18 @@ const BudgetProgressCard: React.FC = ({ calculatePercentage, onSaveBudget }) => { + // 컴포넌트 마운트 시 예산 데이터 업데이트 이벤트 발생 + useEffect(() => { + console.log("BudgetProgressCard 마운트 - 예산 데이터:", budgetData); + + // 지연 작업으로 이벤트 발생 (컴포넌트 마운트 후 데이터 갱신) + const timeoutId = setTimeout(() => { + window.dispatchEvent(new Event('budgetDataUpdated')); + }, 500); + + return () => clearTimeout(timeoutId); + }, []); + return (
diff --git a/src/components/BudgetTabContent.tsx b/src/components/BudgetTabContent.tsx index 83fbbd0..95e9bc5 100644 --- a/src/components/BudgetTabContent.tsx +++ b/src/components/BudgetTabContent.tsx @@ -29,7 +29,7 @@ const BudgetTabContent: React.FC = ({ const spentAmount = data.spentAmount; const targetAmount = data.targetAmount; - // 예산 설정 여부 확인 (더 엄격하게 체크) + // 예산 설정 여부 확인 (수정: 0보다 큰지만 확인) const isBudgetSet = targetAmount > 0; // 디버깅을 위한 로그 추가 @@ -87,6 +87,11 @@ const BudgetTabContent: React.FC = ({ if (totalBudget > 0) { onSaveBudget(totalBudget, updatedCategoryBudgets); setShowBudgetInput(false); + + // 이벤트 발생 추가 (데이터 저장 후 즉시 UI 업데이트를 위해) + setTimeout(() => { + window.dispatchEvent(new Event('budgetDataUpdated')); + }, 200); } else { alert('예산을 입력해주세요.'); } diff --git a/src/contexts/budget/budgetUtils.ts b/src/contexts/budget/budgetUtils.ts index 0f73641..1121af4 100644 --- a/src/contexts/budget/budgetUtils.ts +++ b/src/contexts/budget/budgetUtils.ts @@ -92,10 +92,10 @@ export const calculateUpdatedBudgetData = ( console.log(`최종 예산 계산: 월간=${monthlyAmount}원, 주간=${weeklyAmount}원, 일일=${dailyAmount}원`); - // 새로운 BudgetData를 생성하되, 기존의 spentAmount는 유지합니다 // prevBudgetData 출력하여 디버깅 console.log("이전 예산 데이터:", JSON.stringify(prevBudgetData)); + // 새 예산 데이터 생성 (spentAmount는 이전 값 유지) return { daily: { targetAmount: dailyAmount, diff --git a/src/contexts/budget/hooks/useBudgetDataState.ts b/src/contexts/budget/hooks/useBudgetDataState.ts index 1eb9330..b59d0d7 100644 --- a/src/contexts/budget/hooks/useBudgetDataState.ts +++ b/src/contexts/budget/hooks/useBudgetDataState.ts @@ -65,8 +65,17 @@ export const useBudgetDataState = (transactions: Transaction[]) => { const budgetUpdateHandler = () => handleBudgetUpdate(); window.addEventListener('budgetDataUpdated', budgetUpdateHandler); + // 스토리지 이벤트도 함께 처리 (다른 탭/창에서의 업데이트 감지) + const storageUpdateHandler = (e: StorageEvent) => { + if (e.key === 'budgetData' || e.key === null) { + handleBudgetUpdate(); + } + }; + window.addEventListener('storage', storageUpdateHandler); + return () => { window.removeEventListener('budgetDataUpdated', budgetUpdateHandler); + window.removeEventListener('storage', storageUpdateHandler); }; }, [isInitialized, budgetData]); @@ -75,23 +84,29 @@ export const useBudgetDataState = (transactions: Transaction[]) => { if (transactions.length > 0 && isInitialized) { console.log('트랜잭션 변경으로 인한 예산 데이터 업데이트. 트랜잭션 수:', transactions.length); try { + // 현재 예산 데이터 다시 로드 (최신 상태 확보) + const currentBudgetData = loadBudgetDataFromStorage(); + // 지출 금액 업데이트 - const updatedBudgetData = calculateSpentAmounts(transactions, budgetData); + const updatedBudgetData = calculateSpentAmounts(transactions, currentBudgetData); // 변경이 있을 때만 저장 - if (JSON.stringify(updatedBudgetData) !== JSON.stringify(budgetData)) { + if (JSON.stringify(updatedBudgetData) !== JSON.stringify(currentBudgetData)) { // 상태 및 스토리지 모두 업데이트 setBudgetData(updatedBudgetData); saveBudgetDataToStorage(updatedBudgetData); // 저장 시간 업데이트 localStorage.setItem('lastBudgetSaveTime', new Date().toISOString()); + + // 기타 컴포넌트에 이벤트 알림 + window.dispatchEvent(new Event('budgetDataUpdated')); } } catch (error) { console.error('예산 데이터 업데이트 중 오류:', error); } } - }, [transactions, budgetData, isInitialized]); + }, [transactions, isInitialized]); // 예산 목표 업데이트 함수 - 수정됨 const handleBudgetGoalUpdate = useCallback(( @@ -112,8 +127,11 @@ export const useBudgetDataState = (transactions: Transaction[]) => { return; } + // 현재 최신 예산 데이터 로드 (다른 곳에서 변경되었을 수 있음) + const currentBudgetData = loadBudgetDataFromStorage(); + // 예산 데이터 업데이트 - 일간, 주간, 월간 예산이 모두 자동으로 계산됨 - const updatedBudgetData = calculateUpdatedBudgetData(budgetData, type, amount); + const updatedBudgetData = calculateUpdatedBudgetData(currentBudgetData, type, amount); console.log('새 예산 데이터 계산됨:', updatedBudgetData); // 상태 및 스토리지 둘 다 업데이트 @@ -129,6 +147,10 @@ export const useBudgetDataState = (transactions: Transaction[]) => { // 1초 후 데이터 갱신 이벤트 한 번 더 발생 (UI 갱신 확실히 하기 위함) setTimeout(() => { window.dispatchEvent(new Event('budgetDataUpdated')); + window.dispatchEvent(new StorageEvent('storage', { + key: 'budgetData', + newValue: JSON.stringify(updatedBudgetData) + })); }, 1000); console.log('예산 목표 업데이트 완료:', updatedBudgetData); @@ -147,7 +169,7 @@ export const useBudgetDataState = (transactions: Transaction[]) => { variant: "destructive" }); } - }, [budgetData]); + }, []); // 예산 데이터 초기화 함수 const resetBudgetData = useCallback(() => { diff --git a/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts b/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts index bfc6066..ab7b33d 100644 --- a/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts +++ b/src/contexts/budget/hooks/useExtendedBudgetUpdate.ts @@ -59,8 +59,7 @@ export const useExtendedBudgetUpdate = ( return; } - // 월간 예산 금액으로 예산 데이터 업데이트 - // 월간 예산을 설정하면 자동으로 일간/주간 예산도 계산됨 + // 항상 월간 예산으로 처리하여 일/주간 자동계산 보장 handleBudgetGoalUpdate('monthly', totalAmount); // 명시적으로 BudgetData 업데이트 이벤트 발생 @@ -72,9 +71,18 @@ export const useExtendedBudgetUpdate = ( description: `월간 총 예산이 ${totalAmount.toLocaleString()}원으로 설정되었습니다.` }); - // 1초 후 페이지 리프레시 (데이터 표시 강제 갱신) + // 여러 번의 이벤트 발생으로 UI 업데이트 보장 (타이밍 문제 해결) setTimeout(() => { window.dispatchEvent(new Event('budgetDataUpdated')); + }, 300); + + setTimeout(() => { + window.dispatchEvent(new Event('budgetDataUpdated')); + // 스토리지 이벤트도 발생시켜 데이터 로드 보장 + window.dispatchEvent(new StorageEvent('storage', { + key: 'budgetData', + newValue: localStorage.getItem('budgetData') + })); }, 1000); } catch (error) { console.error('카테고리 예산 업데이트 오류:', error); @@ -86,7 +94,6 @@ export const useExtendedBudgetUpdate = ( } } else { // 카테고리 예산이 없는 경우, 선택된 기간 유형에 맞게 예산 설정 - // 이 경우에도 다른 기간의 예산이 자동으로 계산됨 if (amount <= 0) { toast({ title: "예산 설정 오류", @@ -96,7 +103,24 @@ export const useExtendedBudgetUpdate = ( return; } - handleBudgetGoalUpdate(type, amount); + // 어떤 타입이 들어오더라도 항상 월간으로 처리하고 일/주간은 자동계산 + if (type !== 'monthly') { + console.log(`${type} 입력을 월간 예산으로 변환합니다.`); + // 일간 입력인 경우 월간으로 변환 (30배) + if (type === 'daily') { + amount = amount * 30; + } + // 주간 입력인 경우 월간으로 변환 (4.3배) + else if (type === 'weekly') { + amount = Math.round(amount * 4.3); + } + + // 변환된 금액으로 월간 예산 설정 + handleBudgetGoalUpdate('monthly', amount); + } else { + // 원래 월간이면 그대로 설정 + handleBudgetGoalUpdate('monthly', amount); + } // 명시적으로 BudgetData 업데이트 이벤트 발생 window.dispatchEvent(new Event('budgetDataUpdated')); @@ -108,9 +132,18 @@ export const useExtendedBudgetUpdate = ( description: `${periodText} 예산이 ${amount.toLocaleString()}원으로 설정되었습니다.` }); - // 1초 후 페이지 리프레시 (데이터 표시 강제 갱신) + // 데이터 표시 강제 갱신 (타이밍 문제 해결을 위한 여러 이벤트 발생) setTimeout(() => { window.dispatchEvent(new Event('budgetDataUpdated')); + }, 300); + + setTimeout(() => { + window.dispatchEvent(new Event('budgetDataUpdated')); + // 스토리지 이벤트도 발생시켜 데이터 로드 보장 + window.dispatchEvent(new StorageEvent('storage', { + key: 'budgetData', + newValue: localStorage.getItem('budgetData') + })); }, 1000); } }, [categoryBudgets, handleBudgetGoalUpdate, updateCategoryBudgets]); diff --git a/src/utils/storageUtils.ts b/src/utils/storageUtils.ts index c8a2949..4cd201e 100644 --- a/src/utils/storageUtils.ts +++ b/src/utils/storageUtils.ts @@ -1,175 +1,97 @@ -import { Transaction } from '@/components/TransactionCard'; +/** + * 스토리지 관련 유틸리티 함수 + */ -// 트랜잭션 데이터 불러오기 -export const loadTransactionsFromStorage = (): Transaction[] => { - try { - const localData = localStorage.getItem('transactions'); - if (localData) { - return JSON.parse(localData) as Transaction[]; - } - - // 백업에서 복구 시도 - const backupData = localStorage.getItem('transactions_backup'); - if (backupData) { - console.log('트랜잭션 데이터 백업에서 복구'); - localStorage.setItem('transactions', backupData); // 메인 스토리지 복원 - return JSON.parse(backupData) as Transaction[]; - } - } catch (error) { - console.error('트랜잭션 데이터 로드 중 오류:', error); - } - - // 데이터가 없을 경우 빈 배열 반환 (샘플 데이터 생성하지 않음) - return []; -}; +// 보존할 항목 목록 +const PRESERVE_KEYS = [ + 'dontShowWelcome', + 'hasVisitedBefore', + // Supabase 인증 관련 키는 보존 + 'supabase.auth.token', + 'sb-', + 'supabase-auth', +]; -// 트랜잭션 데이터 저장하기 -export const saveTransactionsToStorage = (transactions: Transaction[]): void => { - try { - const dataString = JSON.stringify(transactions); - localStorage.setItem('transactions', dataString); - - // 백업 저장 (데이터 손실 방지) - localStorage.setItem('transactions_backup', dataString); - - // 이벤트 발생 - window.dispatchEvent(new Event('transactionUpdated')); - } catch (error) { - console.error('트랜잭션 저장 중 오류:', error); - } -}; - -// 예산 불러오기 (이전 버전과의 호환성 유지) -export const loadBudgetFromStorage = (): number => { - try { - // 새 구조의 budgetData 확인 - const budgetData = localStorage.getItem('budgetData'); - if (budgetData) { - const parsedBudgetData = JSON.parse(budgetData); - if (parsedBudgetData.monthly && typeof parsedBudgetData.monthly.targetAmount === 'number') { - return parsedBudgetData.monthly.targetAmount; - } - } - - // 기존 구조 확인 - const budget = localStorage.getItem('budget'); - if (budget) { - const parsedBudget = JSON.parse(budget); - return parsedBudget.total || 0; - } - } catch (error) { - console.error('예산 데이터 로드 중 오류:', error); - } - - return 0; -}; - -// 모든 데이터 완전히 초기화 - 성능 최적화 +/** + * 모든 로컬 스토리지 데이터 초기화 (보존할 항목 제외) + */ export const resetAllStorageData = (): void => { - console.log('완전 초기화 시작 - resetAllStorageData'); - try { - // 중요: 사용자 설정 값 백업 - const dontShowWelcomeValue = localStorage.getItem('dontShowWelcome'); - const hasVisitedBefore = localStorage.getItem('hasVisitedBefore'); - // 로그인 상태 관련 데이터 백업 - const authSession = localStorage.getItem('authSession'); - const sbAuth = localStorage.getItem('sb-auth-token'); - const supabase = localStorage.getItem('supabase.auth.token'); + console.log('[스토리지 초기화] 시작'); - // 모든 Storage 키 목록 (로그인 관련 항목 제외) - const keysToRemove = [ - 'transactions', - 'budget', - 'monthlyExpenses', - 'budgetData', - 'categoryBudgets', - 'analyticData', - 'expenseData', - 'chartData', - 'monthlyData', - 'spendingData', - 'categorySpending', - 'monthlyBudget', - 'dailyBudget', - 'weeklyBudget', - 'monthlyTotals', - 'analytics', - 'expenseHistory', - 'budgetHistory', - 'transactionHistory', - 'lastSync', - 'syncEnabled' - ]; + // 보존할 항목 백업 + const preservedItems: Record = {}; - // 키 동시에 삭제 (성능 최적화) - keysToRemove.forEach(key => { - console.log(`삭제 중: ${key}`); - localStorage.removeItem(key); - localStorage.removeItem(`${key}_backup`); // 백업 키도 함께 삭제 + // 로컬스토리지 키 목록 가져오기 + const keys: string[] = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key) keys.push(key); + } + + // 보존할 항목 백업 + keys.forEach(key => { + const shouldPreserve = PRESERVE_KEYS.some(pattern => + key === pattern || (pattern.endsWith('-') && key.startsWith(pattern)) + ); + + if (shouldPreserve) { + preservedItems[key] = localStorage.getItem(key); + console.log(`[스토리지 초기화] 보존 항목: ${key}`); + } }); - // 기본값으로 초기화 - 한번에 처리 - const defaultData = { - transactions: JSON.stringify([]), - budgetData: JSON.stringify({ - daily: {targetAmount: 0, spentAmount: 0, remainingAmount: 0}, - weekly: {targetAmount: 0, spentAmount: 0, remainingAmount: 0}, - monthly: {targetAmount: 0, spentAmount: 0, remainingAmount: 0} - }), - categoryBudgets: JSON.stringify({ - 식비: 0, - 교통비: 0, - 생활비: 0 - }) - }; + // 모든 데이터 삭제 + localStorage.clear(); + console.log('[스토리지 초기화] 모든 데이터 삭제됨'); - // 모든 기본값 한번에 설정 - Object.entries(defaultData).forEach(([key, value]) => { - localStorage.setItem(key, value); - localStorage.setItem(`${key}_backup`, value); + // 보존할 항목 복원 + Object.entries(preservedItems).forEach(([key, value]) => { + if (value !== null) { + localStorage.setItem(key, value); + console.log(`[스토리지 초기화] 항목 복원: ${key}`); + } }); - // 사용자 설정 값 복원 - if (dontShowWelcomeValue) { - localStorage.setItem('dontShowWelcome', dontShowWelcomeValue); + // 완전 삭제 확인을 위한 백업 데이터도 삭제 + if (localStorage.getItem('transactions_backup')) { + localStorage.removeItem('transactions_backup'); + console.log('[스토리지 초기화] 트랜잭션 백업 데이터 삭제됨'); } - if (hasVisitedBefore) { - localStorage.setItem('hasVisitedBefore', hasVisitedBefore); + if (localStorage.getItem('budgetData_backup')) { + localStorage.removeItem('budgetData_backup'); + console.log('[스토리지 초기화] 예산 백업 데이터 삭제됨'); } - // 로그인 상태 복원 - if (authSession) { - localStorage.setItem('authSession', authSession); + if (localStorage.getItem('categoryBudgets_backup')) { + localStorage.removeItem('categoryBudgets_backup'); + console.log('[스토리지 초기화] 카테고리 예산 백업 삭제됨'); } - if (sbAuth) { - localStorage.setItem('sb-auth-token', sbAuth); - } - - if (supabase) { - localStorage.setItem('supabase.auth.token', supabase); - } - - // 동기화 설정은 무조건 OFF로 설정 - localStorage.setItem('syncEnabled', 'false'); - console.log('동기화 설정이 OFF로 변경되었습니다'); - - // 모든 이벤트 한 번에 발생 (성능 최적화) - const events = [ - new Event('transactionUpdated'), - new Event('budgetDataUpdated'), - new Event('categoryBudgetsUpdated'), - new StorageEvent('storage'), - new Event('auth-state-changed') - ]; - - events.forEach(event => window.dispatchEvent(event)); - - console.log('모든 저장소 데이터가 완전히 초기화되었습니다. (동기화 설정이 OFF로 변경됨)'); + console.log('[스토리지 초기화] 완료'); } catch (error) { - console.error('데이터 초기화 중 오류:', error); + console.error('[스토리지 초기화] 오류:', error); + throw error; + } +}; + +/** + * 특정 키에 해당하는 데이터 삭제 + */ +export const clearStorageItem = (key: string): void => { + try { + localStorage.removeItem(key); + console.log(`[스토리지] ${key} 데이터 삭제됨`); + + // 관련 백업 데이터도 삭제 + const backupKey = `${key}_backup`; + if (localStorage.getItem(backupKey)) { + localStorage.removeItem(backupKey); + console.log(`[스토리지] ${backupKey} 데이터 삭제됨`); + } + } catch (error) { + console.error(`[스토리지] ${key} 삭제 중 오류:`, error); } };