From d1a9b9f89fc987bf454285434ad49a5e3825e4d0 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 11:01:19 +0000 Subject: [PATCH] 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. --- .../budget/hooks/useBudgetDataEvents.ts | 72 ++++++ .../budget/hooks/useBudgetDataLoad.ts | 63 +++++ .../budget/hooks/useBudgetDataState.ts | 218 ++++-------------- .../budget/hooks/useBudgetGoalUpdate.ts | 86 +++++++ src/contexts/budget/hooks/useBudgetState.ts | 26 +++ 5 files changed, 289 insertions(+), 176 deletions(-) create mode 100644 src/contexts/budget/hooks/useBudgetDataEvents.ts create mode 100644 src/contexts/budget/hooks/useBudgetDataLoad.ts create mode 100644 src/contexts/budget/hooks/useBudgetGoalUpdate.ts create mode 100644 src/contexts/budget/hooks/useBudgetState.ts diff --git a/src/contexts/budget/hooks/useBudgetDataEvents.ts b/src/contexts/budget/hooks/useBudgetDataEvents.ts new file mode 100644 index 0000000..c5d9b17 --- /dev/null +++ b/src/contexts/budget/hooks/useBudgetDataEvents.ts @@ -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]); +}; diff --git a/src/contexts/budget/hooks/useBudgetDataLoad.ts b/src/contexts/budget/hooks/useBudgetDataLoad.ts new file mode 100644 index 0000000..5245cb4 --- /dev/null +++ b/src/contexts/budget/hooks/useBudgetDataLoad.ts @@ -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(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; + } + }; +}; diff --git a/src/contexts/budget/hooks/useBudgetDataState.ts b/src/contexts/budget/hooks/useBudgetDataState.ts index dc1d947..7b49682 100644 --- a/src/contexts/budget/hooks/useBudgetDataState.ts +++ b/src/contexts/budget/hooks/useBudgetDataState.ts @@ -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(safelyLoadBudgetData()); - const [selectedTab, setSelectedTab] = useState("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(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(() => { diff --git a/src/contexts/budget/hooks/useBudgetGoalUpdate.ts b/src/contexts/budget/hooks/useBudgetGoalUpdate.ts new file mode 100644 index 0000000..a810ccf --- /dev/null +++ b/src/contexts/budget/hooks/useBudgetGoalUpdate.ts @@ -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; +}; diff --git a/src/contexts/budget/hooks/useBudgetState.ts b/src/contexts/budget/hooks/useBudgetState.ts new file mode 100644 index 0000000..e625e0d --- /dev/null +++ b/src/contexts/budget/hooks/useBudgetState.ts @@ -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(safelyLoadBudgetData()); + const [selectedTab, setSelectedTab] = useState("daily"); + const [isInitialized, setIsInitialized] = useState(false); + const [lastUpdateTime, setLastUpdateTime] = useState(0); + + return { + budgetData, + setBudgetData, + selectedTab, + setSelectedTab, + isInitialized, + setIsInitialized, + lastUpdateTime, + setLastUpdateTime + }; +};