Refactor: Split useBudgetDataState hook

Splits the `useBudgetDataState` hook into smaller, more manageable hooks for state management, data loading, and event handling. This improves code organization and maintainability while preserving existing functionality.
This commit is contained in:
gpt-engineer-app[bot]
2025-03-22 11:01:19 +00:00
parent 8aabb2fa9c
commit d1a9b9f89f
5 changed files with 289 additions and 176 deletions

View File

@@ -0,0 +1,72 @@
import { useEffect } from 'react';
import { BudgetData, Transaction } from '../types';
import { calculateSpentAmounts, safelyLoadBudgetData } from '../budgetUtils';
import { saveBudgetDataToStorage } from '../storage';
/**
* 예산 데이터 관련 이벤트를 처리하는 훅
*/
export const useBudgetDataEvents = (
isInitialized: boolean,
transactions: Transaction[],
setBudgetData: (data: BudgetData) => void,
setLastUpdateTime: (time: number) => void
) => {
// budgetDataUpdated 이벤트 리스너 설정
useEffect(() => {
const handleBudgetUpdate = () => {
console.log('예산 데이터 업데이트 이벤트 감지');
// 현재 예산 데이터 로드하여 상태 업데이트
const loadedData = safelyLoadBudgetData();
setBudgetData(loadedData);
setLastUpdateTime(Date.now());
};
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', handleBudgetUpdate);
window.removeEventListener('storage', storageUpdateHandler);
};
}, [isInitialized]);
// 트랜잭션 변경 시 지출 금액 업데이트
useEffect(() => {
if (transactions.length > 0 && isInitialized) {
console.log('트랜잭션 변경으로 인한 예산 데이터 업데이트. 트랜잭션 수:', transactions.length);
try {
// 현재 예산 데이터 다시 로드 (최신 상태 확보)
const currentBudgetData = safelyLoadBudgetData();
// 지출 금액 업데이트
const updatedBudgetData = calculateSpentAmounts(transactions, currentBudgetData);
// 변경이 있을 때만 저장
if (JSON.stringify(updatedBudgetData) !== JSON.stringify(currentBudgetData)) {
console.log('예산 지출액 업데이트 감지 - 데이터 저장');
// 상태 및 스토리지 모두 업데이트
setBudgetData(updatedBudgetData);
saveBudgetDataToStorage(updatedBudgetData);
setLastUpdateTime(Date.now());
// 기타 컴포넌트에 이벤트 알림
window.dispatchEvent(new Event('budgetDataUpdated'));
}
} catch (error) {
console.error('예산 데이터 업데이트 중 오류:', error);
}
}
}, [transactions, isInitialized]);
};

View File

@@ -0,0 +1,63 @@
import { useEffect } from 'react';
import { BudgetData } from '../types';
import { safelyLoadBudgetData } from '../budgetUtils';
/**
* 예산 데이터 로드 및 초기화를 처리하는 훅
*/
export const useBudgetDataLoad = (
isInitialized: boolean,
setIsInitialized: (value: boolean) => void,
budgetData: BudgetData,
setBudgetData: (data: BudgetData) => void,
setLastUpdateTime: (time: number) => void
) => {
// 초기 로드 및 이벤트 리스너 설정
useEffect(() => {
// 예산 데이터 로드 함수 - 비동기 처리로 변경
const loadBudget = async () => {
try {
console.log('예산 데이터 로드 시도 중...');
// 비동기 작업을 마이크로태스크로 지연
await new Promise<void>(resolve => queueMicrotask(() => resolve()));
// 안전하게 데이터 로드
const loadedData = safelyLoadBudgetData();
console.log('로드된 예산 데이터:', JSON.stringify(loadedData));
// 새로 로드한 데이터와 현재 데이터가 다를 때만 업데이트
if (JSON.stringify(loadedData) !== JSON.stringify(budgetData)) {
console.log('새 예산 데이터 감지됨, 상태 업데이트');
setBudgetData(loadedData);
setLastUpdateTime(Date.now());
}
// 초기화 상태 업데이트
if (!isInitialized) {
setIsInitialized(true);
}
} catch (error) {
console.error('예산 데이터 로드 중 오류:', error);
}
};
// 초기 로드 - 직접 호출
loadBudget();
// 반환 함수에서는 이벤트 리스너 정리할 필요 없음 (이벤트 리스너는 별도 훅에서 처리)
}, [isInitialized]);
// 로드 함수 반환 (외부에서 필요할 때 호출 가능)
return {
loadBudgetData: async () => {
const loadedData = safelyLoadBudgetData();
if (JSON.stringify(loadedData) !== JSON.stringify(budgetData)) {
setBudgetData(loadedData);
setLastUpdateTime(Date.now());
}
return loadedData;
}
};
};

