'use client'; /** * 공정 상세 페이지 (리디자인) * * 기획서 스크린샷 1 기준: * - 기본 정보: 공정번호, 공정형, 담당부서, 담당자, 생산일자, 상태 * - 품목 설정 정보: 품목 선택 버튼 + 개수 표시 * - 단계 테이블: 드래그&드롭 순서변경 + 단계 등록 버튼 */ import { useState, useEffect, useCallback, useRef } from 'react'; import { useRouter } from 'next/navigation'; import { ArrowLeft, Edit, GripVertical, Plus, Package, Trash2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { PageLayout } from '@/components/organisms/PageLayout'; import { PageHeader } from '@/components/organisms/PageHeader'; import { useMenuStore } from '@/store/menuStore'; import { usePermission } from '@/hooks/usePermission'; import { toast } from 'sonner'; import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog'; import { getProcessSteps, reorderProcessSteps, deleteProcess } from './actions'; import type { Process, ProcessStep } from '@/types/process'; interface ProcessDetailProps { process: Process; } export function ProcessDetail({ process }: ProcessDetailProps) { const router = useRouter(); const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed); const { canUpdate } = usePermission(); // 단계 목록 상태 const [steps, setSteps] = useState([]); const [isStepsLoading, setIsStepsLoading] = useState(true); // 삭제 다이얼로그 상태 const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); // 드래그 상태 const [dragIndex, setDragIndex] = useState(null); const [dragOverIndex, setDragOverIndex] = useState(null); const dragNodeRef = useRef(null); // 품목 개수 계산 (기존 classificationRules에서 individual 품목) const itemCount = process.classificationRules .filter((r) => r.registrationType === 'individual') .reduce((sum, r) => sum + (r.items?.length || 0), 0); // 단계 목록 로드 useEffect(() => { const loadSteps = async () => { setIsStepsLoading(true); const result = await getProcessSteps(process.id); if (result.success && result.data) { setSteps(result.data); } setIsStepsLoading(false); }; loadSteps(); }, [process.id]); // 네비게이션 const handleEdit = () => { router.push(`/ko/master-data/process-management/${process.id}?mode=edit`); }; const handleList = () => { router.push('/ko/master-data/process-management'); }; const handleAddStep = () => { router.push(`/ko/master-data/process-management/${process.id}/steps/new`); }; const handleStepClick = (stepId: string) => { router.push(`/ko/master-data/process-management/${process.id}/steps/${stepId}`); }; const handleDelete = async () => { setIsDeleting(true); try { const result = await deleteProcess(process.id); if (result.success) { toast.success('공정이 삭제되었습니다.'); router.push('/ko/master-data/process-management'); } else { toast.error(result.error || '삭제에 실패했습니다.'); } } catch { toast.error('삭제 중 오류가 발생했습니다.'); } finally { setIsDeleting(false); setDeleteDialogOpen(false); } }; // ===== 드래그&드롭 (HTML5 네이티브) ===== const handleDragStart = useCallback((e: React.DragEvent, index: number) => { setDragIndex(index); dragNodeRef.current = e.currentTarget; e.dataTransfer.effectAllowed = 'move'; // 약간의 딜레이로 드래그 시작 시 스타일 적용 requestAnimationFrame(() => { if (dragNodeRef.current) { dragNodeRef.current.style.opacity = '0.4'; } }); }, []); const handleDragOver = useCallback((e: React.DragEvent, index: number) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; setDragOverIndex(index); }, []); const handleDragEnd = useCallback(() => { if (dragNodeRef.current) { dragNodeRef.current.style.opacity = '1'; } setDragIndex(null); setDragOverIndex(null); dragNodeRef.current = null; }, []); const handleDrop = useCallback((e: React.DragEvent, dropIndex: number) => { e.preventDefault(); if (dragIndex === null || dragIndex === dropIndex) return; setSteps((prev) => { const updated = [...prev]; const [moved] = updated.splice(dragIndex, 1); updated.splice(dropIndex, 0, moved); const reordered = updated.map((step, i) => ({ ...step, order: i + 1 })); // API 순서 변경 호출 reorderProcessSteps( process.id, reordered.map((s) => ({ id: s.id, order: s.order })) ); return reordered; }); handleDragEnd(); }, [dragIndex, handleDragEnd, process.id]); return ( {/* 헤더 */}
{/* 기본 정보 */} 기본 정보 {/* Row 1: 공정번호 | 공정명 | 담당부서 | 담당자 */}
공정번호
{process.processCode}
공정명
{process.processName}
담당부서
{process.department || '-'}
담당자
{process.manager || '-'}
{/* Row 2: 구분 | 생산일자 | 상태 */}
구분
{process.processCategory || '없음'}
생산일자
{process.useProductionDate ? '사용' : '미사용'}
상태
{process.status}
{/* 품목 설정 정보 */}
품목 설정 정보

품목을 선택하면 이 공정으로 분류됩니다

{itemCount}개
{/* 단계 테이블 */}
단계 {!isStepsLoading && ( 총 {steps.length}건 )}
{isStepsLoading ? (
로딩 중...
) : steps.length === 0 ? (
등록된 단계가 없습니다. [단계 등록] 버튼으로 추가해주세요.
) : (
{steps.map((step, index) => ( handleDragStart(e, index)} onDragOver={(e) => handleDragOver(e, index)} onDragEnd={handleDragEnd} onDrop={(e) => handleDrop(e, index)} onClick={() => handleStepClick(step.id)} className={`border-b cursor-pointer transition-colors hover:bg-muted/50 ${ dragOverIndex === index && dragIndex !== index ? 'border-t-2 border-t-primary' : '' }`} > ))}
{/* 드래그 핸들 헤더 */} No. 단계코드 단계명 필수여부 승인여부 검사여부 사용
e.stopPropagation()} > {index + 1} {step.stepCode} {step.stepName} {step.isRequired ? 'Y' : 'N'} {step.needsApproval ? 'Y' : 'N'} {step.needsInspection ? 'Y' : 'N'} {step.isActive ? 'Y' : 'N'}
)}
{/* 하단 액션 버튼 (sticky) */}
{canUpdate && (
)}
{/* 삭제 확인 다이얼로그 */}
); }