Refactor: Split Index.tsx into smaller components and hooks

Split the large Index.tsx file into smaller, more manageable components and custom hooks to improve code readability and maintainability. Ensure all functionality remains the same after refactoring.
This commit is contained in:
gpt-engineer-app[bot]
2025-04-05 05:31:21 +00:00
parent c8f0855f6c
commit a326981e5d
6 changed files with 265 additions and 200 deletions

View File

@@ -0,0 +1,58 @@
import React from 'react';
import Header from '@/components/Header';
import HomeContent from '@/components/home/HomeContent';
import { useBudget } from '@/contexts/budget/BudgetContext';
import { BudgetData } from '@/contexts/budget/types';
// 기본 예산 데이터 (빈 객체 대신 사용할 더미 데이터)
const defaultBudgetData: BudgetData = {
daily: {
targetAmount: 0,
spentAmount: 0,
remainingAmount: 0
},
weekly: {
targetAmount: 0,
spentAmount: 0,
remainingAmount: 0
},
monthly: {
targetAmount: 0,
spentAmount: 0,
remainingAmount: 0
}
};
/**
* 인덱스 페이지의 주요 내용을 담당하는 컴포넌트
*/
const IndexContent: React.FC = () => {
const {
transactions,
budgetData,
selectedTab,
setSelectedTab,
handleBudgetGoalUpdate,
updateTransaction,
getCategorySpending
} = useBudget();
return (
<div className="max-w-md mx-auto px-6">
<Header />
<HomeContent
transactions={transactions || []}
budgetData={budgetData || defaultBudgetData}
selectedTab={selectedTab}
setSelectedTab={setSelectedTab}
handleBudgetGoalUpdate={handleBudgetGoalUpdate}
updateTransaction={updateTransaction}
getCategorySpending={getCategorySpending}
/>
</div>
);
};
export default IndexContent;

View File

