From 71d21b9e6e8a09106938462c90beb534b26ef5da Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 16 Mar 2025 05:51:34 +0000 Subject: [PATCH] Refactor: Split Analytics page Splits the Analytics page into smaller, more manageable components to improve code organization and maintainability. --- .../analytics/CategorySpendingList.tsx | 62 +++++++ .../analytics/MonthlyComparisonChart.tsx | 69 ++++++++ src/components/analytics/PeriodSelector.tsx | 39 +++++ src/components/analytics/SummaryCards.tsx | 50 ++++++ src/pages/Analytics.tsx | 163 ++++-------------- 5 files changed, 250 insertions(+), 133 deletions(-) create mode 100644 src/components/analytics/CategorySpendingList.tsx create mode 100644 src/components/analytics/MonthlyComparisonChart.tsx create mode 100644 src/components/analytics/PeriodSelector.tsx create mode 100644 src/components/analytics/SummaryCards.tsx diff --git a/src/components/analytics/CategorySpendingList.tsx b/src/components/analytics/CategorySpendingList.tsx new file mode 100644 index 0000000..2577255 --- /dev/null +++ b/src/components/analytics/CategorySpendingList.tsx @@ -0,0 +1,62 @@ + +import React from 'react'; +import { formatCurrency } from '@/utils/formatters'; + +interface CategorySpending { + title: string; + current: number; + total: number; +} + +interface CategorySpendingListProps { + categories: CategorySpending[]; + totalExpense: number; +} + +const CategorySpendingList: React.FC = ({ + categories, + totalExpense +}) => { + // 카테고리별 색상 매핑 + const getCategoryColor = (title: string) => { + switch (title) { + case '식비': return '#81c784'; + case '생활비': return '#AED581'; + case '교통비': return '#2E7D32'; + default: return '#4CAF50'; + } + }; + + return ( +
+ {categories.some(cat => cat.current > 0) ? ( +
+ {categories.map((category) => ( +
+
+
+ {category.title} +
+
+

+ {formatCurrency(category.current)} +

+

+ {totalExpense > 0 ? Math.round(category.current / totalExpense * 100) : 0}% +

+
+
+ ))} +
+ ) : ( +
+

아직 지출 내역이 없습니다

+
+ )} +
+ ); +}; + +export default CategorySpendingList; diff --git a/src/components/analytics/MonthlyComparisonChart.tsx b/src/components/analytics/MonthlyComparisonChart.tsx new file mode 100644 index 0000000..7229c9b --- /dev/null +++ b/src/components/analytics/MonthlyComparisonChart.tsx @@ -0,0 +1,69 @@ + +import React from 'react'; +import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend } from 'recharts'; +import { formatCurrency } from '@/utils/formatters'; + +interface MonthlyData { + name: string; + budget: number; + expense: number; +} + +interface MonthlyComparisonChartProps { + monthlyData: MonthlyData[]; + isEmpty?: boolean; +} + +const MonthlyComparisonChart: React.FC = ({ + monthlyData, + isEmpty = false +}) => { + // Format for Y-axis (K format) + const formatYAxisTick = (value: number) => { + return `${Math.round(value / 1000)}K`; + }; + + // Format for tooltip (original currency format) + const formatTooltip = (value: number | string) => { + if (typeof value === 'number') { + return formatCurrency(value); + } + return value; + }; + + // Empty state component + const EmptyGraphState = () => ( +
+

데이터가 없습니다

+

지출 내역을 추가하면 그래프가 표시됩니다

+
+ ); + + return ( +
+ {!isEmpty && monthlyData.length > 0 ? ( + + + + + + + + + + + ) : ( + + )} +
+ ); +}; + +export default MonthlyComparisonChart; diff --git a/src/components/analytics/PeriodSelector.tsx b/src/components/analytics/PeriodSelector.tsx new file mode 100644 index 0000000..4a9ead4 --- /dev/null +++ b/src/components/analytics/PeriodSelector.tsx @@ -0,0 +1,39 @@ + +import React from 'react'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; + +interface PeriodSelectorProps { + selectedPeriod: string; + onPrevPeriod: () => void; + onNextPeriod: () => void; +} + +const PeriodSelector: React.FC = ({ + selectedPeriod, + onPrevPeriod, + onNextPeriod +}) => { + return ( +
+ + +
+ {selectedPeriod} +
+ + +
+ ); +}; + +export default PeriodSelector; diff --git a/src/components/analytics/SummaryCards.tsx b/src/components/analytics/SummaryCards.tsx new file mode 100644 index 0000000..75697d4 --- /dev/null +++ b/src/components/analytics/SummaryCards.tsx @@ -0,0 +1,50 @@ + +import React from 'react'; +import { Wallet, CreditCard, PiggyBank } from 'lucide-react'; +import { formatCurrency } from '@/utils/formatters'; + +interface SummaryCardsProps { + totalBudget: number; + totalExpense: number; + savingsPercentage: number; +} + +const SummaryCards: React.FC = ({ + totalBudget, + totalExpense, + savingsPercentage +}) => { + return ( +
+
+
+ +

예산

+
+

+ {formatCurrency(totalBudget)} +

+
+
+
+ +

지출

+
+

+ {formatCurrency(totalExpense)} +

+
+
+
+ +

저축

+
+

+ {savingsPercentage}% +

+
+
+ ); +}; + +export default SummaryCards; diff --git a/src/pages/Analytics.tsx b/src/pages/Analytics.tsx index 897fddf..29ac245 100644 --- a/src/pages/Analytics.tsx +++ b/src/pages/Analytics.tsx @@ -3,14 +3,16 @@ import React, { useState, useEffect } from 'react'; import NavBar from '@/components/NavBar'; import ExpenseChart from '@/components/ExpenseChart'; import AddTransactionButton from '@/components/AddTransactionButton'; -import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend } from 'recharts'; -import { ChevronLeft, ChevronRight, Wallet, CreditCard, PiggyBank } from 'lucide-react'; import { useBudget } from '@/contexts/BudgetContext'; -import { formatCurrency } from '@/utils/formatters'; -import { EXPENSE_CATEGORIES } from '@/constants/categoryIcons'; import { MONTHS_KR } from '@/hooks/useTransactions'; import { useIsMobile } from '@/hooks/use-mobile'; +// 새로 분리한 컴포넌트들 불러오기 +import PeriodSelector from '@/components/analytics/PeriodSelector'; +import SummaryCards from '@/components/analytics/SummaryCards'; +import MonthlyComparisonChart from '@/components/analytics/MonthlyComparisonChart'; +import CategorySpendingList from '@/components/analytics/CategorySpendingList'; + const Analytics = () => { const [selectedPeriod, setSelectedPeriod] = useState('이번 달'); const { budgetData, getCategorySpending, transactions } = useBudget(); @@ -22,7 +24,7 @@ const Analytics = () => { const savings = Math.max(0, totalBudget - totalExpense); const savingsPercentage = totalBudget > 0 ? Math.round(savings / totalBudget * 100) : 0; - // 카테고리별 지출 차트 데이터 생성 (데이터 초기화 고려) + // 카테고리별 지출 차트 데이터 생성 const categorySpending = getCategorySpending(); const expenseData = categorySpending.map(category => ({ name: category.title, @@ -32,10 +34,10 @@ const Analytics = () => { category.title === '교통비' ? '#2E7D32' : '#4CAF50' })); - // 최근 6개월 데이터를 위한 상태 (빈 데이터로 초기화) + // 최근 6개월 데이터를 위한 상태 const [monthlyData, setMonthlyData] = useState([]); - // 월별 데이터 생성 (초기화 로직 개선) + // 월별 데이터 생성 useEffect(() => { // 현재 월 가져오기 const today = new Date(); @@ -65,39 +67,16 @@ const Analytics = () => { setMonthlyData(last6Months); }, [totalBudget, totalExpense]); - - // Custom formatter for Y-axis that removes currency symbol and uses K format - const formatYAxisTick = (value: number) => { - return `${Math.round(value / 1000)}K`; - }; - - // Custom formatter for tooltip that keeps the original formatting with currency symbol - const formatTooltip = (value: number | string) => { - if (typeof value === 'number') { - return formatCurrency(value); - } - return value; - }; - // 이전/다음 기간 이동 처리 (현재는 UI만 표시) + // 이전/다음 기간 이동 처리 const handlePrevPeriod = () => { - // 필요에 따라 구현 console.log('이전 기간으로 이동'); }; const handleNextPeriod = () => { - // 필요에 따라 구현 console.log('다음 기간으로 이동'); }; - // 그래프 데이터 없을 때 표시할 빈 상태 메시지 - const EmptyGraphState = () => ( -
-

