Fix budget data persistence issue
Addresses the problem where budget data was not persisting across page transitions, causing budget and expense information to disappear from the expense page and only expense data to appear on the analytics page.
This commit is contained in:
@@ -25,7 +25,12 @@ export const useBudgetDataState = (transactions: any[]) => {
|
||||
console.log('예산 데이터 로드 시도 중...');
|
||||
const loadedData = loadBudgetDataFromStorage();
|
||||
console.log('예산 데이터 로드됨:', loadedData);
|
||||
setBudgetData(loadedData);
|
||||
|
||||
// 새로 로드한 데이터와 현재 데이터가 다를 때만 업데이트
|
||||
if (JSON.stringify(loadedData) !== JSON.stringify(budgetData)) {
|
||||
console.log('예산 데이터 변경 감지됨, 상태 업데이트');
|
||||
setBudgetData(loadedData);
|
||||
}
|
||||
|
||||
// 최근 데이터 로드 시간 기록
|
||||
localStorage.setItem('lastBudgetDataLoadTime', new Date().toISOString());
|
||||
@@ -49,6 +54,7 @@ export const useBudgetDataState = (transactions: any[]) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 이벤트 발생 시 데이터 새로고침
|
||||
window.addEventListener('budgetDataUpdated', () => handleBudgetUpdate());
|
||||
window.addEventListener('storage', handleBudgetUpdate);
|
||||
window.addEventListener('visibilitychange', () => {
|
||||
@@ -62,7 +68,7 @@ export const useBudgetDataState = (transactions: any[]) => {
|
||||
loadBudget();
|
||||
});
|
||||
|
||||
// 주기적 데이터 검사 (1초마다)
|
||||
// 주기적 데이터 검사 (1초마다) - 다른 컴포넌트에서 변경된 사항 감지
|
||||
const intervalId = setInterval(() => {
|
||||
const lastSaveTime = localStorage.getItem('lastBudgetSaveTime');
|
||||
const lastLoadTime = localStorage.getItem('lastBudgetDataLoadTime');
|
||||
@@ -80,27 +86,30 @@ export const useBudgetDataState = (transactions: any[]) => {
|
||||
window.removeEventListener('focus', () => loadBudget());
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, [isInitialized]);
|
||||
}, [isInitialized, budgetData]);
|
||||
|
||||
// 트랜잭션 변경 시 지출 금액 업데이트
|
||||
useEffect(() => {
|
||||
if (transactions.length > 0) {
|
||||
if (transactions.length > 0 && isInitialized) {
|
||||
console.log('트랜잭션 변경으로 인한 예산 데이터 업데이트. 트랜잭션 수:', transactions.length);
|
||||
try {
|
||||
// 지출 금액 업데이트
|
||||
const updatedBudgetData = calculateSpentAmounts(transactions, budgetData);
|
||||
|
||||
// 상태 및 스토리지 모두 업데이트
|
||||
setBudgetData(updatedBudgetData);
|
||||
saveBudgetDataToStorage(updatedBudgetData);
|
||||
// 변경이 있을 때만 저장
|
||||
if (JSON.stringify(updatedBudgetData) !== JSON.stringify(budgetData)) {
|
||||
// 상태 및 스토리지 모두 업데이트
|
||||
setBudgetData(updatedBudgetData);
|
||||
saveBudgetDataToStorage(updatedBudgetData);
|
||||
|
||||
// 저장 시간 업데이트
|
||||
localStorage.setItem('lastBudgetSaveTime', new Date().toISOString());
|
||||
// 저장 시간 업데이트
|
||||
localStorage.setItem('lastBudgetSaveTime', new Date().toISOString());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('예산 데이터 업데이트 중 오류:', error);
|
||||
}
|
||||
}
|
||||
}, [transactions, budgetData]);
|
||||
}, [transactions, budgetData, isInitialized]);
|
||||
|
||||
// 예산 목표 업데이트 함수
|
||||
const handleBudgetGoalUpdate = useCallback((
|
||||
@@ -114,6 +123,8 @@ export const useBudgetDataState = (transactions: any[]) => {
|
||||
if (!newCategoryBudgets) {
|
||||
const updatedBudgetData = calculateUpdatedBudgetData(budgetData, type, amount);
|
||||
console.log('새 예산 데이터:', updatedBudgetData);
|
||||
|
||||
// 상태 및 스토리지 둘 다 업데이트
|
||||
setBudgetData(updatedBudgetData);
|
||||
saveBudgetDataToStorage(updatedBudgetData);
|
||||
|
||||
|
||||
@@ -1,72 +1,47 @@
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { BudgetPeriod, BudgetData } from '../types';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { BudgetData, BudgetPeriod } from '../types';
|
||||
|
||||
// 확장된 예산 목표 업데이트 훅
|
||||
/**
|
||||
* 확장된 예산 업데이트 기능을 제공하는 훅
|
||||
*/
|
||||
export const useExtendedBudgetUpdate = (
|
||||
budgetData: BudgetData,
|
||||
categoryBudgets: Record<string, number>,
|
||||
handleBudgetGoalUpdate: (type: BudgetPeriod, amount: number) => void,
|
||||
handleBudgetGoalUpdate: (type: BudgetPeriod, amount: number, newCategoryBudgets?: Record<string, number>) => void,
|
||||
updateCategoryBudgets: (newCategoryBudgets: Record<string, number>) => void
|
||||
) => {
|
||||
// 확장된 예산 목표 업데이트 함수
|
||||
/**
|
||||
* 확장된 예산 업데이트 함수
|
||||
* 월간 예산 및 카테고리 예산을 함께 업데이트
|
||||
*/
|
||||
const extendedBudgetGoalUpdate = useCallback((
|
||||
type: BudgetPeriod,
|
||||
amount: number,
|
||||
newCategoryBudgets?: Record<string, number>
|
||||
) => {
|
||||
try {
|
||||
console.log(`확장된 예산 목표 업데이트 호출: ${type}, 금액: ${amount}`);
|
||||
console.log(`확장된 예산 업데이트: 타입=${type}, 금액=${amount}, 카테고리 예산 포함=${!!newCategoryBudgets}`);
|
||||
|
||||
// 카테고리 예산이 직접 업데이트된 경우
|
||||
if (newCategoryBudgets) {
|
||||
console.log('카테고리 예산 직접 업데이트:', newCategoryBudgets);
|
||||
updateCategoryBudgets(newCategoryBudgets);
|
||||
return;
|
||||
}
|
||||
// 기본 예산 업데이트
|
||||
handleBudgetGoalUpdate(type, amount);
|
||||
|
||||
// 월간 예산을 업데이트하고 일일, 주간도 자동 계산
|
||||
if (type === 'monthly') {
|
||||
console.log('월간 예산 업데이트:', amount);
|
||||
if (amount <= 0) return; // 예산이 0 이하면 업데이트하지 않음
|
||||
|
||||
const ratio = amount / (budgetData.monthly.targetAmount || 1); // 0으로 나누기 방지
|
||||
const updatedCategoryBudgets: Record<string, number> = {};
|
||||
|
||||
// 비율에 따라 카테고리 예산 업데이트
|
||||
Object.keys(categoryBudgets).forEach(category => {
|
||||
updatedCategoryBudgets[category] = Math.round(categoryBudgets[category] * ratio);
|
||||
});
|
||||
|
||||
// 모든 카테고리가 0인 경우 (초기 상태)
|
||||
const allZero = Object.values(categoryBudgets).every(value => value === 0);
|
||||
if (allZero) {
|
||||
// 카테고리 간 균등 분배
|
||||
const categories = Object.keys(categoryBudgets);
|
||||
const perCategoryAmount = Math.round(amount / categories.length);
|
||||
|
||||
categories.forEach(category => {
|
||||
updatedCategoryBudgets[category] = perCategoryAmount;
|
||||
});
|
||||
}
|
||||
|
||||
console.log('업데이트된 카테고리 예산:', updatedCategoryBudgets);
|
||||
updateCategoryBudgets(updatedCategoryBudgets);
|
||||
} else {
|
||||
// 일일이나 주간 예산이 직접 업데이트되는 경우
|
||||
console.log(`${type} 예산 직접 업데이트:`, amount);
|
||||
handleBudgetGoalUpdate(type, amount);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('예산 목표 업데이트 중 오류:', error);
|
||||
toast({
|
||||
title: "예산 업데이트 실패",
|
||||
description: "예산 목표를 업데이트하는 중 오류가 발생했습니다.",
|
||||
variant: "destructive"
|
||||
});
|
||||
// 카테고리 예산도 함께 업데이트 (있는 경우)
|
||||
if (newCategoryBudgets) {
|
||||
console.log('카테고리 예산 업데이트:', newCategoryBudgets);
|
||||
updateCategoryBudgets(newCategoryBudgets);
|
||||
}
|
||||
}, [budgetData, categoryBudgets, handleBudgetGoalUpdate, updateCategoryBudgets]);
|
||||
|
||||
// 데이터 저장 시간 기록 - 동일 세션의 다른 컴포넌트에서 변경 감지 용도
|
||||
localStorage.setItem('lastBudgetUpdateTime', new Date().toISOString());
|
||||
|
||||
// 이벤트 발생
|
||||
try {
|
||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||
} catch (error) {
|
||||
console.error('이벤트 발생 오류:', error);
|
||||
}
|
||||
}, [handleBudgetGoalUpdate, updateCategoryBudgets]);
|
||||
|
||||
return { extendedBudgetGoalUpdate };
|
||||
};
|
||||
|
||||
@@ -12,10 +12,29 @@ export const loadBudgetDataFromStorage = (): BudgetData => {
|
||||
if (storedBudgetData) {
|
||||
const parsed = JSON.parse(storedBudgetData);
|
||||
console.log('예산 데이터 로드 완료', parsed);
|
||||
|
||||
// 데이터 유효성 검사 추가
|
||||
if (!parsed || !parsed.monthly || !parsed.daily || !parsed.weekly) {
|
||||
throw new Error('잘못된 형식의 예산 데이터');
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('예산 데이터 파싱 오류:', error);
|
||||
|
||||
// 백업에서 복구 시도
|
||||
try {
|
||||
const backupData = localStorage.getItem('budgetData_backup');
|
||||
if (backupData) {
|
||||
const parsed = JSON.parse(backupData);
|
||||
console.log('백업에서 예산 데이터 복구 완료', parsed);
|
||||
localStorage.setItem('budgetData', backupData); // 메인 스토리지에 저장
|
||||
return parsed;
|
||||
}
|
||||
} catch (backupError) {
|
||||
console.error('백업 데이터 복구 실패:', backupError);
|
||||
}
|
||||
}
|
||||
|
||||
// 새 사용자를 위한 기본 예산 데이터 저장
|
||||
@@ -39,6 +58,7 @@ export const saveBudgetDataToStorage = (budgetData: BudgetData): void => {
|
||||
|
||||
// 중요: 즉시 자동 백업 (데이터 손실 방지)
|
||||
localStorage.setItem('budgetData_backup', dataString);
|
||||
localStorage.setItem('lastBudgetSaveTime', new Date().toISOString());
|
||||
|
||||
// 스토리지 이벤트 수동 트리거 (동일 창에서도 감지하기 위함)
|
||||
try {
|
||||
|
||||
@@ -17,43 +17,63 @@ const Analytics = () => {
|
||||
const [selectedPeriod, setSelectedPeriod] = useState('이번 달');
|
||||
const { budgetData, getCategorySpending, transactions } = useBudget();
|
||||
const isMobile = useIsMobile();
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
||||
|
||||
// 데이터 변경 감지를 위한 효과
|
||||
// 페이지 가시성 변경시 데이터 새로고침
|
||||
useEffect(() => {
|
||||
console.log('Analytics 페이지 마운트: 데이터 감지 시작');
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
console.log('분석 페이지 보임 - 데이터 새로고침');
|
||||
setRefreshTrigger(prev => prev + 1);
|
||||
|
||||
// 이벤트 발생시켜 데이터 새로고침
|
||||
try {
|
||||
window.dispatchEvent(new Event('storage'));
|
||||
window.dispatchEvent(new Event('transactionUpdated'));
|
||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||
} catch (e) {
|
||||
console.error('이벤트 발생 오류:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 페이지 포커스시 새로고침 이벤트 발생
|
||||
const handleFocus = () => {
|
||||
console.log('Analytics 페이지: 창 포커스 감지, 상태 새로고침');
|
||||
// 상태 리렌더링 트리거를 위한 빈 상태 업데이트
|
||||
setSelectedPeriod(prev => prev);
|
||||
};
|
||||
|
||||
// 스토리지 변경 감지
|
||||
const handleStorageChange = () => {
|
||||
console.log('Analytics 페이지: 스토리지 변경 감지, 상태 새로고침');
|
||||
setSelectedPeriod(prev => prev);
|
||||
console.log('분석 페이지 포커스 - 데이터 새로고침');
|
||||
setRefreshTrigger(prev => prev + 1);
|
||||
|
||||
// 이벤트 발생시켜 데이터 새로고침
|
||||
try {
|
||||
window.dispatchEvent(new Event('storage'));
|
||||
window.dispatchEvent(new Event('transactionUpdated'));
|
||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||
} catch (e) {
|
||||
console.error('이벤트 발생 오류:', e);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
window.addEventListener('focus', handleFocus);
|
||||
window.addEventListener('transactionUpdated', handleStorageChange);
|
||||
window.addEventListener('budgetDataUpdated', handleStorageChange);
|
||||
window.addEventListener('categoryBudgetsUpdated', handleStorageChange);
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
window.addEventListener('transactionUpdated', () => setRefreshTrigger(prev => prev + 1));
|
||||
window.addEventListener('budgetDataUpdated', () => setRefreshTrigger(prev => prev + 1));
|
||||
window.addEventListener('categoryBudgetsUpdated', () => setRefreshTrigger(prev => prev + 1));
|
||||
|
||||
// 컴포넌트 마운트 시 초기 데이터 로드 이벤트 트리거
|
||||
handleFocus();
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
window.removeEventListener('focus', handleFocus);
|
||||
window.removeEventListener('transactionUpdated', handleStorageChange);
|
||||
window.removeEventListener('budgetDataUpdated', handleStorageChange);
|
||||
window.removeEventListener('categoryBudgetsUpdated', handleStorageChange);
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
console.log('Analytics 페이지 언마운트: 데이터 감지 중지');
|
||||
window.removeEventListener('transactionUpdated', () => {});
|
||||
window.removeEventListener('budgetDataUpdated', () => {});
|
||||
window.removeEventListener('categoryBudgetsUpdated', () => {});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 실제 예산 및 지출 데이터 사용
|
||||
const totalBudget = budgetData.monthly.targetAmount;
|
||||
const totalExpense = budgetData.monthly.spentAmount;
|
||||
const totalBudget = budgetData?.monthly?.targetAmount || 0;
|
||||
const totalExpense = budgetData?.monthly?.spentAmount || 0;
|
||||
const savings = Math.max(0, totalBudget - totalExpense);
|
||||
const savingsPercentage = totalBudget > 0 ? Math.round(savings / totalBudget * 100) : 0;
|
||||
|
||||
@@ -63,8 +83,8 @@ const Analytics = () => {
|
||||
name: category.title,
|
||||
value: category.current,
|
||||
color: category.title === '식비' ? '#81c784' :
|
||||
category.title === '생활비' ? '#AED581' :
|
||||
category.title === '교통비' ? '#2E7D32' : '#4CAF50'
|
||||
category.title === '생활비' ? '#AED581' :
|
||||
category.title === '교통비' ? '#2E7D32' : '#4CAF50'
|
||||
}));
|
||||
|
||||
// 최근 6개월 데이터를 위한 상태
|
||||
@@ -72,17 +92,12 @@ const Analytics = () => {
|
||||
|
||||
// 월별 데이터 생성
|
||||
useEffect(() => {
|
||||
console.log('Analytics 페이지: 월별 데이터 생성');
|
||||
console.log('Analytics 페이지: 월별 데이터 생성', { totalBudget, totalExpense });
|
||||
|
||||
// 현재 월 가져오기
|
||||
const today = new Date();
|
||||
const currentMonth = today.getMonth();
|
||||
|
||||
if (totalBudget === 0 && totalExpense === 0) {
|
||||
// 모든 데이터가 초기화된 상태라면 빈 배열 사용
|
||||
setMonthlyData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 최근 6개월 데이터 배열 생성
|
||||
const last6Months = [];
|
||||
for (let i = 5; i >= 0; i--) {
|
||||
@@ -101,7 +116,7 @@ const Analytics = () => {
|
||||
|
||||
setMonthlyData(last6Months);
|
||||
console.log('Analytics 페이지: 월별 데이터 생성 완료', last6Months);
|
||||
}, [totalBudget, totalExpense]);
|
||||
}, [totalBudget, totalExpense, refreshTrigger]);
|
||||
|
||||
// 이전/다음 기간 이동 처리
|
||||
const handlePrevPeriod = () => {
|
||||
@@ -112,15 +127,6 @@ const Analytics = () => {
|
||||
console.log('다음 기간으로 이동');
|
||||
};
|
||||
|
||||
// 디버깅을 위한 로그
|
||||
useEffect(() => {
|
||||
console.log('Analytics 페이지 렌더링:', {
|
||||
totalBudget,
|
||||
totalExpense,
|
||||
categorySpending: categorySpending.length
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-neuro-background pb-24">
|
||||
<div className="max-w-md mx-auto px-6">
|
||||
@@ -148,7 +154,7 @@ const Analytics = () => {
|
||||
<h2 className="text-lg font-semibold mb-3">월별 그래프</h2>
|
||||
<MonthlyComparisonChart
|
||||
monthlyData={monthlyData}
|
||||
isEmpty={monthlyData.length === 0}
|
||||
isEmpty={monthlyData.length === 0 || (totalBudget === 0 && totalExpense === 0)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import NavBar from '@/components/NavBar';
|
||||
import TransactionCard from '@/components/TransactionCard';
|
||||
import AddTransactionButton from '@/components/AddTransactionButton';
|
||||
@@ -23,6 +23,14 @@ const Transactions = () => {
|
||||
} = useTransactions();
|
||||
|
||||
const { budgetData } = useBudget();
|
||||
const [isDataLoaded, setIsDataLoaded] = useState(false);
|
||||
|
||||
// 데이터 로드 상태 관리
|
||||
useEffect(() => {
|
||||
if (budgetData && !isLoading) {
|
||||
setIsDataLoaded(true);
|
||||
}
|
||||
}, [budgetData, isLoading]);
|
||||
|
||||
// 트랜잭션을 날짜별로 그룹화
|
||||
const groupedTransactions: Record<string, typeof transactions> = {};
|
||||
@@ -35,6 +43,31 @@ const Transactions = () => {
|
||||
groupedTransactions[datePart].push(transaction);
|
||||
});
|
||||
|
||||
// 페이지 포커스나 가시성 변경 시 데이터 새로고침
|
||||
useEffect(() => {
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
console.log('거래내역 페이지 보임 - 데이터 새로고침');
|
||||
// 상태 업데이트 트리거
|
||||
setIsDataLoaded(prev => !prev);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFocus = () => {
|
||||
console.log('거래내역 페이지 포커스 - 데이터 새로고침');
|
||||
// 상태 업데이트 트리거
|
||||
setIsDataLoaded(prev => !prev);
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
window.addEventListener('focus', handleFocus);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
window.removeEventListener('focus', handleFocus);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-neuro-background pb-24">
|
||||
<div className="max-w-md mx-auto px-6">
|
||||
@@ -81,7 +114,7 @@ const Transactions = () => {
|
||||
<div className="neuro-card">
|
||||
<p className="text-sm text-gray-500 mb-1">총 예산</p>
|
||||
<p className="text-lg font-bold text-neuro-income">
|
||||
{formatCurrency(budgetData.monthly.targetAmount)}
|
||||
{formatCurrency(budgetData?.monthly?.targetAmount || 0)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="neuro-card">
|
||||
|
||||
Reference in New Issue
Block a user