@@ -21,6 +21,31 @@ export const getInitialBudgetData = (): BudgetData => ({
} }
}); });
// 기본 카테고리 예산 값 (수출)
export const DEFAULT_CATEGORY_BUDGETS = {};
// 안전한 스토리지 처리 (수출)
export const safeStorage = {
setItem: (key: string, value: any) => {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (err) {
console.error(`스토리지 저장 오류 (${key}):`, err);
return false;
}
},
getItem: (key: string, defaultValue: any = null) => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (err) {
console.error(`스토리지 로드 오류 (${key}):`, err);
return defaultValue;
}
}
};
// 예산 데이터 스토리지에서 로드 // 예산 데이터 스토리지에서 로드
export const safelyLoadBudgetData = (): BudgetData => { export const safelyLoadBudgetData = (): BudgetData => {
try { try {

View File

@@ -0,0 +1,76 @@
import { useEffect } from 'react';
/**
* 앱이 포커스를 얻었을 때나 가시성이 변경될 때 데이터를 새로고침하는 커스텀 훅
*/
export const useAppFocusEvents = () => {
useEffect(() => {
const handleFocus = () => {
try {
console.log('창이 포커스를 얻음 - 데이터 새로고침');
// 이미 리프레시 중인지 확인하는 플래그
if (sessionStorage.getItem('isRefreshing') === 'true') {
console.log('이미 리프레시 진행 중, 중복 실행 방지');
return;
}
try {
sessionStorage.setItem('isRefreshing', 'true');
// 이벤트 발생시켜 데이터 새로고침
window.dispatchEvent(new Event('storage'));
window.dispatchEvent(new Event('transactionUpdated'));
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
// 리프레시 완료 표시 (300ms 후에 플래그 해제)
setTimeout(() => {
sessionStorage.setItem('isRefreshing', 'false');
}, 300);
} catch (e) {
console.error('이벤트 발생 오류:', e);
sessionStorage.setItem('isRefreshing', 'false');
}
} catch (error) {
console.error('포커스 이벤트 처리 중 오류:', error);
}
};
// 포커스 이벤트
window.addEventListener('focus', handleFocus);
// 가시성 변경 이벤트 (백그라운드에서 전경으로 돌아올 때)
const handleVisibilityChange = () => {
try {
if (document.visibilityState === 'visible') {
console.log('페이지가 다시 보임 - 데이터 새로고침');
handleFocus();
}
} catch (error) {
console.error('가시성 이벤트 처리 중 오류:', error);
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
// 정기적인 데이터 새로고침 (60초마다로 변경 - 너무 빈번한 리프레시 방지)
const refreshInterval = setInterval(() => {
try {
if (document.visibilityState === 'visible' &&
sessionStorage.getItem('isRefreshing') !== 'true') {
console.log('정기 새로고침 - 데이터 업데이트');
handleFocus();
}
} catch (error) {
console.error('정기 새로고침 처리 중 오류:', error);
}
}, 60000); // 60초마다
return () => {
window.removeEventListener('focus', handleFocus);
document.removeEventListener('visibilitychange', handleVisibilityChange);
clearInterval(refreshInterval);
};
}, []);
};

View File

@@ -0,0 +1,57 @@
import { useEffect } from 'react';
/**
* 앱 첫 실행 시 로컬스토리지 데이터를 로드하는 커스텀 훅
*/
export const useInitialDataLoading = () => {
useEffect(() => {
try {
console.log('Index 페이지 마운트, 데이터 확인 중...');
// 페이지 첫 마운트 시에만 실행되는 로직
const isFirstMount = sessionStorage.getItem('initialDataLoaded') !== 'true';
if (isFirstMount) {
try {
// 백업된 데이터 복구 확인 (메인 데이터가 없는 경우만)
if (!localStorage.getItem('budgetData')) {
const budgetBackup = localStorage.getItem('budgetData_backup');
if (budgetBackup) {
console.log('예산 데이터 백업에서 복구');
localStorage.setItem('budgetData', budgetBackup);
}
}
if (!localStorage.getItem('categoryBudgets')) {
const categoryBackup = localStorage.getItem('categoryBudgets_backup');
if (categoryBackup) {
console.log('카테고리 예산 백업에서 복구');
localStorage.setItem('categoryBudgets', categoryBackup);
}
}
if (!localStorage.getItem('transactions')) {
const transactionBackup = localStorage.getItem('transactions_backup');
if (transactionBackup) {
console.log('트랜잭션 백업에서 복구');
localStorage.setItem('transactions', transactionBackup);
}
}
// 한 번만 이벤트 발생
window.dispatchEvent(new Event('transactionUpdated'));
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
// 초기 로드 완료 표시
sessionStorage.setItem('initialDataLoaded', 'true');
} catch (error) {
console.error('백업 복구 시도 중 오류:', error);
}
}
} catch (error) {
console.error('Index 페이지 초기화 중 오류:', error);
}
}, []); // 컴포넌트 마운트 시 한 번만 실행
};

View File

@@ -0,0 +1,35 @@
import { useEffect } from 'react';
import { useAuth } from '@/contexts/auth';
import useNotifications from '@/hooks/useNotifications';
/**
* 앱 초기화 후 환영 메시지 알림을 표시하는 커스텀 훅
*/
export const useWelcomeNotification = (isInitialized: boolean) => {
const { user } = useAuth();
const { addNotification } = useNotifications();
useEffect(() => {
try {
// 환영 메시지가 이미 표시되었는지 확인하는 키
const welcomeNotificationSent = sessionStorage.getItem('welcomeNotificationSent');
if (isInitialized && user && !welcomeNotificationSent) {
// 사용자 로그인 시 알림 예시 (한 번만 실행)
const timeoutId = setTimeout(() => {
addNotification(
'환영합니다!',
'젤리의 적자탈출에 오신 것을 환영합니다. 예산을 설정하고 지출을 기록해보세요.'
);
// 세션 스토리지에 환영 메시지 표시 여부 저장
sessionStorage.setItem('welcomeNotificationSent', 'true');
}, 2000);
return () => clearTimeout(timeoutId);
}
} catch (error) {
console.error('환영 메시지 알림 표시 중 오류:', error);
}
}, [isInitialized, user, addNotification]);
};

View File

@@ -2,55 +2,28 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import NavBar from '@/components/NavBar'; import NavBar from '@/components/NavBar';
import AddTransactionButton from '@/components/AddTransactionButton'; import AddTransactionButton from '@/components/AddTransactionButton';
import Header from '@/components/Header';
import WelcomeDialog from '@/components/onboarding/WelcomeDialog'; import WelcomeDialog from '@/components/onboarding/WelcomeDialog';
import HomeContent from '@/components/home/HomeContent'; import IndexContent from '@/components/home/IndexContent';
import { useBudget } from '@/contexts/budget/BudgetContext'; import { useBudget } from '@/contexts/budget/BudgetContext';
import { useAuth } from '@/contexts/auth';
import { useWelcomeDialog } from '@/hooks/useWelcomeDialog'; import { useWelcomeDialog } from '@/hooks/useWelcomeDialog';
import { useDataInitialization } from '@/hooks/useDataInitialization'; import { useDataInitialization } from '@/hooks/useDataInitialization';
import { useIsMobile } from '@/hooks/use-mobile';
import useNotifications from '@/hooks/useNotifications';
import SafeAreaContainer from '@/components/SafeAreaContainer'; import SafeAreaContainer from '@/components/SafeAreaContainer';
import { BudgetData } from '@/contexts/budget/types'; import { useInitialDataLoading } from '@/hooks/useInitialDataLoading';
import { useAppFocusEvents } from '@/hooks/useAppFocusEvents';
import { useWelcomeNotification } from '@/hooks/useWelcomeNotification';
// 기본 예산 데이터 (빈 객체 대신 사용할 더미 데이터) /**
const defaultBudgetData: BudgetData = { * 애플리케이션의 메인 인덱스 페이지 컴포넌트
daily: { */
targetAmount: 0,
spentAmount: 0,
remainingAmount: 0
},
weekly: {
targetAmount: 0,
spentAmount: 0,
remainingAmount: 0
},
monthly: {
targetAmount: 0,
spentAmount: 0,
remainingAmount: 0
}
};
// 메인 컴포넌트
const Index = () => { const Index = () => {
const { const { resetBudgetData } = useBudget();
transactions,
budgetData,
selectedTab,
setSelectedTab,
handleBudgetGoalUpdate,
updateTransaction,
getCategorySpending,
resetBudgetData
} = useBudget();
const { user } = useAuth();
const { showWelcome, checkWelcomeDialogState, handleCloseWelcome } = useWelcomeDialog(); const { showWelcome, checkWelcomeDialogState, handleCloseWelcome } = useWelcomeDialog();
const { isInitialized } = useDataInitialization(resetBudgetData); const { isInitialized } = useDataInitialization(resetBudgetData);
const isMobile = useIsMobile();
const { addNotification } = useNotifications(); // 커스텀 훅 사용으로 코드 분리
useInitialDataLoading();
useAppFocusEvents();
useWelcomeNotification(isInitialized);
// 초기화 후 환영 메시지 표시 상태 확인 // 초기화 후 환영 메시지 표시 상태 확인
useEffect(() => { useEffect(() => {
@@ -60,168 +33,9 @@ const Index = () => {
} }
}, [isInitialized, checkWelcomeDialogState]); }, [isInitialized, checkWelcomeDialogState]);
// 앱 시작시 예시 알림 추가 (실제 앱에서는 필요한 이벤트에 따라 알림 추가)
useEffect(() => {
try {
// 환영 메시지가 이미 표시되었는지 확인하는 키
const welcomeNotificationSent = sessionStorage.getItem('welcomeNotificationSent');
if (isInitialized && user && !welcomeNotificationSent) {
// 사용자 로그인 시 알림 예시 (한 번만 실행)
const timeoutId = setTimeout(() => {
addNotification(
'환영합니다!',
'젤리의 적자탈출에 오신 것을 환영합니다. 예산을 설정하고 지출을 기록해보세요.'
);
// 세션 스토리지에 환영 메시지 표시 여부 저장
sessionStorage.setItem('welcomeNotificationSent', 'true');
}, 2000);
return () => clearTimeout(timeoutId);
}
} catch (error) {
console.error('환영 메시지 알림 표시 중 오류:', error);
}
}, [isInitialized, user, addNotification]);
// 페이지가 처음 로드될 때 데이터 로딩 확인 - 에러 방지를 위해 try/catch 추가
useEffect(() => {
try {
console.log('Index 페이지 마운트, 현재 데이터 상태:');
console.log('트랜잭션:', transactions?.length || 0);
console.log('예산 데이터:', budgetData || defaultBudgetData);
// 페이지 첫 마운트 시에만 실행되는 로직으로 수정
const isFirstMount = sessionStorage.getItem('initialDataLoaded') !== 'true';
if (isFirstMount) {
try {
// 백업된 데이터 복구 확인 (메인 데이터가 없는 경우만)
if (!localStorage.getItem('budgetData')) {
const budgetBackup = localStorage.getItem('budgetData_backup');
if (budgetBackup) {
console.log('예산 데이터 백업에서 복구');
localStorage.setItem('budgetData', budgetBackup);
}
}
if (!localStorage.getItem('categoryBudgets')) {
const categoryBackup = localStorage.getItem('categoryBudgets_backup');
if (categoryBackup) {
console.log('카테고리 예산 백업에서 복구');
localStorage.setItem('categoryBudgets', categoryBackup);
}
}
if (!localStorage.getItem('transactions')) {
const transactionBackup = localStorage.getItem('transactions_backup');
if (transactionBackup) {
console.log('트랜잭션 백업에서 복구');
localStorage.setItem('transactions', transactionBackup);
}
}
// 한 번만 이벤트 발생
window.dispatchEvent(new Event('transactionUpdated'));
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
// 초기 로드 완료 표시
sessionStorage.setItem('initialDataLoaded', 'true');
} catch (error) {
console.error('백업 복구 시도 중 오류:', error);
}
}
} catch (error) {
console.error('Index 페이지 초기화 중 오류:', error);
}
}, []); // 의존성 배열 비움 - 컴포넌트 마운트 시 한 번만 실행
// 앱이 포커스를 얻었을 때 데이터를 새로고침
useEffect(() => {
const handleFocus = () => {
try {
console.log('창이 포커스를 얻음 - 데이터 새로고침');
// 이미 리프레시 중인지 확인하는 플래그
if (sessionStorage.getItem('isRefreshing') === 'true') {
console.log('이미 리프레시 진행 중, 중복 실행 방지');
return;
}
try {
sessionStorage.setItem('isRefreshing', 'true');
// 이벤트 발생시켜 데이터 새로고침
window.dispatchEvent(new Event('storage'));
window.dispatchEvent(new Event('transactionUpdated'));
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
// 리프레시 완료 표시 (300ms 후에 플래그 해제)
setTimeout(() => {
sessionStorage.setItem('isRefreshing', 'false');
}, 300);
} catch (e) {
console.error('이벤트 발생 오류:', e);
sessionStorage.setItem('isRefreshing', 'false');
}
} catch (error) {
console.error('포커스 이벤트 처리 중 오류:', error);
}
};
// 포커스 이벤트
window.addEventListener('focus', handleFocus);
// 가시성 변경 이벤트 (백그라운드에서 전경으로 돌아올 때)
const handleVisibilityChange = () => {
try {
if (document.visibilityState === 'visible') {
console.log('페이지가 다시 보임 - 데이터 새로고침');
handleFocus();
}
} catch (error) {
console.error('가시성 이벤트 처리 중 오류:', error);
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
// 정기적인 데이터 새로고침 (60초마다로 변경 - 너무 빈번한 리프레시 방지)
const refreshInterval = setInterval(() => {
try {
if (document.visibilityState === 'visible' &&
sessionStorage.getItem('isRefreshing') !== 'true') {
console.log('정기 새로고침 - 데이터 업데이트');
handleFocus();
}
} catch (error) {
console.error('정기 새로고침 처리 중 오류:', error);
}
}, 60000); // 10초에서 60초로 변경
return () => {
window.removeEventListener('focus', handleFocus);
document.removeEventListener('visibilitychange', handleVisibilityChange);
clearInterval(refreshInterval);
};
}, []);
return ( return (
<SafeAreaContainer className="min-h-screen bg-neuro-background pb-24" extraBottomPadding={true}> <SafeAreaContainer className="min-h-screen bg-neuro-background pb-24" extraBottomPadding={true}>
<div className="max-w-md mx-auto px-6"> <IndexContent />
<Header />
<HomeContent
transactions={transactions || []}
budgetData={budgetData || defaultBudgetData}
selectedTab={selectedTab}
setSelectedTab={setSelectedTab}
handleBudgetGoalUpdate={handleBudgetGoalUpdate}
updateTransaction={updateTransaction}
getCategorySpending={getCategorySpending}
/>
</div>
<AddTransactionButton /> <AddTransactionButton />
<NavBar /> <NavBar />