'use client'; import { useState, useMemo, useCallback } from 'react'; import { format, subDays, subMonths, startOfMonth, endOfMonth } from 'date-fns'; import { CreditCard, RefreshCw, Save, } 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'; // ===== Mock 데이터 생성 (결정론적: seed 기반으로 일관된 값 생성) ===== const generateMockData = (): CardTransaction[] => { const cards = ['신한 1234', '국민 5678', '우리 9012', '하나 3456']; const cardNames = ['법인카드1', '법인카드2', '대표이사카드', '영업팀카드']; const users = ['홍길동', '김철수', '이영희', '박민수', '최지영']; const merchants = ['GS25 강남점', '스타벅스 역삼', '교보문고', '쿠팡', '네이버페이']; // 고정된 금액 배열 (Math.random() 대신 결정론적 값 사용) const amounts = [150000, 230000, 45000, 320000, 78000, 190000, 420000, 55000, 280000, 125000, 340000, 67000, 215000, 89000, 175000, 395000, 52000, 268000, 112000, 445000, 73000, 198000, 310000, 48000, 256000, 135000, 380000, 62000, 225000, 95000]; // 고정된 날짜들 (new Date() 대신 고정 기준일 사용) const baseDate = new Date('2025-12-01'); return Array.from({ length: 30 }, (_, i) => { return { id: `card-txn-${i + 1}`, card: cards[i % cards.length], cardName: cardNames[i % cardNames.length], user: users[i % users.length], usedAt: format(subDays(baseDate, i % 30), 'yyyy-MM-dd HH:mm'), merchantName: merchants[i % merchants.length], amount: amounts[i], createdAt: baseDate.toISOString(), updatedAt: baseDate.toISOString(), }; }); }; export function CardTransactionInquiry() { // ===== 상태 관리 ===== const [searchQuery, setSearchQuery] = useState(''); const [sortOption, setSortOption] = useState('latest'); const [cardFilter, setCardFilter] = 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 [showSelectWarningDialog, setShowSelectWarningDialog] = useState(false); // 날짜 범위 상태 const [startDate, setStartDate] = useState('2025-09-01'); const [endDate, setEndDate] = useState('2025-09-03'); // Mock 데이터 (SSR-safe: 결정론적 데이터) const [data] = useState(() => generateMockData()); // ===== 체크박스 핸들러 ===== 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(() => { console.log('새로고침: 카드 사용 내역 최신 데이터 조회'); // TODO: API 호출로 최신 데이터 조회 }, []); // ===== 전체 선택 핸들러 ===== 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 handleConfirmSaveAccountSubject = useCallback(() => { console.log('계정과목명 저장:', selectedAccountSubject, '선택된 항목:', Array.from(selectedItems)); // TODO: API 호출로 저장 setShowSaveDialog(false); setSelectedItems(new Set()); }, [selectedAccountSubject, selectedItems]); // ===== 통계 카드 (전월/당월 사용액) ===== // Mock 데이터 기준일(2025-12-01)에 맞춰 고정된 날짜 사용 (Hydration 오류 방지) const statCards: StatCard[] = useMemo(() => { // Mock 데이터가 2025-12-01 기준이므로, 해당 월 기준으로 계산 const referenceDate = new Date('2025-12-01'); const currentMonthStart = startOfMonth(referenceDate); const currentMonthEnd = endOfMonth(referenceDate); const lastMonthStart = startOfMonth(subMonths(referenceDate, 1)); const lastMonthEnd = endOfMonth(subMonths(referenceDate, 1)); // 전월 사용액 계산 const lastMonthTotal = data .filter(d => { const date = new Date(d.usedAt); return date >= lastMonthStart && date <= lastMonthEnd; }) .reduce((sum, d) => sum + d.amount, 0); // 당월 사용액 계산 const currentMonthTotal = data .filter(d => { const date = new Date(d.usedAt); return date >= currentMonthStart && date <= currentMonthEnd; }) .reduce((sum, d) => sum + d.amount, 0); return [ { label: '전월 사용액', value: `${lastMonthTotal.toLocaleString()}원`, icon: CreditCard, iconColor: 'text-gray-500' }, { label: '당월 사용액', value: `${currentMonthTotal.toLocaleString()}원`, icon: CreditCard, iconColor: 'text-blue-500' }, ]; }, [data]); // ===== 테이블 컬럼 (체크박스/번호 없음) ===== 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)}> 확인 ); }