189 lines
6.3 KiB
TypeScript
189 lines
6.3 KiB
TypeScript
|
|
import React, { useState, useEffect } 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';
|
|
|
|
const Transactions = () => {
|
|
const {
|
|
transactions,
|
|
isLoading,
|
|
selectedMonth,
|
|
searchQuery,
|
|
setSearchQuery,
|
|
handlePrevMonth,
|
|
handleNextMonth,
|
|
updateTransaction,
|
|
deleteTransaction,
|
|
totalExpenses,
|
|
} = useTransactions();
|
|
|
|
const { budgetData } = useBudget();
|
|
const [isDataLoaded, setIsDataLoaded] = useState(false);
|
|
|
|
// 데이터 로드 상태 관리
|
|
useEffect(() => {
|
|
if (budgetData && !isLoading) {
|
|
setIsDataLoaded(true);
|
|
}
|
|
}, [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);
|
|
});
|
|
|
|
// 페이지 포커스나 가시성 변경 시 데이터 새로고침
|
|
useEffect(() => {
|
|
const handleVisibilityChange = () => {
|
|
if (document.visibilityState === 'visible') {
|
|
console.log('거래내역 페이지 보임 - 데이터 새로고침');
|
|
// 상태 업데이트 트리거
|
|
setIsDataLoaded(prev => !prev);
|
|
}
|
|
};
|
|
|
|
const handleFocus = () => {
|
|
console.log('거래내역 페이지 포커스 - 데이터 새로고침');
|
|
// 상태 업데이트 트리거
|
|
setIsDataLoaded(prev => !prev);
|
|
};
|
|
|
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
window.addEventListener('focus', handleFocus);
|
|
|
|
return () => {
|
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
window.removeEventListener('focus', handleFocus);
|
|
};
|
|
}, []);
|
|
|
|
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)}
|
|
/>
|
|
</div>
|
|
|
|
{/* Month Selector */}
|
|
<div className="flex items-center justify-between mb-5">
|
|
<button
|
|
className="neuro-flat p-2 rounded-full"
|
|
onClick={handlePrevMonth}
|
|
>
|
|
<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}
|
|
>
|
|
<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 State */}
|
|
{isLoading && (
|
|
<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">로딩 중...</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Empty State */}
|
|
{!isLoading && 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('')}
|
|
>
|
|
검색 초기화
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Transactions By Date */}
|
|
{!isLoading && transactions.length > 0 && (
|
|
<div className="space-y-6 mb-[50px]">
|
|
{Object.entries(groupedTransactions).map(([date, transactions]) => (
|
|
<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">
|
|
{transactions.map(transaction => (
|
|
<TransactionCard
|
|
key={transaction.id}
|
|
transaction={transaction}
|
|
onUpdate={updateTransaction}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<AddTransactionButton />
|
|
<NavBar />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Transactions;
|