'use client'; /** * 입출금 계좌조회 - UniversalListPage 마이그레이션 * * 기존 IntegratedListTemplateV2 → UniversalListPage config 기반으로 변환 * - 서버 사이드 필터링/페이지네이션 * - dateRangeSelector (헤더 액션) * - beforeTableContent: 새로고침 버튼 * - tableHeaderActions: 3개 Select 필터 (결제계좌, 입출금유형, 정렬) * - tableFooter: 합계 행 * - 수정 버튼 (입금/출금 상세 페이지 이동) */ 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 { MobileCard } from '@/components/organisms/MobileCard'; import { UniversalListPage, type UniversalListConfig, type SelectionHandlers, type RowClickHandlers, type StatCard, } from '@/components/templates/UniversalListPage'; 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'; // ===== 테이블 컬럼 정의 ===== const tableColumns = [ { 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]' }, ]; // ===== 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(); // ===== 외부 상태 (UniversalListPage 외부에서 관리) ===== 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 [searchQuery, setSearchQuery] = useState(''); const [sortOption, setSortOption] = useState('latest'); const [accountFilter, setAccountFilter] = useState('all'); const [transactionTypeFilter, setTransactionTypeFilter] = useState('all'); const [currentPage, setCurrentPage] = useState(initialPagination?.currentPage || 1); 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 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: 20, 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 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 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 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]); // ===== UniversalListPage Config ===== const config: UniversalListConfig = useMemo( () => ({ // 페이지 기본 정보 title: '입출금 계좌조회', description: '은행 계좌 정보와 입출금 내역을 조회할 수 있습니다', icon: Building2, basePath: '/accounting/bank-transactions', // ID 추출 idField: 'id', // API 액션 actions: { getList: async () => { return { success: true, data: data, totalCount: pagination.total, }; }, }, // 테이블 컬럼 columns: tableColumns, // 서버 사이드 필터링 (클라이언트 사이드 아님) clientSideFiltering: false, itemsPerPage: 20, // 검색 searchPlaceholder: '은행명, 계좌명, 거래처, 입금자/수취인 검색...', onSearchChange: setSearchQuery, // 필터 설정 (모바일용) filterConfig: [ { key: 'account', label: '결제계좌', type: 'single', options: accountOptions.filter((o) => o.value !== 'all'), }, { key: 'transactionType', label: '입출금유형', type: 'single', options: TRANSACTION_TYPE_FILTER_OPTIONS.filter((o) => o.value !== 'all').map((o) => ({ value: o.value, label: o.label, })), }, { key: 'sortBy', label: '정렬', type: 'single', options: SORT_OPTIONS.map((o) => ({ value: o.value, label: o.label, })), }, ], initialFilters: { account: 'all', transactionType: 'all', sortBy: 'latest', }, filterTitle: '계좌 필터', // 날짜 선택기 (헤더 액션) dateRangeSelector: { enabled: true, startDate, endDate, onStartDateChange: setStartDate, onEndDateChange: setEndDate, }, // 헤더 액션: 새로고침 버튼 headerActions: () => ( ), // 테이블 헤더 액션 (3개 필터) tableHeaderActions: () => (
{/* 결제계좌 필터 */} {/* 입출금유형 필터 */} {/* 정렬 */}
), // 테이블 푸터 (합계 행) tableFooter: ( 합계 {tableTotals.totalDeposit.toLocaleString()} {tableTotals.totalWithdrawal.toLocaleString()} ), // Stats 카드 computeStats: (): StatCard[] => [ { 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', }, ], // 테이블 행 렌더링 renderTableRow: ( item: BankTransaction, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => { const isTypeUnset = item.transactionType === 'unset'; return ( {/* 체크박스 */} e.stopPropagation()}> {/* 번호 */} {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()}> {handlers.isSelected && ( )} ); }, // 모바일 카드 렌더링 renderMobileCard: ( item: BankTransaction, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => ( 0 ? `${item.depositAmount.toLocaleString()}원` : '-', }, { label: '출금', value: item.withdrawalAmount > 0 ? `${item.withdrawalAmount.toLocaleString()}원` : '-', }, { label: '잔액', value: `${item.balance.toLocaleString()}원` }, { label: '거래처', value: item.vendorName || '-' }, { label: '입출금 유형', value: getTransactionTypeLabel(item) }, ]} actions={ handlers.isSelected ? ( ) : undefined } /> ), }), [ data, pagination, summary, accountOptions, accountFilter, transactionTypeFilter, sortOption, startDate, endDate, tableTotals, isLoading, handleRefresh, handleEditClick, getTransactionTypeLabel, ] ); return ( ); }