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:
58
src/components/home/IndexContent.tsx
Normal file
58
src/components/home/IndexContent.tsx
Normal 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;
|
||||
@@ -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 => {
|
||||
try {
|
||||
|
||||
76
src/hooks/useAppFocusEvents.tsx
Normal file
76
src/hooks/useAppFocusEvents.tsx
Normal 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);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
57
src/hooks/useInitialDataLoading.tsx
Normal file
57
src/hooks/useInitialDataLoading.tsx
Normal 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);
|
||||
}
|
||||
}, []); // 컴포넌트 마운트 시 한 번만 실행
|
||||
};
|
||||
35
src/hooks/useWelcomeNotification.tsx
Normal file
35
src/hooks/useWelcomeNotification.tsx
Normal 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]);
|
||||
};
|
||||
@@ -2,55 +2,28 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import NavBar from '@/components/NavBar';
|
||||
import AddTransactionButton from '@/components/AddTransactionButton';
|
||||
import Header from '@/components/Header';
|
||||
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 { useAuth } from '@/contexts/auth';
|
||||
import { useWelcomeDialog } from '@/hooks/useWelcomeDialog';
|
||||
import { useDataInitialization } from '@/hooks/useDataInitialization';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import useNotifications from '@/hooks/useNotifications';
|
||||
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 {
|
||||
transactions,
|
||||
budgetData,
|
||||
selectedTab,
|
||||
setSelectedTab,
|
||||
handleBudgetGoalUpdate,
|
||||
updateTransaction,
|
||||
getCategorySpending,
|
||||
resetBudgetData
|
||||
} = useBudget();
|
||||
|
||||
const { user } = useAuth();
|
||||
const { resetBudgetData } = useBudget();
|
||||
const { showWelcome, checkWelcomeDialogState, handleCloseWelcome } = useWelcomeDialog();
|
||||
const { isInitialized } = useDataInitialization(resetBudgetData);
|
||||
const isMobile = useIsMobile();
|
||||
const { addNotification } = useNotifications();
|
||||
|
||||
// 커스텀 훅 사용으로 코드 분리
|
||||
useInitialDataLoading();
|
||||
useAppFocusEvents();
|
||||
useWelcomeNotification(isInitialized);
|
||||
|
||||
// 초기화 후 환영 메시지 표시 상태 확인
|
||||
useEffect(() => {
|
||||
@@ -60,168 +33,9 @@ const Index = () => {
|
||||
}
|
||||
}, [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 (
|
||||
<SafeAreaContainer className="min-h-screen bg-neuro-background pb-24" extraBottomPadding={true}>
|
||||
<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>
|
||||
<IndexContent />
|
||||
<AddTransactionButton />
|
||||
<NavBar />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user