'use client'; /** * 공과관리 - UniversalListPage 마이그레이션 * * 기존 IntegratedListTemplateV2 → UniversalListPage config 기반으로 변환 * - 클라이언트 사이드 필터링 (검색, 필터, 정렬) * - Stats 카드 클릭 필터링 (activeStatTab) * - DateRangeSelector (headerActions → dateRangeSelector config) * - filterConfig (multi: 거래처, 현장명, 공사PM, 작업반장 / single: 공과, 상태, 정렬) * - 삭제 기능 (deleteConfirmMessage로 AlertDialog 대체) */ import { useState, useMemo, useEffect } from 'react'; import { Zap, Trash2, FileText, CheckCircle, Clock } 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/organisms/MobileCard'; import { UniversalListPage, type UniversalListConfig, type SelectionHandlers, type RowClickHandlers, type StatCard, } from '@/components/templates/UniversalListPage'; import type { Utility, UtilityStats } from './types'; import { UTILITY_STATUS_OPTIONS, UTILITY_SORT_OPTIONS, UTILITY_STATUS_STYLES, UTILITY_STATUS_LABELS, MOCK_PARTNERS, MOCK_SITES, MOCK_CONSTRUCTION_PM, MOCK_UTILITY_TYPES, MOCK_WORK_TEAM_LEADERS, } from './types'; import { getUtilityList, getUtilityStats, deleteUtility, deleteUtilities, } from './actions'; // 테이블 컬럼 정의 const tableColumns = [ { key: 'no', label: '번호', className: 'w-[50px] text-center' }, { key: 'utilityNumber', label: '공과번호', className: 'w-[120px]' }, { key: 'partnerName', label: '거래처', className: 'w-[100px]' }, { key: 'siteName', label: '현장명', className: 'min-w-[120px]' }, { key: 'constructionPM', label: '공사PM', className: 'w-[80px]' }, { key: 'utilityType', label: '공과', className: 'w-[80px]' }, { key: 'scheduledDate', label: '공과예정일시', className: 'w-[110px]' }, { key: 'amount', label: '금액', className: 'w-[100px] text-right' }, { key: 'workTeamLeader', label: '작업반장', className: 'w-[80px]' }, { key: 'constructionStartDate', label: '시공투입일', className: 'w-[100px]' }, { key: 'status', label: '상태', className: 'w-[80px] text-center' }, { key: 'actions', label: '작업', className: 'w-[100px] text-center' }, ]; // 날짜 포맷 function formatDate(dateStr: string | null): string { if (!dateStr) return '-'; return dateStr.split('T')[0]; } // 금액 포맷 function formatAmount(amount: number): string { return amount.toLocaleString('ko-KR') + '원'; } interface UtilityManagementListClientProps { initialData?: Utility[]; initialStats?: UtilityStats; } export default function UtilityManagementListClient({ initialData = [], initialStats, }: UtilityManagementListClientProps) { // ===== 외부 상태 (UniversalListPage 외부에서 관리) ===== const [activeStatTab, setActiveStatTab] = useState<'all' | 'waiting' | 'complete'>('all'); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [stats, setStats] = useState(initialStats || null); const [searchQuery, setSearchQuery] = useState(''); // Stats 로드 useEffect(() => { if (!initialStats) { getUtilityStats().then((result) => { if (result.success && result.data) { setStats(result.data); } }); } }, [initialStats]); // ===== UniversalListPage Config ===== const config: UniversalListConfig = useMemo( () => ({ // 페이지 기본 정보 title: '공과관리', description: '공과 목록을 관리합니다', icon: Zap, basePath: '/construction/project/utility-management', // ID 추출 idField: 'id', // API 액션 actions: { getList: async () => { const result = await getUtilityList({ 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 }; }, deleteItem: async (id: string) => { const result = await deleteUtility(id); return { success: result.success, error: result.error }; }, deleteBulk: async (ids: string[]) => { const result = await deleteUtilities(ids); return { success: result.success, error: result.error }; }, }, // 테이블 컬럼 columns: tableColumns, // 클라이언트 사이드 필터링 clientSideFiltering: true, itemsPerPage: 20, // 검색 필터 searchPlaceholder: '공과번호, 거래처, 현장명, 공사PM 검색', searchFilter: (item, searchValue) => { const search = searchValue.toLowerCase(); return ( item.utilityNumber.toLowerCase().includes(search) || item.partnerName.toLowerCase().includes(search) || item.siteName.toLowerCase().includes(search) || item.constructionPM.toLowerCase().includes(search) ); }, // 필터 설정 filterConfig: [ { key: 'partners', label: '거래처', type: 'multi', options: MOCK_PARTNERS, }, { key: 'sites', label: '현장명', type: 'multi', options: MOCK_SITES, }, { key: 'constructionPMs', label: '공사PM', type: 'multi', options: MOCK_CONSTRUCTION_PM, }, { key: 'utilityType', label: '공과', type: 'single', options: MOCK_UTILITY_TYPES, }, { key: 'workTeamLeaders', label: '작업반장', type: 'multi', options: MOCK_WORK_TEAM_LEADERS, }, { key: 'status', label: '상태', type: 'single', options: UTILITY_STATUS_OPTIONS.filter((o) => o.value !== 'all'), }, { key: 'sortBy', label: '정렬', type: 'single', options: UTILITY_SORT_OPTIONS, }, ], initialFilters: { partners: [], sites: [], constructionPMs: [], utilityType: 'all', workTeamLeaders: [], status: 'all', sortBy: 'latest', }, filterTitle: '공과 필터', // 커스텀 필터 함수 customFilterFn: (items, filterValues) => { if (!items || items.length === 0) return items; return items.filter((item) => { // Stats 탭 필터 if (activeStatTab === 'waiting' && item.status !== 'scheduled' && item.status !== 'issued') return false; if (activeStatTab === 'complete' && item.status !== 'completed') 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; // 공사PM 필터 (다중선택) const constructionPMFilters = filterValues.constructionPMs as string[]; if (constructionPMFilters?.length > 0 && !constructionPMFilters.includes(item.constructionPMId)) return false; // 공과 유형 필터 (단일선택) const utilityTypeFilter = filterValues.utilityType as string; if (utilityTypeFilter && utilityTypeFilter !== 'all') { const matchingType = MOCK_UTILITY_TYPES.find((t) => t.value === utilityTypeFilter); if (!matchingType || item.utilityType !== matchingType.label) { return false; } } // 작업반장 필터 (다중선택) const workTeamFilters = filterValues.workTeamLeaders as string[]; if (workTeamFilters?.length > 0 && !workTeamFilters.includes(item.workTeamLeaderId)) 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.scheduledDate).getTime() - new Date(a.scheduledDate).getTime()); break; case 'oldest': sorted.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()); break; case 'partnerNameDesc': sorted.sort((a, b) => b.partnerName.localeCompare(a.partnerName, 'ko')); break; case 'issuedDate': sorted.sort((a, b) => new Date(b.scheduledDate).getTime() - new Date(a.scheduledDate).getTime()); break; case 'completedDate': sorted.sort((a, b) => { if (a.status === 'completed' && b.status !== 'completed') return -1; if (a.status !== 'completed' && b.status === 'completed') return 1; return 0; }); break; } return sorted; }, // 검색창 (공통 컴포넌트에서 자동 생성) hideSearch: true, searchValue: searchQuery, onSearchChange: setSearchQuery, // 공통 헤더 옵션 dateRangeSelector: { enabled: true, startDate, endDate, onStartDateChange: setStartDate, onEndDateChange: setEndDate, }, // Stats 카드 computeStats: (): StatCard[] => [ { label: '전체 계약', value: stats?.totalContract ?? 0, icon: FileText, iconColor: 'text-blue-600', onClick: () => setActiveStatTab('all'), isActive: activeStatTab === 'all', }, { label: '계약 대기', value: stats?.contractWaiting ?? 0, icon: Clock, iconColor: 'text-yellow-600', onClick: () => setActiveStatTab('waiting'), isActive: activeStatTab === 'waiting', }, { label: '계약 완료', value: stats?.contractComplete ?? 0, icon: CheckCircle, iconColor: 'text-green-600', onClick: () => setActiveStatTab('complete'), isActive: activeStatTab === 'complete', }, ], // 삭제 확인 메시지 (AlertDialog 대체) deleteConfirmMessage: { title: '공과 삭제', description: '선택한 공과를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.', }, // 테이블 행 렌더링 renderTableRow: ( item: Utility, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => ( {globalIndex} {item.utilityNumber} {item.partnerName} {item.siteName} {item.constructionPM} {item.utilityType} {formatDate(item.scheduledDate)} {formatAmount(item.amount)} {item.workTeamLeader} {formatDate(item.constructionStartDate)} {UTILITY_STATUS_LABELS[item.status]} {handlers.isSelected && ( )} ), // 모바일 카드 렌더링 renderMobileCard: ( item: Utility, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => ( ), }), [startDate, endDate, activeStatTab, stats, searchQuery] ); return ; }