'use client'; import { useState, useMemo, useCallback, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { format, startOfMonth, endOfMonth } from 'date-fns'; import { Building2, Pencil, RefreshCw, Loader2, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Badge } from '@/components/ui/badge'; import { TableRow, TableCell } from '@/components/ui/table'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { IntegratedListTemplateV2, type TableColumn, type StatCard, } from '@/components/templates/IntegratedListTemplateV2'; import { DateRangeSelector } from '@/components/molecules/DateRangeSelector'; import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard'; import type { BankTransaction, SortOption, } from './types'; import { TRANSACTION_KIND_LABELS, DEPOSIT_TYPE_LABELS, WITHDRAWAL_TYPE_LABELS, SORT_OPTIONS, TRANSACTION_TYPE_FILTER_OPTIONS, } from './types'; import { getBankTransactionList, getBankTransactionSummary, getBankAccountOptions } from './actions'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; // ===== Props ===== interface BankTransactionInquiryProps { initialData?: BankTransaction[]; initialSummary?: { totalDeposit: number; totalWithdrawal: number; depositUnsetCount: number; withdrawalUnsetCount: number; }; initialPagination?: { currentPage: number; lastPage: number; perPage: number; total: number; }; } export function BankTransactionInquiry({ initialData = [], initialSummary, initialPagination, }: BankTransactionInquiryProps) { const router = useRouter(); // ===== 상태 관리 ===== const [searchQuery, setSearchQuery] = useState(''); const [sortOption, setSortOption] = useState('latest'); const [accountFilter, setAccountFilter] = useState('all'); // 결제계좌 필터 const [transactionTypeFilter, setTransactionTypeFilter] = useState('all'); // 입출금유형 필터 const [selectedItems, setSelectedItems] = useState>(new Set()); const [currentPage, setCurrentPage] = useState(initialPagination?.currentPage || 1); const itemsPerPage = 20; const [isLoading, setIsLoading] = useState(!initialData.length); // 날짜 범위 상태 const [startDate, setStartDate] = useState(() => format(startOfMonth(new Date()), 'yyyy-MM-dd')); const [endDate, setEndDate] = useState(() => format(endOfMonth(new Date()), 'yyyy-MM-dd')); // 데이터 상태 const [data, setData] = useState(initialData); const [summary, setSummary] = useState( initialSummary || { totalDeposit: 0, totalWithdrawal: 0, depositUnsetCount: 0, withdrawalUnsetCount: 0 } ); const [pagination, setPagination] = useState( initialPagination || { currentPage: 1, lastPage: 1, perPage: 20, total: 0 } ); const [accountOptions, setAccountOptions] = useState<{ value: string; label: string }[]>([ { value: 'all', label: '전체' } ]); // ===== 데이터 로드 ===== const loadData = useCallback(async () => { setIsLoading(true); try { // 정렬 옵션 매핑 const sortMapping: Record = { latest: { sortBy: 'transaction_date', sortDir: 'desc' }, oldest: { sortBy: 'transaction_date', sortDir: 'asc' }, amountHigh: { sortBy: 'amount', sortDir: 'desc' }, amountLow: { sortBy: 'amount', sortDir: 'asc' }, }; const sortParams = sortMapping[sortOption]; const [listResult, summaryResult, accountsResult] = await Promise.all([ getBankTransactionList({ page: currentPage, perPage: itemsPerPage, startDate, endDate, bankAccountId: accountFilter !== 'all' ? parseInt(accountFilter, 10) : undefined, transactionType: transactionTypeFilter !== 'all' ? transactionTypeFilter : undefined, search: searchQuery || undefined, sortBy: sortParams.sortBy, sortDir: sortParams.sortDir, }), getBankTransactionSummary({ startDate, endDate }), getBankAccountOptions(), ]); if (listResult.success) { setData(listResult.data); setPagination(listResult.pagination); } if (summaryResult.success && summaryResult.data) { setSummary(summaryResult.data); } if (accountsResult.success) { setAccountOptions([ { value: 'all', label: '전체' }, ...accountsResult.data.map(acc => ({ value: String(acc.id), label: acc.label })) ]); } } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[BankTransactionInquiry] loadData error:', error); } finally { setIsLoading(false); } }, [currentPage, startDate, endDate, accountFilter, transactionTypeFilter, searchQuery, sortOption]); // 데이터 로드 (필터 변경 시) useEffect(() => { loadData(); }, [loadData]); // ===== 체크박스 핸들러 ===== const toggleSelection = useCallback((id: string) => { setSelectedItems(prev => { const newSet = new Set(prev); if (newSet.has(id)) newSet.delete(id); else newSet.add(id); return newSet; }); }, []); // ===== 데이터 (서버 사이드 필터링/정렬/페이지네이션) ===== // 필터링, 정렬, 페이지네이션은 서버에서 처리됨 const totalPages = pagination.lastPage; // ===== 전체 선택 핸들러 ===== const toggleSelectAll = useCallback(() => { if (selectedItems.size === data.length && data.length > 0) { setSelectedItems(new Set()); } else { setSelectedItems(new Set(data.map(item => item.id))); } }, [selectedItems.size, data]); // ===== 수정 버튼 클릭 (상세 이동) ===== const handleEditClick = useCallback((item: BankTransaction) => { if (item.type === 'deposit') { router.push(`/ko/accounting/deposits/${item.sourceId}?mode=edit`); } else { router.push(`/ko/accounting/withdrawals/${item.sourceId}?mode=edit`); } }, [router]); // 새로고침 핸들러 const handleRefresh = useCallback(() => { loadData(); }, [loadData]); // ===== 통계 카드 ===== const statCards: StatCard[] = useMemo(() => { return [ { label: '입금', value: `${summary.totalDeposit.toLocaleString()}원`, icon: Building2, iconColor: 'text-blue-500' }, { label: '출금', value: `${summary.totalWithdrawal.toLocaleString()}원`, icon: Building2, iconColor: 'text-red-500' }, { label: '입금 유형 미설정', value: `${summary.depositUnsetCount}건`, icon: Building2, iconColor: 'text-green-500' }, { label: '출금 유형 미설정', value: `${summary.withdrawalUnsetCount}건`, icon: Building2, iconColor: 'text-orange-500' }, ]; }, [summary]); // ===== 테이블 컬럼 (14개) ===== const tableColumns: TableColumn[] = useMemo(() => [ { key: 'bankName', label: '은행명' }, { key: 'accountName', label: '계좌명' }, { key: 'transactionDate', label: '거래일시' }, { key: 'type', label: '구분', className: 'text-center' }, { key: 'note', label: '적요' }, { key: 'vendorName', label: '거래처' }, { key: 'depositorName', label: '입금자/수취인' }, { key: 'depositAmount', label: '입금', className: 'text-right' }, { key: 'withdrawalAmount', label: '출금', className: 'text-right' }, { key: 'balance', label: '잔액', className: 'text-right' }, { key: 'transactionType', label: '입출금 유형', className: 'text-center' }, { key: 'actions', label: '작업', className: 'text-center w-[80px]' }, ], []); // ===== 유형 라벨 가져오기 ===== const getTransactionTypeLabel = useCallback((item: BankTransaction) => { if (!item.transactionType) return '미설정'; if (item.type === 'deposit') { return DEPOSIT_TYPE_LABELS[item.transactionType as keyof typeof DEPOSIT_TYPE_LABELS] || item.transactionType; } return WITHDRAWAL_TYPE_LABELS[item.transactionType as keyof typeof WITHDRAWAL_TYPE_LABELS] || item.transactionType; }, []); // ===== 테이블 행 렌더링 ===== const renderTableRow = useCallback((item: BankTransaction, index: number, globalIndex: number) => { const isSelected = selectedItems.has(item.id); const isTypeUnset = item.transactionType === 'unset'; return ( {/* 체크박스 */} e.stopPropagation()}> toggleSelection(item.id)} /> {/* 번호 */} {globalIndex} {/* 은행명 */} {item.bankName} {/* 계좌명 */} {item.accountName} {/* 거래일시 */} {item.transactionDate} {/* 구분 */} {TRANSACTION_KIND_LABELS[item.type]} {/* 적요 */} {item.note || '-'} {/* 거래처 */} {item.vendorName || '-'} {/* 입금자/수취인 */} {item.depositorName || '-'} {/* 입금 */} {item.depositAmount > 0 ? item.depositAmount.toLocaleString() : '-'} {/* 출금 */} {item.withdrawalAmount > 0 ? item.withdrawalAmount.toLocaleString() : '-'} {/* 잔액 */} {item.balance.toLocaleString()} {/* 입출금 유형 */} {getTransactionTypeLabel(item)} {/* 작업 */} e.stopPropagation()}> {isSelected && ( )} ); }, [selectedItems, toggleSelection, handleEditClick, getTransactionTypeLabel]); // ===== 모바일 카드 렌더링 ===== const renderMobileCard = useCallback(( item: BankTransaction, index: number, globalIndex: number, isSelected: boolean, onToggle: () => void ) => { return ( {TRANSACTION_KIND_LABELS[item.type]} {getTransactionTypeLabel(item)} } isSelected={isSelected} onToggleSelection={onToggle} infoGrid={
0 ? `${item.depositAmount.toLocaleString()}원` : '-'} /> 0 ? `${item.withdrawalAmount.toLocaleString()}원` : '-'} />
} actions={ isSelected ? ( ) : undefined } /> ); }, [handleEditClick, getTransactionTypeLabel]); // ===== 헤더 액션 (날짜 선택만) ===== const headerActions = ( ); // ===== 테이블 상단 새로고침 버튼 (출금관리 스타일) ===== const beforeTableContent = (
); // ===== 테이블 헤더 액션 (3개 필터) ===== const tableHeaderActions = (
{/* 결제계좌 필터 */} {/* 입출금유형 필터 */} {/* 정렬 */}
); // ===== 테이블 합계 계산 ===== const tableTotals = useMemo(() => { const totalDeposit = data.reduce((sum, item) => sum + item.depositAmount, 0); const totalWithdrawal = data.reduce((sum, item) => sum + item.withdrawalAmount, 0); return { totalDeposit, totalWithdrawal }; }, [data]); // ===== 테이블 하단 합계 행 ===== const tableFooter = ( 합계 {tableTotals.totalDeposit.toLocaleString()} {tableTotals.totalWithdrawal.toLocaleString()} ); return ( item.id} renderTableRow={renderTableRow} renderMobileCard={renderMobileCard} pagination={{ currentPage, totalPages, totalItems: pagination.total, itemsPerPage, onPageChange: setCurrentPage, }} /> ); }