From f1f9227abff12344f5746e331613b544276dea19 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 23:24:12 +0000 Subject: [PATCH] Refactor Transactions page Refactors the Transactions page into smaller, more manageable components to improve code organization and maintainability. The functionality remains the same. --- .../transactions/EmptyTransactions.tsx | 37 ++++ .../transactions/TransactionDateGroup.tsx | 37 ++++ .../transactions/TransactionsContent.tsx | 61 +++++++ .../transactions/TransactionsHeader.tsx | 87 +++++++++ .../transactions/TransactionsList.tsx | 29 +++ .../filterOperations/useMonthSelection.ts | 2 + src/pages/Transactions.tsx | 169 +++++------------- 7 files changed, 296 insertions(+), 126 deletions(-) create mode 100644 src/components/transactions/EmptyTransactions.tsx create mode 100644 src/components/transactions/TransactionDateGroup.tsx create mode 100644 src/components/transactions/TransactionsContent.tsx create mode 100644 src/components/transactions/TransactionsHeader.tsx create mode 100644 src/components/transactions/TransactionsList.tsx diff --git a/src/components/transactions/EmptyTransactions.tsx b/src/components/transactions/EmptyTransactions.tsx new file mode 100644 index 0000000..eca4186 --- /dev/null +++ b/src/components/transactions/EmptyTransactions.tsx @@ -0,0 +1,37 @@ + +import React from 'react'; + +interface EmptyTransactionsProps { + searchQuery: string; + selectedMonth: string; + setSearchQuery: (query: string) => void; + isDisabled: boolean; +} + +const EmptyTransactions: React.FC = ({ + searchQuery, + selectedMonth, + setSearchQuery, + isDisabled +}) => { + return ( +
+

+ {searchQuery.trim() + ? '검색 결과가 없습니다.' + : `${selectedMonth}에 등록된 지출이 없습니다.`} +

+ {searchQuery.trim() && ( + + )} +
+ ); +}; + +export default EmptyTransactions; diff --git a/src/components/transactions/TransactionDateGroup.tsx b/src/components/transactions/TransactionDateGroup.tsx new file mode 100644 index 0000000..8a29cdb --- /dev/null +++ b/src/components/transactions/TransactionDateGroup.tsx @@ -0,0 +1,37 @@ + +import React from 'react'; +import TransactionCard, { Transaction } from '@/components/TransactionCard'; + +interface TransactionDateGroupProps { + date: string; + transactions: Transaction[]; + onTransactionDelete: (id: string) => void; +} + +const TransactionDateGroup: React.FC = ({ + date, + transactions, + onTransactionDelete +}) => { + return ( +
+
+
+

{date}

+
+
+ +
+ {transactions.map(transaction => ( + + ))} +
+
+ ); +}; + +export default TransactionDateGroup; diff --git a/src/components/transactions/TransactionsContent.tsx b/src/components/transactions/TransactionsContent.tsx new file mode 100644 index 0000000..d362d0b --- /dev/null +++ b/src/components/transactions/TransactionsContent.tsx @@ -0,0 +1,61 @@ + +import React from 'react'; +import { Loader2 } from 'lucide-react'; +import { Transaction } from '@/components/TransactionCard'; +import TransactionsList from './TransactionsList'; +import EmptyTransactions from './EmptyTransactions'; + +interface TransactionsContentProps { + isLoading: boolean; + isProcessing: boolean; + transactions: Transaction[]; + groupedTransactions: Record; + searchQuery: string; + selectedMonth: string; + setSearchQuery: (query: string) => void; + onTransactionDelete: (id: string) => void; + isDisabled: boolean; +} + +const TransactionsContent: React.FC = ({ + isLoading, + isProcessing, + transactions, + groupedTransactions, + searchQuery, + selectedMonth, + setSearchQuery, + onTransactionDelete, + isDisabled +}) => { + if (isLoading || isProcessing) { + return ; + } + + if (!isLoading && !isProcessing && transactions.length === 0) { + return ( + + ); + } + + return ( + + ); +}; + +const LoadingState: React.FC<{ isProcessing: boolean }> = ({ isProcessing }) => ( +
+ + {isProcessing ? '처리 중...' : '로딩 중...'} +
+); + +export default TransactionsContent; diff --git a/src/components/transactions/TransactionsHeader.tsx b/src/components/transactions/TransactionsHeader.tsx new file mode 100644 index 0000000..cf5c3eb --- /dev/null +++ b/src/components/transactions/TransactionsHeader.tsx @@ -0,0 +1,87 @@ + +import React from 'react'; +import { Calendar, Search, ChevronLeft, ChevronRight } from 'lucide-react'; +import { formatCurrency } from '@/utils/formatters'; + +interface TransactionsHeaderProps { + selectedMonth: string; + searchQuery: string; + setSearchQuery: (query: string) => void; + handlePrevMonth: () => void; + handleNextMonth: () => void; + budgetData: any; + totalExpenses: number; + isDisabled: boolean; +} + +const TransactionsHeader: React.FC = ({ + selectedMonth, + searchQuery, + setSearchQuery, + handlePrevMonth, + handleNextMonth, + budgetData, + totalExpenses, + isDisabled +}) => { + return ( +
+

지출 내역

+ + {/* Search */} +
+ + setSearchQuery(e.target.value)} + disabled={isDisabled} + /> +
+ + {/* Month Selector */} +
+ + +
+ + {selectedMonth} +
+ + +
+ + {/* Summary */} +
+
+

총 예산

+

+ {formatCurrency(budgetData?.monthly?.targetAmount || 0)} +

+
+
+

총 지출

+

+ {formatCurrency(totalExpenses)} +

+
+
+
+ ); +}; + +export default TransactionsHeader; diff --git a/src/components/transactions/TransactionsList.tsx b/src/components/transactions/TransactionsList.tsx new file mode 100644 index 0000000..1b6fd67 --- /dev/null +++ b/src/components/transactions/TransactionsList.tsx @@ -0,0 +1,29 @@ + +import React from 'react'; +import TransactionCard, { Transaction } from '@/components/TransactionCard'; +import TransactionDateGroup from './TransactionDateGroup'; + +interface TransactionsListProps { + groupedTransactions: Record; + onTransactionDelete: (id: string) => void; +} + +const TransactionsList: React.FC = ({ + groupedTransactions, + onTransactionDelete +}) => { + return ( +
+ {Object.entries(groupedTransactions).map(([date, dateTransactions]) => ( + + ))} +
+ ); +}; + +export default TransactionsList; diff --git a/src/hooks/transactions/filterOperations/useMonthSelection.ts b/src/hooks/transactions/filterOperations/useMonthSelection.ts index 27a0731..afb3f8b 100644 --- a/src/hooks/transactions/filterOperations/useMonthSelection.ts +++ b/src/hooks/transactions/filterOperations/useMonthSelection.ts @@ -1,4 +1,6 @@ +import { getPrevMonth, getNextMonth } from '../dateUtils'; + /** * 월 선택 관련 훅 * 이전/다음 월 선택 기능을 제공합니다. diff --git a/src/pages/Transactions.tsx b/src/pages/Transactions.tsx index 975b0af..723c0b3 100644 --- a/src/pages/Transactions.tsx +++ b/src/pages/Transactions.tsx @@ -1,12 +1,12 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import NavBar from '@/components/NavBar'; -import TransactionCard from '@/components/TransactionCard'; import AddTransactionButton from '@/components/AddTransactionButton'; -import { Calendar, Search, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; -import { formatCurrency } from '@/utils/formatters'; -import { useTransactions, MONTHS_KR } from '@/hooks/transactions'; import { useBudget } from '@/contexts/BudgetContext'; +import { useTransactions } from '@/hooks/transactions'; +import TransactionsHeader from '@/components/transactions/TransactionsHeader'; +import TransactionsContent from '@/components/transactions/TransactionsContent'; +import { Transaction } from '@/components/TransactionCard'; const Transactions = () => { const { @@ -32,17 +32,6 @@ const Transactions = () => { } }, [budgetData, isLoading]); - // 트랜잭션을 날짜별로 그룹화 - const groupedTransactions: Record = {}; - - transactions.forEach(transaction => { - const datePart = transaction.date.split(',')[0]; - if (!groupedTransactions[datePart]) { - groupedTransactions[datePart] = []; - } - groupedTransactions[datePart].push(transaction); - }); - // 트랜잭션 삭제 핸들러 (예외 처리 개선) const handleTransactionDelete = (id: string) => { try { @@ -88,122 +77,50 @@ const Transactions = () => { }; }, [refreshTransactions]); + // 트랜잭션을 날짜별로 그룹화 + const groupTransactionsByDate = (transactions: Transaction[]): Record => { + const grouped: Record = {}; + + transactions.forEach(transaction => { + const datePart = transaction.date.split(',')[0]; + if (!grouped[datePart]) { + grouped[datePart] = []; + } + grouped[datePart].push(transaction); + }); + + return grouped; + }; + // 로딩이나 처리 중이면 비활성화된 UI 상태 표시 const isDisabled = isLoading || isProcessing; + const groupedTransactions = groupTransactionsByDate(transactions); return (
- {/* Header */} -
-

지출 내역

- - {/* Search */} -
- - setSearchQuery(e.target.value)} - disabled={isDisabled} - /> -
- - {/* Month Selector */} -
- - -
- - {selectedMonth} -
- - -
- - {/* Summary */} -
-
-

총 예산

-

- {formatCurrency(budgetData?.monthly?.targetAmount || 0)} -

-
-
-

총 지출

-

- {formatCurrency(totalExpenses)} -

-
-
-
- - {/* Loading or Processing State */} - {(isLoading || isProcessing) && ( -
- - {isProcessing ? '처리 중...' : '로딩 중...'} -
- )} - - {/* Empty State */} - {!isLoading && !isProcessing && transactions.length === 0 && ( -
-

- {searchQuery.trim() - ? '검색 결과가 없습니다.' - : `${selectedMonth}에 등록된 지출이 없습니다.`} -

- {searchQuery.trim() && ( - - )} -
- )} - - {/* Transactions By Date */} - {!isLoading && !isProcessing && transactions.length > 0 && ( -
- {Object.entries(groupedTransactions).map(([date, dateTransactions]) => ( -
-
-
-

{date}

-
-
- -
- {dateTransactions.map(transaction => ( - - ))} -
-
- ))} -
- )} + + +