'use client'; import { useState, useMemo, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { format } from 'date-fns'; import { Banknote, Pencil, Save, Trash2, RefreshCw, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; 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 { WithdrawalRecord, SortOption, } from './types'; import { SORT_OPTIONS, WITHDRAWAL_TYPE_LABELS, WITHDRAWAL_TYPE_FILTER_OPTIONS, ACCOUNT_SUBJECT_OPTIONS, } from './types'; import { deleteWithdrawal, updateWithdrawalTypes, getWithdrawals } from './actions'; import { toast } from 'sonner'; // ===== 컴포넌트 Props ===== interface WithdrawalManagementProps { initialData: WithdrawalRecord[]; initialPagination: { currentPage: number; lastPage: number; perPage: number; total: number; }; } export function WithdrawalManagement({ initialData, initialPagination }: WithdrawalManagementProps) { const router = useRouter(); // ===== 상태 관리 ===== const [searchQuery, setSearchQuery] = useState(''); const [sortOption, setSortOption] = useState('latest'); const [withdrawalTypeFilter, setWithdrawalTypeFilter] = useState('all'); const [vendorFilter, setVendorFilter] = useState('all'); const [selectedItems, setSelectedItems] = useState>(new Set()); const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 20; // 상단 계정과목명 선택 (저장용) const [selectedAccountSubject, setSelectedAccountSubject] = useState('unset'); // 계정과목명 저장 다이얼로그 const [showSaveDialog, setShowSaveDialog] = useState(false); // 삭제 다이얼로그 const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [deleteTargetId, setDeleteTargetId] = useState(null); // 선택 필요 알림 다이얼로그 const [showSelectWarningDialog, setShowSelectWarningDialog] = useState(false); // 날짜 범위 상태 const [startDate, setStartDate] = useState('2025-09-01'); const [endDate, setEndDate] = useState('2025-09-03'); // 출금 데이터 (서버에서 전달받은 initialData 사용) const [data, setData] = useState(initialData); // 로딩 상태 const [isRefreshing, setIsRefreshing] = useState(false); // ===== 체크박스 핸들러 ===== 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 filteredData = useMemo(() => { let result = data.filter(item => item.recipientName.includes(searchQuery) || item.accountName.includes(searchQuery) || item.note.includes(searchQuery) || item.vendorName.includes(searchQuery) ); // 거래처 필터 if (vendorFilter !== 'all') { result = result.filter(item => item.vendorName === vendorFilter); } // 출금 유형 필터 if (withdrawalTypeFilter !== 'all') { result = result.filter(item => item.withdrawalType === withdrawalTypeFilter); } // 정렬 switch (sortOption) { case 'latest': result.sort((a, b) => new Date(b.withdrawalDate).getTime() - new Date(a.withdrawalDate).getTime()); break; case 'oldest': result.sort((a, b) => new Date(a.withdrawalDate).getTime() - new Date(b.withdrawalDate).getTime()); break; case 'amountHigh': result.sort((a, b) => b.withdrawalAmount - a.withdrawalAmount); break; case 'amountLow': result.sort((a, b) => a.withdrawalAmount - b.withdrawalAmount); break; } return result; }, [data, searchQuery, vendorFilter, withdrawalTypeFilter, sortOption]); const paginatedData = useMemo(() => { const startIndex = (currentPage - 1) * itemsPerPage; return filteredData.slice(startIndex, startIndex + itemsPerPage); }, [filteredData, currentPage, itemsPerPage]); const totalPages = Math.ceil(filteredData.length / itemsPerPage); // ===== 전체 선택 핸들러 ===== const toggleSelectAll = useCallback(() => { if (selectedItems.size === filteredData.length && filteredData.length > 0) { setSelectedItems(new Set()); } else { setSelectedItems(new Set(filteredData.map(item => item.id))); } }, [selectedItems.size, filteredData]); // ===== 액션 핸들러 ===== const handleRowClick = useCallback((item: WithdrawalRecord) => { router.push(`/ko/accounting/withdrawals/${item.id}`); }, [router]); // 개별 항목 삭제 핸들러 const handleDeleteClick = useCallback((id: string) => { setDeleteTargetId(id); setShowDeleteDialog(true); }, []); // 삭제 확정 핸들러 const handleConfirmDelete = useCallback(async () => { if (deleteTargetId) { const result = await deleteWithdrawal(deleteTargetId); if (result.success) { toast.success('출금 내역이 삭제되었습니다.'); setData(prev => prev.filter(item => item.id !== deleteTargetId)); setSelectedItems(prev => { const newSet = new Set(prev); newSet.delete(deleteTargetId); return newSet; }); } else { toast.error(result.error || '삭제에 실패했습니다.'); } } setShowDeleteDialog(false); setDeleteTargetId(null); }, [deleteTargetId]); // 새로고침 핸들러 const handleRefresh = useCallback(async () => { setIsRefreshing(true); try { const result = await getWithdrawals({ perPage: 100, startDate, endDate, withdrawalType: withdrawalTypeFilter !== 'all' ? withdrawalTypeFilter : undefined, search: searchQuery || undefined, }); if (result.success) { setData(result.data); setSelectedItems(new Set()); setCurrentPage(1); toast.success('데이터를 새로고침했습니다.'); } else { toast.error(result.error || '새로고침에 실패했습니다.'); } } catch { toast.error('새로고침 중 오류가 발생했습니다.'); } finally { setIsRefreshing(false); } }, [startDate, endDate, withdrawalTypeFilter, searchQuery]); // ===== 통계 카드 (총 출금, 당월 출금, 거래처 미설정, 출금유형 미설정) ===== const statCards: StatCard[] = useMemo(() => { const totalWithdrawal = data.reduce((sum, d) => sum + d.withdrawalAmount, 0); // 당월 출금 const currentMonth = new Date().getMonth(); const currentYear = new Date().getFullYear(); const monthlyWithdrawal = data .filter(d => { const date = new Date(d.withdrawalDate); return date.getMonth() === currentMonth && date.getFullYear() === currentYear; }) .reduce((sum, d) => sum + d.withdrawalAmount, 0); // 거래처 미설정 건수 const vendorUnsetCount = data.filter(d => !d.vendorName).length; // 출금유형 미설정 건수 const withdrawalTypeUnsetCount = data.filter(d => d.withdrawalType === 'unset').length; return [ { label: '총 출금', value: `${totalWithdrawal.toLocaleString()}원`, icon: Banknote, iconColor: 'text-blue-500' }, { label: '당월 출금', value: `${monthlyWithdrawal.toLocaleString()}원`, icon: Banknote, iconColor: 'text-green-500' }, { label: '거래처 미설정', value: `${vendorUnsetCount}건`, icon: Banknote, iconColor: 'text-orange-500' }, { label: '출금유형 미설정', value: `${withdrawalTypeUnsetCount}건`, icon: Banknote, iconColor: 'text-red-500' }, ]; }, [data]); // ===== 테이블 컬럼 ===== const tableColumns: TableColumn[] = useMemo(() => [ { key: 'withdrawalDate', label: '출금일' }, { key: 'accountName', label: '출금계좌' }, { key: 'recipientName', label: '수취인명' }, { key: 'withdrawalAmount', label: '출금금액', className: 'text-right' }, { key: 'vendorName', label: '거래처' }, { key: 'note', label: '적요' }, { key: 'withdrawalType', label: '출금유형', className: 'text-center' }, { key: 'actions', label: '작업', className: 'text-center w-[80px]' }, ], []); // ===== 테이블 행 렌더링 ===== const renderTableRow = useCallback((item: WithdrawalRecord, index: number, globalIndex: number) => { const isSelected = selectedItems.has(item.id); const isVendorUnset = !item.vendorName; const isWithdrawalTypeUnset = item.withdrawalType === 'unset'; return ( handleRowClick(item)} > e.stopPropagation()}> toggleSelection(item.id)} /> {/* 출금일 */} {item.withdrawalDate} {/* 출금계좌 */} {item.accountName} {/* 수취인명 */} {item.recipientName} {/* 출금금액 */} {item.withdrawalAmount.toLocaleString()} {/* 거래처 */} {item.vendorName || '미설정'} {/* 적요 */} {item.note || '-'} {/* 출금유형 */} {WITHDRAWAL_TYPE_LABELS[item.withdrawalType]} {/* 작업 */} e.stopPropagation()}> {isSelected && (
)}
); }, [selectedItems, toggleSelection, handleRowClick, handleDeleteClick, router]); // ===== 모바일 카드 렌더링 ===== const renderMobileCard = useCallback(( item: WithdrawalRecord, index: number, globalIndex: number, isSelected: boolean, onToggle: () => void ) => { return ( {WITHDRAWAL_TYPE_LABELS[item.withdrawalType]} } isSelected={isSelected} onToggleSelection={onToggle} infoGrid={
} actions={ isSelected ? (
) : undefined } onCardClick={() => handleRowClick(item)} /> ); }, [handleRowClick, handleDeleteClick]); // ===== 헤더 액션 ===== const headerActions = ( ); // ===== 계정과목명 저장 핸들러 ===== const handleSaveAccountSubject = useCallback(() => { if (selectedItems.size === 0) { setShowSelectWarningDialog(true); return; } setShowSaveDialog(true); }, [selectedItems.size]); // 계정과목명 저장 확정 const handleConfirmSaveAccountSubject = useCallback(async () => { const ids = Array.from(selectedItems); const result = await updateWithdrawalTypes(ids, selectedAccountSubject); if (result.success) { toast.success('계정과목명이 저장되었습니다.'); setData(prev => prev.map(item => selectedItems.has(item.id) ? { ...item, withdrawalType: selectedAccountSubject as WithdrawalRecord['withdrawalType'] } : item )); setSelectedItems(new Set()); } else { toast.error(result.error || '계정과목명 저장에 실패했습니다.'); } setShowSaveDialog(false); }, [selectedAccountSubject, selectedItems]); // ===== 거래처 목록 (필터용) ===== const vendorOptions = useMemo(() => { const uniqueVendors = [...new Set(data.map(d => d.vendorName).filter(v => v))]; return [ { value: 'all', label: '전체' }, ...uniqueVendors.map(v => ({ value: v, label: v })) ]; }, [data]); // ===== 테이블 헤더 액션 (필터들) ===== const tableHeaderActions = (
{/* 거래처 필터 */} {/* 출금유형 필터 */} {/* 정렬 */}
); // ===== 상단 계정과목명 + 저장 버튼 + 새로고침 ===== const accountSubjectSelector = (
계정과목명
); // ===== 테이블 합계 계산 ===== const tableTotals = useMemo(() => { const totalAmount = filteredData.reduce((sum, item) => sum + item.withdrawalAmount, 0); return { totalAmount }; }, [filteredData]); // ===== 테이블 하단 합계 행 ===== const tableFooter = ( 합계 {tableTotals.totalAmount.toLocaleString()} ); return ( <> item.id} renderTableRow={renderTableRow} renderMobileCard={renderMobileCard} pagination={{ currentPage, totalPages, totalItems: filteredData.length, itemsPerPage, onPageChange: setCurrentPage, }} /> {/* 계정과목명 저장 확인 다이얼로그 */} 계정과목명 변경 {selectedItems.size}개의 출금 유형을{' '} {ACCOUNT_SUBJECT_OPTIONS.find(o => o.value === selectedAccountSubject)?.label} (으)로 모두 변경하시겠습니까? {/* 삭제 확인 다이얼로그 */} 출금 삭제 이 출금 내역을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다. 취소 삭제 {/* 선택 필요 알림 다이얼로그 */} 항목 선택 필요 변경할 출금 항목을 먼저 선택해주세요. setShowSelectWarningDialog(false)}> 확인 ); }