Refactor useTransactions hook

Refactor the useTransactions hook into smaller, more manageable files to improve code organization and maintainability. All existing functionality is preserved.
This commit is contained in:
gpt-engineer-app[bot]
2025-03-16 09:39:56 +00:00
parent 662cacbc99
commit d45e1bbf78
7 changed files with 410 additions and 215 deletions

View File

@@ -1,218 +1,10 @@
import { useState, useEffect, useCallback } from 'react';
import { Transaction } from '@/components/TransactionCard';
import { useAuth } from '@/contexts/auth/AuthProvider';
import { toast } from '@/hooks/useToast.wrapper';
import {
getCurrentMonth,
getPrevMonth,
getNextMonth
} from './dateUtils';
import {
filterTransactionsByMonth,
filterTransactionsByQuery,
calculateTotalExpenses
} from './filterUtils';
import {
loadTransactionsFromStorage,
saveTransactionsToStorage,
loadBudgetFromStorage
} from './storageUtils';
import {
syncTransactionsWithSupabase,
updateTransactionInSupabase,
deleteTransactionFromSupabase
} from './supabaseUtils';
import { Loader2 } from 'lucide-react';
// useTransactions
import { useTransactionsCore } from './useTransactionsCore';
/**
* 메인 트랜잭션 훅
* useTransactionsCore를 통해 모든 트랜잭션 관련 기능에 접근합니다.
*/
export const useTransactions = () => {
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [filteredTransactions, setFilteredTransactions] = useState<Transaction[]>([]);
const [selectedMonth, setSelectedMonth] = useState(getCurrentMonth());
const [searchQuery, setSearchQuery] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [totalBudget, setTotalBudget] = useState(0);
const { user } = useAuth();
const [refreshKey, setRefreshKey] = useState(0); // 강제 새로고침을 위한 키
// 월 변경 처리
const handlePrevMonth = () => {
setSelectedMonth(getPrevMonth(selectedMonth));
};
const handleNextMonth = () => {
setSelectedMonth(getNextMonth(selectedMonth));
};
// 트랜잭션 로드
const loadTransactions = useCallback(() => {
setIsLoading(true);
setError(null);
try {
const localTransactions = loadTransactionsFromStorage();
setTransactions(localTransactions);
// 예산 가져오기
const budgetAmount = loadBudgetFromStorage();
setTotalBudget(budgetAmount);
} catch (err) {
console.error('트랜잭션 로드 중 오류:', err);
setError('데이터를 불러오는 중 문제가 발생했습니다.');
toast({
title: "데이터 로드 실패",
description: "지출 내역을 불러오는데 실패했습니다.",
variant: "destructive",
duration: 4000
});
} finally {
// 로딩 상태를 약간 지연시켜 UI 업데이트가 원활하게 이루어지도록 함
setTimeout(() => setIsLoading(false), 300);
}
}, []);
// 필터 적용
useEffect(() => {
// 1. 월별 필터링
let filtered = filterTransactionsByMonth(transactions, selectedMonth);
// 2. 검색어 필터링
if (searchQuery.trim()) {
filtered = filterTransactionsByQuery(filtered, searchQuery);
}
console.log('필터링 결과:', filtered.length, '트랜잭션');
setFilteredTransactions(filtered);
}, [transactions, selectedMonth, searchQuery]);
// 초기 데이터 로드 및 이벤트 리스너 설정
useEffect(() => {
console.log('useTransactions - 초기 데이터 로드');
loadTransactions();
// 트랜잭션 업데이트 이벤트 리스너
const handleTransactionUpdated = () => {
console.log('트랜잭션 업데이트 이벤트 감지됨');
loadTransactions();
};
// 스토리지 변경 이벤트 리스너
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'transactions' || e.key === null) {
console.log('로컬 스토리지 변경 감지됨:', e.key);
loadTransactions();
}
};
// 페이지 포커스/가시성 이벤트 리스너
const handleFocus = () => {
console.log('창 포커스 - 트랜잭션 새로고침');
loadTransactions();
};
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
console.log('페이지 가시성 변경 - 트랜잭션 새로고침');
loadTransactions();
}
};
// 이벤트 리스너 등록
window.addEventListener('transactionUpdated', handleTransactionUpdated);
window.addEventListener('storage', handleStorageChange);
window.addEventListener('focus', handleFocus);
document.addEventListener('visibilitychange', handleVisibilityChange);
// 컴포넌트 마운트시에만 수동으로 트랜잭션 업데이트 이벤트 발생
window.dispatchEvent(new Event('transactionUpdated'));
return () => {
window.removeEventListener('transactionUpdated', handleTransactionUpdated);
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener('focus', handleFocus);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [loadTransactions, refreshKey]);
// 트랜잭션 업데이트
const updateTransaction = (updatedTransaction: Transaction) => {
const updatedTransactions = transactions.map(transaction =>
transaction.id === updatedTransaction.id ? updatedTransaction : transaction
);
// 로컬 스토리지 업데이트
saveTransactionsToStorage(updatedTransactions);
// 상태 업데이트
setTransactions(updatedTransactions);
// Supabase 업데이트 시도
if (user) {
updateTransactionInSupabase(user, updatedTransaction);
}
// 이벤트 발생
window.dispatchEvent(new Event('transactionUpdated'));
// 약간의 지연을 두고 토스트 표시
setTimeout(() => {
toast({
title: "지출이 수정되었습니다",
description: `${updatedTransaction.title} 항목이 업데이트되었습니다.`,
duration: 3000
});
}, 100);
};
// 트랜잭션 삭제
const deleteTransaction = (id: string) => {
const updatedTransactions = transactions.filter(transaction => transaction.id !== id);
// 로컬 스토리지 업데이트
saveTransactionsToStorage(updatedTransactions);
// 상태 업데이트
setTransactions(updatedTransactions);
// Supabase 삭제 시도
if (user) {
deleteTransactionFromSupabase(user, id);
}
// 이벤트 발생
window.dispatchEvent(new Event('transactionUpdated'));
// 약간의 지연을 두고 토스트 표시
setTimeout(() => {
toast({
title: "지출이 삭제되었습니다",
description: "선택한 지출 항목이 삭제되었습니다.",
duration: 3000
});
}, 100);
};
// 데이터 강제 새로고침
const refreshTransactions = () => {
setRefreshKey(prev => prev + 1);
loadTransactions();
};
return {
transactions: filteredTransactions,
allTransactions: transactions,
isLoading,
error,
totalBudget,
selectedMonth,
searchQuery,
setSearchQuery,
handlePrevMonth,
handleNextMonth,
updateTransaction,
deleteTransaction,
totalExpenses: calculateTotalExpenses(filteredTransactions),
refreshTransactions
};
return useTransactionsCore();
};

