'use client'; /** * 기성청구관리 - UniversalListPage 마이그레이션 * * 기존 IntegratedListTemplateV2 → UniversalListPage config 기반으로 변환 * - 클라이언트 사이드 필터링 (검색, 필터, 정렬) * - Stats 카드 클릭 필터링 (activeStatTab) * - DateRangeSelector with showQuickButtons * - filterConfig (multi: 거래처, 현장명 / single: 상태, 정렬) * - 삭제 기능 없음 (조회/수정 전용) */ import { useState, useMemo, useCallback, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { FileText, Pencil } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { TableCell, TableRow } from '@/components/ui/table'; import { Checkbox } from '@/components/ui/checkbox'; import { MobileCard } from '@/components/molecules/MobileCard'; import { UniversalListPage, type UniversalListConfig, type SelectionHandlers, type RowClickHandlers, type StatCard, } from '@/components/templates/UniversalListPage'; import type { ProgressBilling, ProgressBillingStats } from './types'; import { PROGRESS_BILLING_STATUS_OPTIONS, PROGRESS_BILLING_SORT_OPTIONS, PROGRESS_BILLING_STATUS_STYLES, PROGRESS_BILLING_STATUS_LABELS, MOCK_PARTNERS, MOCK_SITES, } from './types'; import { getProgressBillingList, getProgressBillingStats, } from './actions'; // 테이블 컬럼 정의 const tableColumns = [ { key: 'no', label: '번호', className: 'w-[50px] text-center' }, { key: 'billingNumber', label: '기성청구번호', className: 'w-[140px]' }, { key: 'partnerName', label: '거래처', className: 'w-[120px]' }, { key: 'siteName', label: '현장명', className: 'min-w-[150px]' }, { key: 'round', label: '회차', className: 'w-[60px] text-center' }, { key: 'billingYearMonth', label: '기성청구연월', className: 'w-[110px] text-center' }, { key: 'previousBilling', label: '전회기성', className: 'w-[120px] text-right' }, { key: 'currentBilling', label: '금회기성', className: 'w-[120px] text-right' }, { key: 'cumulativeBilling', label: '누계기성', className: 'w-[120px] text-right' }, { key: 'status', label: '상태', className: 'w-[100px] text-center' }, { key: 'actions', label: '작업', className: 'w-[80px] text-center' }, ]; // 금액 포맷 function formatCurrency(amount: number): string { return new Intl.NumberFormat('ko-KR').format(amount); } interface ProgressBillingManagementListClientProps { initialData?: ProgressBilling[]; initialStats?: ProgressBillingStats; } export default function ProgressBillingManagementListClient({ initialData = [], initialStats, }: ProgressBillingManagementListClientProps) { const router = useRouter(); // ===== 외부 상태 (UniversalListPage 외부에서 관리) ===== const [activeStatTab, setActiveStatTab] = useState<'all' | 'contractWaiting' | 'contractComplete'>('all'); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [stats, setStats] = useState(initialStats || null); // Stats 로드 useEffect(() => { if (!initialStats) { getProgressBillingStats().then((result) => { if (result.success && result.data) { setStats(result.data); } }); } }, [initialStats]); // ===== 핸들러 ===== const handleRowClick = useCallback( (item: ProgressBilling) => { router.push(`/ko/construction/billing/progress-billing-management/${item.id}`); }, [router] ); const handleEdit = useCallback( (item: ProgressBilling) => { router.push(`/ko/construction/billing/progress-billing-management/${item.id}/edit`); }, [router] ); // ===== UniversalListPage Config ===== const config: UniversalListConfig = useMemo( () => ({ // 페이지 기본 정보 title: '기성청구관리', description: '기성청구를 등록하고 관리합니다.', icon: FileText, basePath: '/construction/billing/progress-billing-management', // ID 추출 idField: 'id', // API 액션 actions: { getList: async () => { const result = await getProgressBillingList({ size: 1000, startDate: startDate || undefined, endDate: endDate || undefined, }); if (result.success && result.data) { return { success: true, data: result.data.items, totalCount: result.data.total, }; } return { success: false, error: result.error }; }, }, // 테이블 컬럼 columns: tableColumns, // 클라이언트 사이드 필터링 clientSideFiltering: true, itemsPerPage: 20, // 검색 필터 searchPlaceholder: '기성청구번호, 거래처, 현장명 검색', searchFilter: (item, searchValue) => { const search = searchValue.toLowerCase(); return ( item.billingNumber.toLowerCase().includes(search) || item.partnerName.toLowerCase().includes(search) || item.siteName.toLowerCase().includes(search) ); }, // 필터 설정 filterConfig: [ { key: 'partners', label: '거래처', type: 'multi', options: MOCK_PARTNERS, }, { key: 'sites', label: '현장명', type: 'multi', options: MOCK_SITES, }, { key: 'status', label: '상태', type: 'single', options: PROGRESS_BILLING_STATUS_OPTIONS.filter((o) => o.value !== 'all'), }, { key: 'sortBy', label: '정렬', type: 'single', options: PROGRESS_BILLING_SORT_OPTIONS, }, ], initialFilters: { partners: [], sites: [], status: 'all', sortBy: 'latest', }, filterTitle: '기성청구 필터', // 커스텀 필터 함수 customFilterFn: (items, filterValues) => { return items.filter((item) => { // Stats 탭 필터 if (activeStatTab === 'contractWaiting' && item.status !== 'billing_waiting' && item.status !== 'approval_waiting') return false; if (activeStatTab === 'contractComplete' && item.status !== 'billing_complete') return false; // 거래처 필터 (다중선택) const partnerFilters = filterValues.partners as string[]; if (partnerFilters?.length > 0 && !partnerFilters.includes(item.partnerId)) return false; // 현장 필터 (다중선택) const siteFilters = filterValues.sites as string[]; if (siteFilters?.length > 0 && !siteFilters.includes(item.siteId)) return false; // 상태 필터 (단일선택) const statusFilter = filterValues.status as string; if (statusFilter && statusFilter !== 'all' && item.status !== statusFilter) return false; return true; }); }, // 커스텀 정렬 함수 customSortFn: (items, filterValues) => { const sorted = [...items]; const sortBy = (filterValues.sortBy as string) || 'latest'; switch (sortBy) { case 'latest': sorted.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); break; case 'oldest': sorted.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()); break; case 'partnerNameAsc': sorted.sort((a, b) => a.partnerName.localeCompare(b.partnerName, 'ko')); break; case 'partnerNameDesc': sorted.sort((a, b) => b.partnerName.localeCompare(a.partnerName, 'ko')); break; case 'siteNameAsc': sorted.sort((a, b) => a.siteName.localeCompare(b.siteName, 'ko')); break; case 'siteNameDesc': sorted.sort((a, b) => b.siteName.localeCompare(a.siteName, 'ko')); break; } return sorted; }, // 공통 헤더 옵션 dateRangeSelector: { enabled: true, startDate, endDate, onStartDateChange: setStartDate, onEndDateChange: setEndDate, showQuickButtons: true, }, // Stats 카드 computeStats: (): StatCard[] => [ { label: '전체 계약', value: stats?.total ?? 0, icon: FileText, iconColor: 'text-blue-600', onClick: () => setActiveStatTab('all'), isActive: activeStatTab === 'all', }, { label: '계약대기', value: stats?.contractWaiting ?? 0, icon: FileText, iconColor: 'text-yellow-600', onClick: () => setActiveStatTab('contractWaiting'), isActive: activeStatTab === 'contractWaiting', }, { label: '계약완료', value: stats?.contractComplete ?? 0, icon: FileText, iconColor: 'text-green-600', onClick: () => setActiveStatTab('contractComplete'), isActive: activeStatTab === 'contractComplete', }, ], // 테이블 행 렌더링 renderTableRow: ( item: ProgressBilling, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => ( handleRowClick(item)} > e.stopPropagation()}> {globalIndex} {item.billingNumber} {item.partnerName} {item.siteName} {item.round}차 {item.billingYearMonth} {formatCurrency(item.previousBilling)} {formatCurrency(item.currentBilling)} {formatCurrency(item.cumulativeBilling)} {PROGRESS_BILLING_STATUS_LABELS[item.status]} {handlers.isSelected && (
)}
), // 모바일 카드 렌더링 renderMobileCard: ( item: ProgressBilling, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => ( handleRowClick(item)} details={[ { label: '거래처', value: item.partnerName }, { label: '회차', value: `${item.round}차` }, { label: '금회기성', value: formatCurrency(item.currentBilling) }, ]} /> ), }), [startDate, endDate, activeStatTab, stats, handleRowClick, handleEdit] ); return ; }