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:
72
src/contexts/budget/hooks/useBudgetDataEvents.ts
Normal file
72
src/contexts/budget/hooks/useBudgetDataEvents.ts
Normal 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]);
|
||||
};
|
||||
63
src/contexts/budget/hooks/useBudgetDataLoad.ts
Normal file
63
src/contexts/budget/hooks/useBudgetDataLoad.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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('예산 데이터 로드 시도 중...');
|
||||
|
||||
// 비동기 작업을 마이크로태스크로 지연
|
||||
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);
|
||||
}
|
||||
};
|
||||
// 데이터 로드 기능
|
||||
const { loadBudgetData } = useBudgetDataLoad(
|
||||
isInitialized,
|
||||
setIsInitialized,
|
||||
budgetData,
|
||||
setBudgetData,
|
||||
setLastUpdateTime
|
||||
);
|
||||
|
||||
// 초기 로드 - 직접 호출
|
||||
loadBudget();
|
||||
// 이벤트 처리
|
||||
useBudgetDataEvents(
|
||||
isInitialized,
|
||||
transactions,
|
||||
setBudgetData,
|
||||
setLastUpdateTime
|
||||
);
|
||||
|
||||
// 필수 이벤트 등록
|
||||
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(() => {
|
||||
|
||||
86
src/contexts/budget/hooks/useBudgetGoalUpdate.ts
Normal file
86
src/contexts/budget/hooks/useBudgetGoalUpdate.ts
Normal 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;
|
||||
};
|
||||
26
src/contexts/budget/hooks/useBudgetState.ts
Normal file
26
src/contexts/budget/hooks/useBudgetState.ts
Normal 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
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user