View File

@@ -0,0 +1,100 @@
import { useCallback } from 'react';
import { useTransactionsState } from './useTransactionsState';
import { useTransactionsFiltering } from './useTransactionsFiltering';
import { useTransactionsLoader } from './useTransactionsLoader';
import { useTransactionsOperations } from './useTransactionsOperations';
import { useTransactionsEvents } from './useTransactionsEvents';
/**
* 핵심 트랜잭션 훅
* 모든 트랜잭션 관련 훅을 통합합니다.
*/
export const useTransactionsCore = () => {
// 상태 관리
const {
transactions,
setTransactions,
filteredTransactions,
setFilteredTransactions,
selectedMonth,
setSelectedMonth,
searchQuery,
setSearchQuery,
isLoading,
setIsLoading,
error,
setError,
totalBudget,
setTotalBudget,
refreshKey,
setRefreshKey
} = useTransactionsState();
// 데이터 로딩
const { loadTransactions } = useTransactionsLoader(
setTransactions,
setTotalBudget,
setIsLoading,
setError
);
// 필터링
const {
handlePrevMonth,
handleNextMonth,
getTotalExpenses
} = useTransactionsFiltering(
transactions,
selectedMonth,
setSelectedMonth,
searchQuery,
setFilteredTransactions
);
// 트랜잭션 작업
const {
updateTransaction,
deleteTransaction
} = useTransactionsOperations(
transactions,
setTransactions
);
// 이벤트 리스너
useTransactionsEvents(loadTransactions, refreshKey);
// 데이터 강제 새로고침
const refreshTransactions = useCallback(() => {
setRefreshKey(prev => prev + 1);
loadTransactions();
}, [loadTransactions, setRefreshKey]);
return {
// 데이터
transactions: filteredTransactions,
allTransactions: transactions,
// 상태
isLoading,
error,
totalBudget,
// 필터링
selectedMonth,
searchQuery,
setSearchQuery,
handlePrevMonth,
handleNextMonth,
// 작업
updateTransaction,
deleteTransaction,
// 합계
totalExpenses: getTotalExpenses(filteredTransactions),
// 새로고침
refreshTransactions
};
};

