'use client'; /** * 계좌관리 - UniversalListPage 마이그레이션 * * 기존 IntegratedListTemplateV2 → UniversalListPage config 기반으로 변환 * - 클라이언트 사이드 필터링 (전체 데이터 로드 후 필터) * - 삭제/일괄삭제 다이얼로그 */ import { useState, useMemo, useCallback, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Landmark, Pencil, Trash2, Plus, Loader2, } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Badge } from '@/components/ui/badge'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { TableRow, TableCell } from '@/components/ui/table'; import { UniversalListPage, type UniversalListConfig, type SelectionHandlers, type RowClickHandlers, type ListParams, } from '@/components/templates/UniversalListPage'; import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard'; import type { Account } from './types'; import { BANK_LABELS, ACCOUNT_STATUS_LABELS, ACCOUNT_STATUS_COLORS, } from './types'; import { getBankAccounts, deleteBankAccount, deleteBankAccounts } from './actions'; // ===== 계좌번호 마스킹 함수 ===== const maskAccountNumber = (accountNumber: string): string => { if (accountNumber.length <= 8) return accountNumber; const parts = accountNumber.split('-'); if (parts.length >= 3) { // 1234-****-****-1234 형태 return parts.map((part, idx) => { if (idx === 0 || idx === parts.length - 1) return part; return '****'; }).join('-'); } // 단순 형태: 앞 4자리-****-뒤 4자리 const first = accountNumber.slice(0, 4); const last = accountNumber.slice(-4); return `${first}-****-****-${last}`; }; export function AccountManagement() { const router = useRouter(); // ===== 상태 관리 ===== const itemsPerPage = 20; // 로딩 상태 const [isDeleting, setIsDeleting] = useState(false); // 삭제 다이얼로그 const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [deleteTargetId, setDeleteTargetId] = useState(null); const [showBulkDeleteDialog, setShowBulkDeleteDialog] = useState(false); const [bulkDeleteIds, setBulkDeleteIds] = useState([]); // ===== 액션 핸들러 ===== const handleRowClick = useCallback((item: Account) => { router.push(`/ko/settings/accounts/${item.id}`); }, [router]); const handleEdit = useCallback((item: Account) => { router.push(`/ko/settings/accounts/${item.id}?mode=edit`); }, [router]); const handleDeleteClick = useCallback((id: string) => { setDeleteTargetId(id); setShowDeleteDialog(true); }, []); const handleConfirmDelete = useCallback(async () => { if (!deleteTargetId) return; setIsDeleting(true); try { const result = await deleteBankAccount(Number(deleteTargetId)); if (result.success) { toast.success('계좌가 삭제되었습니다.'); // 페이지 새로고침으로 데이터 갱신 window.location.reload(); } else { toast.error(result.error || '계좌 삭제에 실패했습니다.'); } } catch { toast.error('서버 오류가 발생했습니다.'); } finally { setIsDeleting(false); setShowDeleteDialog(false); setDeleteTargetId(null); } }, [deleteTargetId]); const handleBulkDelete = useCallback((selectedIds: string[]) => { if (selectedIds.length > 0) { setBulkDeleteIds(selectedIds); setShowBulkDeleteDialog(true); } }, []); const handleConfirmBulkDelete = useCallback(async () => { const ids = bulkDeleteIds.map(id => Number(id)); setIsDeleting(true); try { const result = await deleteBankAccounts(ids); if (result.success) { toast.success(`${result.deletedCount}개의 계좌가 삭제되었습니다.`); if (result.error) { toast.warning(result.error); } // 페이지 새로고침으로 데이터 갱신 window.location.reload(); } else { toast.error(result.error || '계좌 삭제에 실패했습니다.'); } } catch { toast.error('서버 오류가 발생했습니다.'); } finally { setIsDeleting(false); setShowBulkDeleteDialog(false); setBulkDeleteIds([]); } }, [bulkDeleteIds]); const handleCreate = useCallback(() => { router.push('/ko/settings/accounts/new'); }, [router]); // ===== UniversalListPage Config ===== const config: UniversalListConfig = useMemo( () => ({ // 페이지 기본 정보 title: '계좌관리', description: '계좌 목록을 관리합니다', icon: Landmark, basePath: '/settings/accounts', // ID 추출 idField: 'id', getItemId: (item: Account) => String(item.id), // API 액션 actions: { getList: async (params?: ListParams) => { try { const result = await getBankAccounts(); if (result.success && result.data) { // 클라이언트 사이드 검색 필터링 let filteredData = result.data; if (params?.search) { const search = params.search.toLowerCase(); filteredData = result.data.filter(item => item.accountName.toLowerCase().includes(search) || item.accountNumber.includes(search) || item.accountHolder.toLowerCase().includes(search) || BANK_LABELS[item.bankCode]?.toLowerCase().includes(search) ); } return { success: true, data: filteredData, totalCount: filteredData.length, totalPages: Math.ceil(filteredData.length / itemsPerPage), }; } return { success: false, error: result.error || '계좌 목록을 불러오는데 실패했습니다.' }; } catch { return { success: false, error: '서버 오류가 발생했습니다.' }; } }, }, // 테이블 컬럼 columns: [ { key: 'no', label: '번호', className: 'text-center w-[60px]' }, { key: 'bank', label: '은행', className: 'min-w-[100px]' }, { key: 'accountNumber', label: '계좌번호', className: 'min-w-[160px]' }, { key: 'accountName', label: '계좌명', className: 'min-w-[120px]' }, { key: 'accountHolder', label: '예금주', className: 'min-w-[80px]' }, { key: 'status', label: '상태', className: 'min-w-[80px]' }, { key: 'actions', label: '작업', className: 'w-[100px] text-right' }, ], // 클라이언트 사이드 필터링 clientSideFiltering: true, itemsPerPage, // 검색 searchPlaceholder: '은행명, 계좌번호, 계좌명, 예금주 검색...', // 헤더 액션 headerActions: () => ( ), // 일괄 삭제 핸들러 onBulkDelete: handleBulkDelete, // 테이블 행 렌더링 renderTableRow: ( item: Account, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => { return ( handleRowClick(item)} > e.stopPropagation()}> {globalIndex} {BANK_LABELS[item.bankCode] || item.bankCode} {maskAccountNumber(item.accountNumber)} {item.accountName} {item.accountHolder} {ACCOUNT_STATUS_LABELS[item.status]} e.stopPropagation()}> {handlers.isSelected && (
)}
); }, // 모바일 카드 렌더링 renderMobileCard: ( item: Account, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => { return ( #{globalIndex} {BANK_LABELS[item.bankCode] || item.bankCode} } statusBadge={ {ACCOUNT_STATUS_LABELS[item.status]} } isSelected={handlers.isSelected} onToggleSelection={handlers.onToggle} onClick={() => handleRowClick(item)} infoGrid={
} actions={ handlers.isSelected ? (
) : undefined } /> ); }, }), [handleCreate, handleRowClick, handleEdit, handleDeleteClick, handleBulkDelete] ); return ( <> {/* 단일 삭제 확인 다이얼로그 */} 계좌 삭제 계좌를 정말 삭제하시겠습니까?
삭제된 계좌의 과거 사용 내역은 보존됩니다.
취소 {isDeleting ? ( <> 삭제 중... ) : '삭제'}
{/* 다중 삭제 확인 다이얼로그 */} 계좌 삭제 선택하신 {bulkDeleteIds.length}개의 계좌를 정말 삭제하시겠습니까?
삭제된 계좌의 과거 사용 내역은 보존됩니다.
취소 {isDeleting ? ( <> 삭제 중... ) : '삭제'}
); }