Refactor Transactions page
Refactors the Transactions page into smaller, more manageable components to improve code organization and maintainability. The functionality remains the same.
This commit is contained in:
@@ -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<string, typeof transactions> = {};
|
||||
|
||||
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<string, Transaction[]> => {
|
||||
const grouped: Record<string, Transaction[]> = {};
|
||||
|
||||
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 (
|
||||
<div className="min-h-screen bg-neuro-background pb-24">
|
||||
<div className="max-w-md mx-auto px-6">
|
||||
{/* Header */}
|
||||
<header className="py-8">
|
||||
<h1 className="text-2xl font-bold neuro-text mb-5">지출 내역</h1>
|
||||
|
||||
{/* Search */}
|
||||
<div className="neuro-pressed mb-5 flex items-center px-4 py-3 rounded-xl">
|
||||
<Search size={18} className="text-gray-500 mr-2" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="지출 검색..."
|
||||
className="bg-transparent flex-1 outline-none text-sm"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Month Selector */}
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<button
|
||||
className="neuro-flat p-2 rounded-full"
|
||||
onClick={handlePrevMonth}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
<ChevronLeft size={20} />
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar size={18} className="text-neuro-income" />
|
||||
<span className="font-medium text-lg">{selectedMonth}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="neuro-flat p-2 rounded-full"
|
||||
onClick={handleNextMonth}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
<ChevronRight size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Summary */}
|
||||
<div className="grid grid-cols-2 gap-4 mb-8">
|
||||
<div className="neuro-card">
|
||||
<p className="text-sm text-gray-500 mb-1">총 예산</p>
|
||||
<p className="text-lg font-bold text-neuro-income">
|
||||
{formatCurrency(budgetData?.monthly?.targetAmount || 0)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="neuro-card">
|
||||
<p className="text-sm text-gray-500 mb-1">총 지출</p>
|
||||
<p className="text-lg font-bold text-neuro-income">
|
||||
{formatCurrency(totalExpenses)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Loading or Processing State */}
|
||||
{(isLoading || isProcessing) && (
|
||||
<div className="flex justify-center items-center py-10">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-neuro-income" />
|
||||
<span className="ml-2 text-gray-500">{isProcessing ? '처리 중...' : '로딩 중...'}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Empty State */}
|
||||
{!isLoading && !isProcessing && transactions.length === 0 && (
|
||||
<div className="text-center py-10">
|
||||
<p className="text-gray-500 mb-3">
|
||||
{searchQuery.trim()
|
||||
? '검색 결과가 없습니다.'
|
||||
: `${selectedMonth}에 등록된 지출이 없습니다.`}
|
||||
</p>
|
||||
{searchQuery.trim() && (
|
||||
<button
|
||||
className="text-neuro-income"
|
||||
onClick={() => setSearchQuery('')}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
검색 초기화
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Transactions By Date */}
|
||||
{!isLoading && !isProcessing && transactions.length > 0 && (
|
||||
<div className="space-y-6 mb-[50px]">
|
||||
{Object.entries(groupedTransactions).map(([date, dateTransactions]) => (
|
||||
<div key={date}>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="h-1 flex-1 neuro-pressed"></div>
|
||||
<h2 className="text-sm font-medium text-gray-500">{date}</h2>
|
||||
<div className="h-1 flex-1 neuro-pressed"></div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3">
|
||||
{dateTransactions.map(transaction => (
|
||||
<TransactionCard
|
||||
key={transaction.id}
|
||||
transaction={transaction}
|
||||
onDelete={handleTransactionDelete}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<TransactionsHeader
|
||||
selectedMonth={selectedMonth}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
handlePrevMonth={handlePrevMonth}
|
||||
handleNextMonth={handleNextMonth}
|
||||
budgetData={budgetData}
|
||||
totalExpenses={totalExpenses}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
|
||||
<TransactionsContent
|
||||
isLoading={isLoading}
|
||||
isProcessing={isProcessing}
|
||||
transactions={transactions}
|
||||
groupedTransactions={groupedTransactions}
|
||||
searchQuery={searchQuery}
|
||||
selectedMonth={selectedMonth}
|
||||
setSearchQuery={setSearchQuery}
|
||||
onTransactionDelete={handleTransactionDelete}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AddTransactionButton />
|
||||
|
||||
Reference in New Issue
Block a user