View File

@@ -0,0 +1,59 @@
import { useEffect } from 'react';
/**
* 트랜잭션 이벤트 관련 훅
* 각종 이벤트 리스너를 설정합니다.
*/
export const useTransactionsEvents = (
loadTransactions: () => void,
refreshKey: number
) => {
// 이벤트 리스너 설정
useEffect(() => {
console.log('useTransactions - 이벤트 리스너 설정');
// 트랜잭션 업데이트 이벤트 리스너
const handleTransactionUpdated = () => {
console.log('트랜잭션 업데이트 이벤트 감지됨');
loadTransactions();
};
// 스토리지 변경 이벤트 리스너
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'transactions' || e.key === null) {
console.log('로컬 스토리지 변경 감지됨:', e.key);
loadTransactions();
}
};
// 페이지 포커스/가시성 이벤트 리스너
const handleFocus = () => {
console.log('창 포커스 - 트랜잭션 새로고침');
loadTransactions();
};
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
console.log('페이지 가시성 변경 - 트랜잭션 새로고침');
loadTransactions();
}
};
// 이벤트 리스너 등록
window.addEventListener('transactionUpdated', handleTransactionUpdated);
window.addEventListener('storage', handleStorageChange);
window.addEventListener('focus', handleFocus);
document.addEventListener('visibilitychange', handleVisibilityChange);
// 컴포넌트 마운트시에만 수동으로 트랜잭션 업데이트 이벤트 발생
window.dispatchEvent(new Event('transactionUpdated'));
return () => {
window.removeEventListener('transactionUpdated', handleTransactionUpdated);
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener('focus', handleFocus);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [loadTransactions, refreshKey]);
};

View File

@@ -0,0 +1,55 @@
import { useEffect } from 'react';
import { Transaction } from '@/components/TransactionCard';
import {
filterTransactionsByMonth,
filterTransactionsByQuery,
calculateTotalExpenses
} from './filterUtils';
import { getPrevMonth, getNextMonth } from './dateUtils';
/**
* 트랜잭션 필터링 관련 훅
* 월별 및 검색어 필터링 기능을 제공합니다.
*/
export const useTransactionsFiltering = (
transactions: Transaction[],
selectedMonth: string,
setSelectedMonth: (month: string) => void,
searchQuery: string,
setFilteredTransactions: (transactions: Transaction[]) => void
) => {
// 월 변경 처리
const handlePrevMonth = () => {
setSelectedMonth(getPrevMonth(selectedMonth));
};
const handleNextMonth = () => {
setSelectedMonth(getNextMonth(selectedMonth));
};
// 필터 적용
useEffect(() => {
// 1. 월별 필터링
let filtered = filterTransactionsByMonth(transactions, selectedMonth);
// 2. 검색어 필터링
if (searchQuery.trim()) {
filtered = filterTransactionsByQuery(filtered, searchQuery);
}
console.log('필터링 결과:', filtered.length, '트랜잭션');
setFilteredTransactions(filtered);
}, [transactions, selectedMonth, searchQuery, setFilteredTransactions]);
// 필터링된 트랜잭션의 총 지출 계산
const getTotalExpenses = (filteredTransactions: Transaction[]) => {
return calculateTotalExpenses(filteredTransactions);
};
return {
handlePrevMonth,
handleNextMonth,
getTotalExpenses
};
};

View File

@@ -0,0 +1,49 @@
import { useCallback } from 'react';
import { toast } from '@/hooks/useToast.wrapper';
import {
loadTransactionsFromStorage,
loadBudgetFromStorage
} from './storageUtils';
/**
* 트랜잭션 로딩 관련 훅
* 로컬 스토리지에서 트랜잭션 데이터를 로드합니다.
*/
export const useTransactionsLoader = (
setTransactions: (transactions: any[]) => void,
setTotalBudget: (budget: number) => void,
setIsLoading: (isLoading: boolean) => void,
setError: (error: string | null) => void
) => {
// 트랜잭션 로드
const loadTransactions = useCallback(() => {
setIsLoading(true);
setError(null);
try {
const localTransactions = loadTransactionsFromStorage();
setTransactions(localTransactions);
// 예산 가져오기
const budgetAmount = loadBudgetFromStorage();
setTotalBudget(budgetAmount);
} catch (err) {
console.error('트랜잭션 로드 중 오류:', err);
setError('데이터를 불러오는 중 문제가 발생했습니다.');
toast({
title: "데이터 로드 실패",
description: "지출 내역을 불러오는데 실패했습니다.",
variant: "destructive",
duration: 4000
});
} finally {
// 로딩 상태를 약간 지연시켜 UI 업데이트가 원활하게 이루어지도록 함
setTimeout(() => setIsLoading(false), 300);
}
}, [setTransactions, setTotalBudget, setIsLoading, setError]);
return {
loadTransactions
};
};

