'use client'; /** * 공정 목록 - UniversalListPage 마이그레이션 * * 기존 IntegratedListTemplateV2 → UniversalListPage config 기반으로 변환 * - 클라이언트 사이드 필터링 (탭별, 검색) * - 상태 토글 기능 * - 삭제 다이얼로그 */ import { useState, useMemo, useCallback, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Wrench, Plus, Pencil, Trash2, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { TableCell, TableRow } from '@/components/ui/table'; import { Checkbox } from '@/components/ui/checkbox'; import { Badge } from '@/components/ui/badge'; import { UniversalListPage, type UniversalListConfig, type SelectionHandlers, type RowClickHandlers, type TabOption, type ListParams, } from '@/components/templates/UniversalListPage'; import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard'; import { toast } from 'sonner'; import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog'; import type { Process } from '@/types/process'; import { getProcessList, deleteProcess, deleteProcesses, toggleProcessActive, getProcessStats } from './actions'; interface ProcessListClientProps { initialData?: Process[]; initialStats?: { total: number; active: number; inactive: number }; } export default function ProcessListClient({ initialData = [], initialStats }: ProcessListClientProps) { const router = useRouter(); // ===== 상태 ===== const [allProcesses, setAllProcesses] = useState(initialData); const [stats, setStats] = useState(initialStats ?? { total: 0, active: 0, inactive: 0 }); const [isLoading, setIsLoading] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteTargetId, setDeleteTargetId] = useState(null); // ===== 데이터 로드 ===== const loadData = useCallback(async () => { setIsLoading(true); try { const [listResult, statsResult] = await Promise.all([ getProcessList({ size: 1000 }), getProcessStats(), ]); if (listResult.success && listResult.data) { setAllProcesses(listResult.data.items); } if (statsResult.success && statsResult.data) { setStats({ total: statsResult.data.total, active: statsResult.data.active, inactive: statsResult.data.inactive, }); } } catch { toast.error('데이터 로드에 실패했습니다.'); } finally { setIsLoading(false); } }, []); // 초기 데이터가 없으면 로드 useEffect(() => { if (initialData.length === 0) { loadData(); } }, [initialData.length, loadData]); // ===== 핸들러 ===== const handleRowClick = useCallback((process: Process) => { router.push(`/ko/master-data/process-management/${process.id}`); }, [router]); const handleCreate = useCallback(() => { router.push('/ko/master-data/process-management/new'); }, [router]); const handleEdit = useCallback((process: Process) => { router.push(`/ko/master-data/process-management/${process.id}/edit`); }, [router]); const handleDeleteClick = useCallback((processId: string) => { setDeleteTargetId(processId); setDeleteDialogOpen(true); }, []); const handleDeleteConfirm = useCallback(async () => { if (!deleteTargetId) return; setIsLoading(true); try { const result = await deleteProcess(deleteTargetId); if (result.success) { toast.success('공정이 삭제되었습니다.'); const deletedProcess = allProcesses.find((p) => p.id === deleteTargetId); setAllProcesses((prev) => prev.filter((p) => p.id !== deleteTargetId)); setStats((prev) => ({ ...prev, total: prev.total - 1, active: prev.active - (deletedProcess?.status === '사용중' ? 1 : 0), inactive: prev.inactive - (deletedProcess?.status === '미사용' ? 1 : 0), })); } else { toast.error(result.error || '삭제에 실패했습니다.'); } } catch { toast.error('삭제 중 오류가 발생했습니다.'); } finally { setIsLoading(false); setDeleteDialogOpen(false); setDeleteTargetId(null); } }, [deleteTargetId, allProcesses]); const handleBulkDelete = useCallback(async (selectedIds: string[]) => { if (selectedIds.length === 0) { toast.warning('삭제할 항목을 선택해주세요.'); return; } setIsLoading(true); try { const result = await deleteProcesses(selectedIds); if (result.success) { toast.success(`${result.deletedCount}개 항목이 삭제되었습니다.`); await loadData(); } else { toast.error(result.error || '일괄 삭제에 실패했습니다.'); } } catch { toast.error('일괄 삭제 중 오류가 발생했습니다.'); } finally { setIsLoading(false); } }, [loadData]); const handleToggleStatus = useCallback(async (processId: string) => { setIsLoading(true); try { const result = await toggleProcessActive(processId); if (result.success && result.data) { toast.success('상태가 변경되었습니다.'); setAllProcesses((prev) => prev.map((p) => (p.id === processId ? result.data! : p))); const oldProcess = allProcesses.find((p) => p.id === processId); if (oldProcess) { const wasActive = oldProcess.status === '사용중'; setStats((prev) => ({ ...prev, active: wasActive ? prev.active - 1 : prev.active + 1, inactive: wasActive ? prev.inactive + 1 : prev.inactive - 1, })); } } else { toast.error(result.error || '상태 변경에 실패했습니다.'); } } catch { toast.error('상태 변경 중 오류가 발생했습니다.'); } finally { setIsLoading(false); } }, [allProcesses]); // ===== 탭 옵션 ===== const tabs: TabOption[] = useMemo(() => [ { value: 'all', label: '전체', count: stats.total }, { value: '사용중', label: '사용중', count: stats.active }, { value: '미사용', label: '미사용', count: stats.inactive }, ], [stats]); // ===== UniversalListPage Config ===== const config: UniversalListConfig = useMemo( () => ({ // 페이지 기본 정보 title: '공정 목록', icon: Wrench, basePath: '/master-data/process-management', // ID 추출 idField: 'id', // API 액션 actions: { getList: async (params?: ListParams) => { try { const [listResult, statsResult] = await Promise.all([ getProcessList({ size: 1000 }), getProcessStats(), ]); if (listResult.success && listResult.data) { setAllProcesses(listResult.data.items); if (statsResult.success && statsResult.data) { setStats({ total: statsResult.data.total, active: statsResult.data.active, inactive: statsResult.data.inactive, }); } return { success: true, data: listResult.data.items, totalCount: listResult.data.items.length, totalPages: 1, }; } return { success: false, error: '데이터 로드에 실패했습니다.' }; } catch { return { success: false, error: '서버 오류가 발생했습니다.' }; } }, deleteItem: async (id: string) => { const result = await deleteProcess(id); return { success: result.success, error: result.error }; }, deleteBulk: async (ids: string[]) => { const result = await deleteProcesses(ids); return { success: result.success, error: result.error }; }, }, // 테이블 컬럼 columns: [ { key: 'no', label: '번호', className: 'w-[60px] text-center' }, { key: 'processCode', label: '공정코드', className: 'w-[100px]' }, { key: 'processName', label: '공정명', className: 'min-w-[250px]' }, { key: 'processType', label: '구분', className: 'w-[80px] text-center' }, { key: 'department', label: '담당부서', className: 'w-[120px]' }, { key: 'classificationRules', label: '분류규칙', className: 'w-[80px] text-center' }, { key: 'requiredWorkers', label: '인원', className: 'w-[60px] text-center' }, { key: 'status', label: '상태', className: 'w-[80px] text-center' }, { key: 'actions', label: '작업', className: 'w-[100px] text-center' }, ], // 클라이언트 사이드 필터링 clientSideFiltering: true, itemsPerPage: 20, // 탭 필터 함수 tabFilter: (item: Process, activeTab: string) => { if (activeTab === 'all') return true; return item.status === activeTab; }, // 검색 필터 함수 searchFilter: (item: Process, searchValue: string) => { const search = searchValue.toLowerCase(); return ( item.processCode.toLowerCase().includes(search) || item.processName.toLowerCase().includes(search) || item.department.toLowerCase().includes(search) ); }, // 탭 설정 tabs, defaultTab: 'all', // 검색 searchPlaceholder: '공정코드, 공정명, 담당부서 검색', // 헤더 액션 headerActions: () => ( ), // 일괄 삭제 핸들러 onBulkDelete: handleBulkDelete, // 테이블 행 렌더링 renderTableRow: ( process: Process, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => { return ( handleRowClick(process)} > e.stopPropagation()}> {globalIndex} {process.processCode}
{process.processName}
{process.description && (
{process.description}
)}
{process.processType} {process.department} {process.classificationRules.length > 0 ? ( {process.classificationRules.length} ) : ( - )} {process.requiredWorkers}명 e.stopPropagation()}> handleToggleStatus(process.id)} > {process.status} {handlers.isSelected && (
)}
); }, // 모바일 카드 렌더링 renderMobileCard: ( process: Process, index: number, globalIndex: number, handlers: SelectionHandlers & RowClickHandlers ) => { return ( handleRowClick(process)} headerBadges={ <> #{globalIndex} {process.processCode} } title={process.processName} statusBadge={ { e.stopPropagation(); handleToggleStatus(process.id); }} > {process.status} } infoGrid={
0 ? `${process.classificationRules.length}개` : '-'} /> {process.description && (
)}
} actions={ handlers.isSelected ? (
) : undefined } /> ); }, }), [tabs, handleCreate, handleRowClick, handleEdit, handleDeleteClick, handleToggleStatus, handleBulkDelete] ); return ( <> {/* 삭제 확인 다이얼로그 */} ); }