View File

@@ -1,192 +1,58 @@
import { useState, useEffect, useCallback } from 'react';
import { BudgetData, BudgetPeriod } from '../types';
import {
loadBudgetDataFromStorage,
saveBudgetDataToStorage,
clearAllBudgetData
} from '../storage';
import { toast } from '@/components/ui/use-toast';
import {
calculateUpdatedBudgetData,
calculateSpentAmounts,
safelyLoadBudgetData
} from '../budgetUtils';
import { Transaction } from '../types';
import { useEffect } from 'react';
import { BudgetData, BudgetPeriod, Transaction } from '../types';
import { clearAllBudgetData } from '../storage';
import { useBudgetState } from './useBudgetState';
import { useBudgetDataLoad } from './useBudgetDataLoad';
import { useBudgetDataEvents } from './useBudgetDataEvents';
import { useBudgetGoalUpdate } from './useBudgetGoalUpdate';
import { toast } from '@/hooks/useToast.wrapper';
// 예산 데이터 상태 관리 훅
export const useBudgetDataState = (transactions: Transaction[]) => {
// 초기 데이터 로드 시 safelyLoadBudgetData 함수 사용
const [budgetData, setBudgetData] = useState<BudgetData>(safelyLoadBudgetData());
const [selectedTab, setSelectedTab] = useState<BudgetPeriod>("daily");
const [isInitialized, setIsInitialized] = useState(false);
const [lastUpdateTime, setLastUpdateTime] = useState(0);
// 기본 상태 관리
const {
budgetData,
setBudgetData,
selectedTab,
setSelectedTab,
isInitialized,
setIsInitialized,
lastUpdateTime,
setLastUpdateTime
} = useBudgetState();
// 초기 로드 및 이벤트 리스너 설정 - 최적화된 버전
useEffect(() => {
// 예산 데이터 로드 함수 - 비동기 처리로 변경
const loadBudget = async () => {
try {
console.log('예산 데이터 로드 시도 중...');
// 데이터 로드 기능
const { loadBudgetData } = useBudgetDataLoad(
isInitialized,
setIsInitialized,
budgetData,
setBudgetData,
setLastUpdateTime
);
// 비동기 작업을 마이크로태스크로 지연
await new Promise<void>(resolve => queueMicrotask(() => resolve()));
// 이벤트 처리
useBudgetDataEvents(
isInitialized,
transactions,
setBudgetData,
setLastUpdateTime
);
// 안전하게 데이터 로드
const loadedData = safelyLoadBudgetData();
console.log('로드된 예산 데이터:', JSON.stringify(loadedData));
// 새로 로드한 데이터와 현재 데이터가 다를 때만 업데이트
if (JSON.stringify(loadedData) !== JSON.stringify(budgetData)) {
console.log('새 예산 데이터 감지됨, 상태 업데이트');
setBudgetData(loadedData);
setLastUpdateTime(Date.now());
}
// 초기화 상태 업데이트
if (!isInitialized) {
setIsInitialized(true);
}
} catch (error) {
console.error('예산 데이터 로드 중 오류:', error);
}
};
// 초기 로드 - 직접 호출
loadBudget();
// 필수 이벤트 등록
const handleBudgetUpdate = () => {
console.log('예산 데이터 업데이트 이벤트 감지');
loadBudget();
};
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', handleBudgetUpdate);
window.removeEventListener('storage', storageUpdateHandler);
};
}, [isInitialized]);
// 트랜잭션 변경 시 지출 금액 업데이트
useEffect(() => {
if (transactions.length > 0 && isInitialized) {
console.log('트랜잭션 변경으로 인한 예산 데이터 업데이트. 트랜잭션 수:', transactions.length);
try {
// 현재 예산 데이터 다시 로드 (최신 상태 확보)
const currentBudgetData = safelyLoadBudgetData();
// 지출 금액 업데이트
const updatedBudgetData = calculateSpentAmounts(transactions, currentBudgetData);
// 변경이 있을 때만 저장
if (JSON.stringify(updatedBudgetData) !== JSON.stringify(currentBudgetData)) {
console.log('예산 지출액 업데이트 감지 - 데이터 저장');
// 상태 및 스토리지 모두 업데이트
setBudgetData(updatedBudgetData);
saveBudgetDataToStorage(updatedBudgetData);
setLastUpdateTime(Date.now());
// 기타 컴포넌트에 이벤트 알림
window.dispatchEvent(new Event('budgetDataUpdated'));
}
} catch (error) {
console.error('예산 데이터 업데이트 중 오류:', error);
}
}
}, [transactions, isInitialized]);
// 예산 목표 업데이트 함수 - 수정됨
const handleBudgetGoalUpdate = useCallback((
type: BudgetPeriod,
amount: number
) => {
try {
console.log(`예산 목표 업데이트 시작: ${type}, 금액: ${amount}`);
// 금액이 유효한지 확인
if (isNaN(amount) || amount <= 0) {
console.error('유효하지 않은 예산 금액:', amount);
toast({
title: "예산 설정 오류",
description: "유효한 예산 금액을 입력해주세요.",
variant: "destructive"
});
return;
}
// 현재 최신 예산 데이터 로드 (다른 곳에서 변경되었을 수 있음)
const currentBudgetData = safelyLoadBudgetData();
// 예산 데이터 업데이트 - 일간, 주간, 월간 예산이 모두 자동으로 계산됨
const updatedBudgetData = calculateUpdatedBudgetData(currentBudgetData, type, amount);
console.log('새 예산 데이터 계산됨:', updatedBudgetData);
// 상태 및 스토리지 둘 다 업데이트
setBudgetData(updatedBudgetData);
saveBudgetDataToStorage(updatedBudgetData);
setLastUpdateTime(Date.now());
// 이벤트 발생시켜 다른 컴포넌트에 알림
window.dispatchEvent(new Event('budgetDataUpdated'));
// 1초 후 데이터 갱신 이벤트 한 번 더 발생 (UI 갱신 확실히 하기 위함)
setTimeout(() => {
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new StorageEvent('storage', {
key: 'budgetData',
newValue: JSON.stringify(updatedBudgetData)
}));
}, 500);
// 2초 후 데이터 갱신 이벤트 한 번 더 발생
setTimeout(() => {
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new StorageEvent('storage', {
key: 'budgetData',
newValue: JSON.stringify(updatedBudgetData)
}));
}, 2000);
console.log('예산 목표 업데이트 완료:', updatedBudgetData);
// 성공 메시지 표시
const periodText = type === 'daily' ? '일일' : type === 'weekly' ? '주간' : '월간';
toast({
title: "예산 설정 완료",
description: `${periodText} 예산이 ${amount.toLocaleString()}원으로 설정되었습니다.`,
});
} catch (error) {
console.error('예산 목표 업데이트 중 오류:', error);
toast({
title: "예산 업데이트 실패",
description: "예산 목표를 업데이트하는데 문제가 발생했습니다.",
variant: "destructive"
});
}
}, []);
// 예산 목표 업데이트 함수
const handleBudgetGoalUpdate = useBudgetGoalUpdate(
setBudgetData,
setLastUpdateTime
);
// 예산 데이터 초기화 함수
const resetBudgetData = useCallback(() => {
const resetBudgetData = () => {
try {
console.log('예산 데이터 초기화');
clearAllBudgetData();
// 로컬스토리지에서 다시 로드
const freshData = safelyLoadBudgetData();
setBudgetData(freshData);
setLastUpdateTime(Date.now());
loadBudgetData();
// 이벤트 발생시켜 다른 컴포넌트에 알림
window.dispatchEvent(new Event('budgetDataUpdated'));
@@ -198,7 +64,7 @@ export const useBudgetDataState = (transactions: Transaction[]) => {
variant: "destructive"
});
}
}, []);
};
// 디버깅 로그 추가
useEffect(() => {

View File

@@ -0,0 +1,86 @@
import { useCallback } from 'react';
import { BudgetData, BudgetPeriod } from '../types';
import { calculateUpdatedBudgetData, safelyLoadBudgetData } from '../budgetUtils';
import { saveBudgetDataToStorage } from '../storage';
import { toast } from '@/hooks/useToast.wrapper';
/**
* 예산 목표 업데이트 기능을 제공하는 훅
*/
export const useBudgetGoalUpdate = (
setBudgetData: (data: BudgetData) => void,
setLastUpdateTime: (time: number) => void
) => {
// 예산 목표 업데이트 함수
const handleBudgetGoalUpdate = useCallback((
type: BudgetPeriod,
amount: number
) => {
try {
console.log(`예산 목표 업데이트 시작: ${type}, 금액: ${amount}`);
// 금액이 유효한지 확인
if (isNaN(amount) || amount <= 0) {
console.error('유효하지 않은 예산 금액:', amount);
toast({
title: "예산 설정 오류",
description: "유효한 예산 금액을 입력해주세요.",
variant: "destructive"
});
return;
}
// 현재 최신 예산 데이터 로드 (다른 곳에서 변경되었을 수 있음)
const currentBudgetData = safelyLoadBudgetData();
// 예산 데이터 업데이트 - 일간, 주간, 월간 예산이 모두 자동으로 계산됨
const updatedBudgetData = calculateUpdatedBudgetData(currentBudgetData, type, amount);
console.log('새 예산 데이터 계산됨:', updatedBudgetData);
// 상태 및 스토리지 둘 다 업데이트
setBudgetData(updatedBudgetData);
saveBudgetDataToStorage(updatedBudgetData);
setLastUpdateTime(Date.now());
// 이벤트 발생시켜 다른 컴포넌트에 알림
window.dispatchEvent(new Event('budgetDataUpdated'));
// 1초 후 데이터 갱신 이벤트 한 번 더 발생 (UI 갱신 확실히 하기 위함)
setTimeout(() => {
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new StorageEvent('storage', {
key: 'budgetData',
newValue: JSON.stringify(updatedBudgetData)
}));
}, 500);
// 2초 후 데이터 갱신 이벤트 한 번 더 발생
setTimeout(() => {
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new StorageEvent('storage', {
key: 'budgetData',
newValue: JSON.stringify(updatedBudgetData)
}));
}, 2000);
console.log('예산 목표 업데이트 완료:', updatedBudgetData);
// 성공 메시지 표시
const periodText = type === 'daily' ? '일일' : type === 'weekly' ? '주간' : '월간';
toast({
title: "예산 설정 완료",
description: `${periodText} 예산이 ${amount.toLocaleString()}원으로 설정되었습니다.`,
});
} catch (error) {
console.error('예산 목표 업데이트 중 오류:', error);
toast({
title: "예산 업데이트 실패",
description: "예산 목표를 업데이트하는데 문제가 발생했습니다.",
variant: "destructive"
});
}
}, []);
return handleBudgetGoalUpdate;
};

View File

@@ -0,0 +1,26 @@
import { useState } from 'react';
import { BudgetData, BudgetPeriod } from '../types';
import { safelyLoadBudgetData } from '../budgetUtils';
/**
* 예산 데이터의 기본 상태를 관리하는 훅
*/
export const useBudgetState = () => {
// 초기 데이터 로드 시 safelyLoadBudgetData 함수 사용
const [budgetData, setBudgetData] = useState<BudgetData>(safelyLoadBudgetData());
const [selectedTab, setSelectedTab] = useState<BudgetPeriod>("daily");
const [isInitialized, setIsInitialized] = useState(false);
const [lastUpdateTime, setLastUpdateTime] = useState(0);
return {
budgetData,
setBudgetData,
selectedTab,
setSelectedTab,
isInitialized,
setIsInitialized,
lastUpdateTime,
setLastUpdateTime
};
};