View File

@@ -0,0 +1,84 @@
import { useCallback } from 'react';
import { Transaction } from '@/components/TransactionCard';
import { useAuth } from '@/contexts/auth/AuthProvider';
import { toast } from '@/hooks/useToast.wrapper';
import { saveTransactionsToStorage } from './storageUtils';
import {
updateTransactionInSupabase,
deleteTransactionFromSupabase
} from './supabaseUtils';
/**
* 트랜잭션 작업 관련 훅
* 트랜잭션 업데이트, 삭제 기능을 제공합니다.
*/
export const useTransactionsOperations = (
transactions: Transaction[],
setTransactions: (transactions: Transaction[]) => void
) => {
const { user } = useAuth();
// 트랜잭션 업데이트
const updateTransaction = useCallback((updatedTransaction: Transaction) => {
const updatedTransactions = transactions.map(transaction =>
transaction.id === updatedTransaction.id ? updatedTransaction : transaction
);
// 로컬 스토리지 업데이트
saveTransactionsToStorage(updatedTransactions);
// 상태 업데이트
setTransactions(updatedTransactions);
// Supabase 업데이트 시도
if (user) {
updateTransactionInSupabase(user, updatedTransaction);
}
// 이벤트 발생
window.dispatchEvent(new Event('transactionUpdated'));
// 약간의 지연을 두고 토스트 표시
setTimeout(() => {
toast({
title: "지출이 수정되었습니다",
description: `${updatedTransaction.title} 항목이 업데이트되었습니다.`,
duration: 3000
});
}, 100);
}, [transactions, setTransactions, user]);
// 트랜잭션 삭제
const deleteTransaction = useCallback((id: string) => {
const updatedTransactions = transactions.filter(transaction => transaction.id !== id);
// 로컬 스토리지 업데이트
saveTransactionsToStorage(updatedTransactions);
// 상태 업데이트
setTransactions(updatedTransactions);
// Supabase 삭제 시도
if (user) {
deleteTransactionFromSupabase(user, id);
}
// 이벤트 발생
window.dispatchEvent(new Event('transactionUpdated'));
// 약간의 지연을 두고 토스트 표시
setTimeout(() => {
toast({
title: "지출이 삭제되었습니다",
description: "선택한 지출 항목이 삭제되었습니다.",
duration: 3000
});
}, 100);
}, [transactions, setTransactions, user]);
return {
updateTransaction,
deleteTransaction
};
};

View File

@@ -0,0 +1,56 @@
import { useState } from 'react';
import { Transaction } from '@/components/TransactionCard';
import { getCurrentMonth } from './dateUtils';
/**
* 트랜잭션 관련 상태 관리 훅
* 트랜잭션, 필터링, 로딩 상태 등을 관리합니다.
*/
export const useTransactionsState = () => {
// 트랜잭션 상태
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [filteredTransactions, setFilteredTransactions] = useState<Transaction[]>([]);
// 필터링 상태
const [selectedMonth, setSelectedMonth] = useState(getCurrentMonth());
const [searchQuery, setSearchQuery] = useState('');
// 로딩 및 에러 상태
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// 예산 상태
const [totalBudget, setTotalBudget] = useState(0);
// 새로고침 키
const [refreshKey, setRefreshKey] = useState(0);
return {
// 상태
transactions,
setTransactions,
filteredTransactions,
setFilteredTransactions,
// 필터링 상태
selectedMonth,
setSelectedMonth,
searchQuery,
setSearchQuery,
// 로딩 및 에러 상태
isLoading,
setIsLoading,
error,
setError,
// 예산 상태
totalBudget,
setTotalBudget,
// 새로고침 키
refreshKey,
setRefreshKey
};
};