데이터가 없습니다

-

지출 내역을 추가하면 그래프가 표시됩니다

-
- ); - return (
@@ -106,87 +85,30 @@ const Analytics = () => {

지출 분석

{/* Period Selector */} -
- - -
- {selectedPeriod} -
- - -
+ {/* Summary Cards */} -
-
-
- -

예산

-
-

- {formatCurrency(totalBudget)} -

-
-
-
- -

지출

-
-

- {formatCurrency(totalExpense)} -

-
-
-
- -

저축

-
-

- {savingsPercentage}% -

-
-
+ - {/* Monthly Comparison - MOVED UP */} + {/* Monthly Comparison Chart */}

월별 그래프

-
- {monthlyData.length > 0 ? ( - - - - - - - - - - - ) : ( - - )} -
+
- {/* Category Pie Chart - MOVED DOWN */} + {/* Category Pie Chart */}

카테고리별 지출

{expenseData.some(item => item.value > 0) ? ( @@ -198,35 +120,10 @@ const Analytics = () => { {/* Top Spending Categories */}

주요 지출 카테고리

-
- {categorySpending.some(cat => cat.current > 0) ? ( -
- {categorySpending.map((category) => ( -
-
-
- {category.title} -
-
-

- {formatCurrency(category.current)} -

-

- {totalExpense > 0 ? Math.round(category.current / totalExpense * 100) : 0}% -

-
-
- ))} -
- ) : ( -
-

아직 지출 내역이 없습니다

-
- )} -
+