'use client'; import { useState, useMemo, useCallback, useEffect } from 'react'; import { format, startOfMonth, endOfMonth } from 'date-fns'; import { CreditCard, RefreshCw, Save, Loader2, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { TableRow, TableCell } from '@/components/ui/table'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { AlertDialog, AlertDialogAction, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; 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 { CardTransaction, SortOption, } from './types'; import { SORT_OPTIONS, ACCOUNT_SUBJECT_OPTIONS, } from './types'; import { getCardTransactionList, getCardTransactionSummary, bulkUpdateAccountCode } from './actions'; // ===== Props ===== interface CardTransactionInquiryProps { initialData?: CardTransaction[]; initialSummary?: { previousMonthTotal: number; currentMonthTotal: number; }; initialPagination?: { currentPage: number; lastPage: number; perPage: number; total: number; }; } export function CardTransactionInquiry({ initialData = [], initialSummary, initialPagination, }: CardTransactionInquiryProps) { // ===== 상태 관리 ===== const [searchQuery, setSearchQuery] = useState(''); const [sortOption, setSortOption] = useState('latest'); const [cardFilter, setCardFilter] = 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 [selectedAccountSubject, setSelectedAccountSubject] = useState('unset'); // 계정과목명 저장 다이얼로그 const [showSaveDialog, setShowSaveDialog] = useState(false); // 선택 필요 알림 다이얼로그 const [showSelectWarningDialog, setShowSelectWarningDialog] = useState(false); // 날짜 범위 상태 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 || { previousMonthTotal: 0, currentMonthTotal: 0 } ); const [pagination, setPagination] = useState( initialPagination || { currentPage: 1, lastPage: 1, perPage: 20, total: 0 } ); // ===== 데이터 로드 ===== const loadData = useCallback(async () => { setIsLoading(true); try { // 정렬 옵션 매핑 const sortMapping: Record = { latest: { sortBy: 'used_at', sortDir: 'desc' }, oldest: { sortBy: 'used_at', sortDir: 'asc' }, amountHigh: { sortBy: 'amount', sortDir: 'desc' }, amountLow: { sortBy: 'amount', sortDir: 'asc' }, }; const sortParams = sortMapping[sortOption]; const [listResult, summaryResult] = await Promise.all([ getCardTransactionList({ page: currentPage, perPage: itemsPerPage, startDate, endDate, search: searchQuery || undefined, sortBy: sortParams.sortBy, sortDir: sortParams.sortDir, }), getCardTransactionSummary({ startDate, endDate }), ]); if (listResult.success) { setData(listResult.data); setPagination(listResult.pagination); } if (summaryResult.success && summaryResult.data) { setSummary({ previousMonthTotal: summaryResult.data.previousMonthTotal, currentMonthTotal: summaryResult.data.currentMonthTotal, }); } } catch (error) { console.error('[CardTransactionInquiry] loadData error:', error); } finally { setIsLoading(false); } }, [currentPage, startDate, endDate, 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 cardOptions = useMemo(() => { const uniqueCards = [...new Set(data.map(d => d.cardName))]; return [ { value: 'all', label: '전체' }, ...uniqueCards.map(card => ({ value: card, label: card })) ]; }, [data]); // ===== 필터링된 데이터 ===== const filteredData = useMemo(() => { let result = data.filter(item => item.card.includes(searchQuery) || item.cardName.includes(searchQuery) || item.user.includes(searchQuery) || item.merchantName.includes(searchQuery) ); // 카드명 필터 if (cardFilter !== 'all') { result = result.filter(item => item.cardName === cardFilter); } // 정렬 switch (sortOption) { case 'latest': result.sort((a, b) => new Date(b.usedAt).getTime() - new Date(a.usedAt).getTime()); break; case 'oldest': result.sort((a, b) => new Date(a.usedAt).getTime() - new Date(b.usedAt).getTime()); break; case 'amountHigh': result.sort((a, b) => b.amount - a.amount); break; case 'amountLow': result.sort((a, b) => a.amount - b.amount); break; } return result; }, [data, searchQuery, cardFilter, 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 handleRefresh = useCallback(() => { loadData(); }, [loadData]); // ===== 전체 선택 핸들러 ===== 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 handleSaveAccountSubject = useCallback(() => { if (selectedItems.size === 0) { setShowSelectWarningDialog(true); return; } setShowSaveDialog(true); }, [selectedItems.size]); // 계정과목명 저장 확정 const [isSaving, setIsSaving] = useState(false); const handleConfirmSaveAccountSubject = useCallback(async () => { if (selectedAccountSubject === 'unset') return; setIsSaving(true); try { const ids = Array.from(selectedItems).map(id => parseInt(id, 10)); const result = await bulkUpdateAccountCode(ids, selectedAccountSubject); if (result.success) { // 성공 시 데이터 새로고침 await loadData(); setSelectedItems(new Set()); setSelectedAccountSubject('unset'); } else { console.error('[CardTransactionInquiry] bulkUpdate error:', result.error); } } catch (error) { console.error('[CardTransactionInquiry] bulkUpdate error:', error); } finally { setIsSaving(false); setShowSaveDialog(false); } }, [selectedAccountSubject, selectedItems, loadData]); // ===== 통계 카드 (전월/당월 사용액) ===== const statCards: StatCard[] = useMemo(() => { return [ { label: '전월 사용액', value: `${summary.previousMonthTotal.toLocaleString()}원`, icon: CreditCard, iconColor: 'text-gray-500' }, { label: '당월 사용액', value: `${summary.currentMonthTotal.toLocaleString()}원`, icon: CreditCard, iconColor: 'text-blue-500' }, ]; }, [summary]); // ===== 테이블 컬럼 (체크박스/번호 없음) ===== const tableColumns: TableColumn[] = useMemo(() => [ { key: 'card', label: '카드' }, { key: 'cardName', label: '카드명' }, { key: 'user', label: '사용자' }, { key: 'usedAt', label: '사용일시' }, { key: 'merchantName', label: '가맹점명' }, { key: 'amount', label: '사용금액', className: 'text-right' }, ], []); // ===== 테이블 행 렌더링 ===== const renderTableRow = useCallback((item: CardTransaction) => { const isSelected = selectedItems.has(item.id); return ( {/* 체크박스 */} e.stopPropagation()}> toggleSelection(item.id)} /> {/* 카드 */} {item.card} {/* 카드명 */} {item.cardName} {/* 사용자 */} {item.user} {/* 사용일시 */} {item.usedAt} {/* 가맹점명 */} {item.merchantName} {/* 사용금액 */} {item.amount.toLocaleString()} ); }, [selectedItems, toggleSelection]); // ===== 모바일 카드 렌더링 ===== const renderMobileCard = useCallback(( item: CardTransaction, index: number, globalIndex: number, isSelected: boolean, onToggle: () => void ) => { return ( } /> ); }, []); // ===== 헤더 액션 (날짜 선택만) ===== const headerActions = ( ); // ===== 상단 계정과목명 + 저장 버튼 + 새로고침 ===== const beforeTableContent = (
계정과목명
); // ===== 테이블 헤더 액션 (2개 필터) ===== const tableHeaderActions = (
{/* 카드명 필터 */} {/* 정렬 */}
); // ===== 테이블 합계 계산 ===== const totalAmount = useMemo(() => { return filteredData.reduce((sum, item) => sum + item.amount, 0); }, [filteredData]); // ===== 테이블 하단 합계 행 ===== const tableFooter = ( 합계 {totalAmount.toLocaleString()} ); return ( <> item.id} renderTableRow={renderTableRow} renderMobileCard={renderMobileCard} showCheckbox={true} showRowNumber={false} pagination={{ currentPage, totalPages, totalItems: filteredData.length, itemsPerPage, onPageChange: setCurrentPage, }} /> {/* 계정과목명 저장 확인 다이얼로그 */} 계정과목명 변경 {selectedItems.size}개의 카드 사용 내역을{' '} {ACCOUNT_SUBJECT_OPTIONS.find(o => o.value === selectedAccountSubject)?.label} (으)로 모두 변경하시겠습니까? {/* 선택 필요 알림 다이얼로그 */} 항목 선택 필요 변경할 카드 사용 내역을 먼저 선택해주세요. setShowSelectWarningDialog(false)}> 확인 ); }