'use client'; /** * 공정상세 섹션 컴포넌트 * API 연동 완료 (2025-12-26) * * 기획 화면에 맞춘 레이아웃: * - 자재 투입 필요 섹션 (흰색 박스, 검은색 전체너비 버튼) * - 공정 단계 (N단계) + N/N 완료 * - 숫자 뱃지 + 공정명 + 검사 뱃지 + 진행률 * - 검사 항목: 검사 요청 버튼 * - 상세 정보: 위치, 규격, LOT, 자재 */ import { useState, useEffect, useCallback } from 'react'; import { ChevronDown, Loader2 } from 'lucide-react'; import { ContentLoadingSpinner } from '@/components/ui/loading-spinner'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { AlertDialog, AlertDialogAction, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { cn } from '@/lib/utils'; import { toast } from 'sonner'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; import { getProcessSteps, requestInspection, type ProcessStep, type ProcessStepItem } from './actions'; interface ProcessDetailSectionProps { workOrderId: string; isExpanded: boolean; materialRequired: boolean; onMaterialInput: () => void; } export function ProcessDetailSection({ workOrderId, isExpanded, materialRequired, onMaterialInput, }: ProcessDetailSectionProps) { const [steps, setSteps] = useState([]); const [expandedSteps, setExpandedSteps] = useState>(new Set()); const [inspectionDialogOpen, setInspectionDialogOpen] = useState(false); const [inspectionStepName, setInspectionStepName] = useState(''); const [pendingInspectionStepId, setPendingInspectionStepId] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); // API로 공정 단계 로드 const loadProcessSteps = useCallback(async () => { if (!workOrderId) return; setIsLoading(true); try { const result = await getProcessSteps(workOrderId); if (result.success) { setSteps(result.data); // 첫 번째 단계 자동 펼치기 if (result.data.length > 0) { setExpandedSteps(new Set([result.data[0].id])); } } else { toast.error(result.error || '공정 단계 조회에 실패했습니다.'); } } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ProcessDetailSection] loadProcessSteps error:', error); toast.error('공정 단계 로드 중 오류가 발생했습니다.'); } finally { setIsLoading(false); } }, [workOrderId]); // 섹션이 펼쳐질 때 데이터 로드 useEffect(() => { if (isExpanded && workOrderId) { loadProcessSteps(); } }, [isExpanded, workOrderId, loadProcessSteps]); const totalSteps = steps.length; const completedSteps = steps.filter((s) => s.completed === s.total).length; const toggleStep = (stepId: string) => { setExpandedSteps((prev) => { const next = new Set(prev); if (next.has(stepId)) { next.delete(stepId); } else { next.add(stepId); } return next; }); }; // 검사 요청 핸들러 const handleInspectionRequest = (step: ProcessStep) => { setInspectionStepName(step.name); setPendingInspectionStepId(step.id); setInspectionDialogOpen(true); }; // 검사 요청 확인 후 API 호출 const handleInspectionConfirm = async () => { if (!pendingInspectionStepId) { setInspectionDialogOpen(false); return; } setIsSubmitting(true); try { const result = await requestInspection(workOrderId, pendingInspectionStepId); if (result.success) { // 로컬 상태 업데이트 setSteps((prev) => prev.map((step) => step.id === pendingInspectionStepId ? { ...step, completed: step.total } : step ) ); // 다음 단계 펼치기 const stepIndex = steps.findIndex((s) => s.id === pendingInspectionStepId); if (stepIndex < steps.length - 1) { setExpandedSteps((prev) => new Set([...prev, steps[stepIndex + 1].id])); } toast.success('검사 요청이 전송되었습니다.'); } else { toast.error(result.error || '검사 요청에 실패했습니다.'); } } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ProcessDetailSection] handleInspectionConfirm error:', error); toast.error('검사 요청 중 오류가 발생했습니다.'); } finally { setIsSubmitting(false); setInspectionDialogOpen(false); setPendingInspectionStepId(null); } }; if (!isExpanded) return null; return (
{/* 자재 투입 필요 섹션 */} {materialRequired && (

자재 투입 필요

)} {/* 공정 단계 헤더 */}

공정 단계 ({totalSteps}단계)

{completedSteps} / {totalSteps} 완료
{/* 공정 단계 목록 */}
{isLoading ? ( ) : steps.length === 0 ? (
등록된 공정 단계가 없습니다.
) : ( steps.map((step) => ( toggleStep(step.id)} onInspectionRequest={() => handleInspectionRequest(step)} /> )) )}
{/* 검사 요청 확인 다이얼로그 */} 검사 요청 {inspectionStepName} 검사를 품질팀에 요청하시겠습니까?
); } // ===== 하위 컴포넌트 ===== interface ProcessStepCardProps { step: ProcessStep; isExpanded: boolean; onToggle: () => void; onInspectionRequest: () => void; } function ProcessStepCard({ step, isExpanded, onToggle, onInspectionRequest }: ProcessStepCardProps) { const isCompleted = step.completed === step.total; const hasItems = step.items.length > 0; return (
{/* 헤더 */}
{/* 숫자 뱃지 */}
{step.stepNo}
{/* 공정명 + 검사 뱃지 */}
{step.name} {step.isInspection && ( 검사 )}
{/* 진행률 + 완료 표시 */}
{isCompleted && ( 완료 )} {step.completed}/{step.total} {(hasItems || step.isInspection) && ( )}
{/* 검사 요청 버튼 (검사 항목일 때만) */} {step.isInspection && !isCompleted && isExpanded && (
)} {/* 상세 항목 리스트 */} {hasItems && isExpanded && (
{step.items.map((item, index) => ( ))}
)}
); } interface ProcessStepItemCardProps { item: ProcessStepItem; index: number; isCompleted: boolean; } function ProcessStepItemCard({ item, index, isCompleted }: ProcessStepItemCardProps) { return (
{/* 인덱스 뱃지 */}
{index}
{/* 상세 정보 */}
{/* 첫 번째 줄: #N + 위치 + 선행생산 + 완료 */}
{item.itemNo} {item.location} {item.isPriority && ( 선행 생산 )} {isCompleted && ( 완료 )}
{/* 두 번째 줄: 규격 + 자재 */}
규격: {item.spec} 자재: {item.material}
{/* 세 번째 줄: LOT */}
LOT: {item.lot}
); }