"use client"; import React, { useState, useEffect } from "react"; import { apiClient } from "@/lib/api/client"; /* ------------------------------------------------------------------ */ /* Types */ /* ------------------------------------------------------------------ */ export interface ProcessStep { no: number; name: string; code: string; status: "completed" | "in_progress" | "waiting" | "acceptable"; inputQty: number; goodQty: number; defectQty: number; planQty: number; availableQty: number; } interface ReworkChainItem { id: string; seq_no: string; process_code: string; process_name: string; status: string; input_qty: string; good_qty: string; defect_qty: string; concession_qty: string; is_rework: string; rework_source_id: string | null; started_at: string | null; completed_at: string | null; } interface ReworkChain { source: ReworkChainItem; reworks: ReworkChainItem[]; totalReworkCount: number; } interface ProcessDetailModalProps { open: boolean; onClose: () => void; workInstructionNo: string; totalQty: number; steps: ProcessStep[]; woId?: string; showReworkHistory?: boolean; } /* ------------------------------------------------------------------ */ /* Component */ /* ------------------------------------------------------------------ */ export function ProcessDetailModal({ open, onClose, workInstructionNo, totalQty, steps, woId, showReworkHistory, }: ProcessDetailModalProps) { const [activeTab, setActiveTab] = useState<"steps" | "rework">("steps"); const [reworkChains, setReworkChains] = useState([]); const [reworkLoading, setReworkLoading] = useState(false); const [totalReworkCount, setTotalReworkCount] = useState(0); // Fetch rework history when tab switches to rework useEffect(() => { if (!open || !woId || activeTab !== "rework") return; let cancelled = false; const fetchHistory = async () => { setReworkLoading(true); try { const res = await apiClient.get(`/pop/production/rework-history/${woId}`); if (!cancelled && res.data?.success) { setReworkChains(res.data.data.chains || []); setTotalReworkCount(res.data.data.total_rework_count || 0); } } catch { // Non-fatal } finally { if (!cancelled) setReworkLoading(false); } }; fetchHistory(); return () => { cancelled = true; }; }, [open, woId, activeTab]); // Reset tab on close useEffect(() => { if (!open) setActiveTab("steps"); }, [open]); if (!open) return null; const completedCount = steps.filter((s) => s.status === "completed").length; const overallPct = steps.length > 0 ? Math.round((completedCount / steps.length) * 100) : 0; const hasReworkData = showReworkHistory && woId; return (
{/* Header */}

공정 순서 상세

{workInstructionNo}

{/* Tab switcher — only show if rework data available */} {hasReworkData && (
)} {/* Scrollable body */}
{/* Summary bar */} {activeTab === "steps" && (
작업지시 총량 {totalQty.toLocaleString()} EA
{completedCount}/{steps.length} 공정
)} {/* Steps — shown when activeTab is "steps" */} {activeTab === "steps" && (
{steps.map((s) => { const borderColor = s.status === "completed" ? "border-green-400 bg-green-50" : s.status === "in_progress" ? "border-blue-400 bg-blue-50" : s.status === "acceptable" ? "border-amber-400 bg-amber-50" : "border-gray-200 bg-gray-50"; const dotColor = s.status === "completed" ? "bg-green-500" : s.status === "in_progress" ? "bg-blue-500" : s.status === "acceptable" ? "bg-amber-500" : "bg-gray-400"; const badge = s.status === "completed" ? ( 완료 ) : s.status === "in_progress" ? ( 진행중 ) : s.status === "acceptable" ? ( 접수가능 ) : ( 대기 ); const barColor = s.status === "completed" ? "bg-green-500" : s.status === "in_progress" ? "bg-blue-500" : s.status === "acceptable" ? "bg-amber-500" : "bg-gray-300"; const barTextColor = s.status === "completed" ? "text-green-600" : s.status === "in_progress" ? "text-blue-600" : s.status === "acceptable" ? "text-amber-600" : "text-gray-400"; const pct = s.planQty > 0 ? Math.round((s.inputQty / s.planQty) * 100) : 0; const unaccept = s.planQty - s.inputQty; return (
{/* Header */}
{s.no}
{s.name} {badge}
{s.code}
{/* Progress bar */}
{pct}%
{/* Qty grid */}
지시
{s.planQty.toLocaleString()}
접수
{s.inputQty.toLocaleString()}
양품
{s.goodQty.toLocaleString()}
{s.status === "completed" || s.status === "in_progress" ? (
불량
{s.defectQty.toLocaleString()}
) : (
미접수
{Math.max(0, unaccept).toLocaleString()}
)}
{/* Additional accept qty */} {s.status === "in_progress" && s.availableQty > 0 && (
추가접수가능 {s.availableQty.toLocaleString()}
)} {s.status === "acceptable" && s.availableQty > 0 && (
접수가능 {s.availableQty.toLocaleString()}
)}
); })}
)} {/* Rework History — shown when activeTab is "rework" */} {activeTab === "rework" && (
{reworkLoading ? (
) : reworkChains.length === 0 ? (
🔍

재작업 이력이 없습니다

) : ( <> {/* Summary */}
총 재작업 {totalReworkCount}회

{reworkChains.length}개 불량 발생 지점

{/* Chain tree */} {reworkChains.map((chain, chainIdx) => { const srcDefect = parseInt(chain.source.defect_qty || "0", 10); const srcGood = parseInt(chain.source.good_qty || "0", 10); return (
{/* Source (origin) node */}
{chain.source.seq_no || "?"}
{chain.reworks.length > 0 && (
)}
{chain.source.process_name || chain.source.process_code} 불량 {srcDefect}
양품 {srcGood} 불량 {srcDefect}
{/* Rework chain nodes */} {chain.reworks.map((rw, rwIdx) => { const rwGood = parseInt(rw.good_qty || "0", 10); const rwDefect = parseInt(rw.defect_qty || "0", 10); const rwInput = parseInt(rw.input_qty || "0", 10); const isLast = rwIdx === chain.reworks.length - 1; const statusColor = rw.status === "completed" ? "bg-green-500" : rw.status === "in_progress" ? "bg-blue-500" : rw.status === "acceptable" ? "bg-amber-500" : "bg-gray-400"; const statusLabel = rw.status === "completed" ? "완료" : rw.status === "in_progress" ? "진행중" : rw.status === "acceptable" ? "접수가능" : "대기"; return (
{!isLast &&
}
R{rwIdx + 1}
{!isLast &&
}
{rw.process_name || rw.process_code} {statusLabel}
투입 {rwInput} 양품 {rwGood} {rwDefect > 0 && 불량 {rwDefect}}
); })}
); })} )}
)}
{/* end scrollable body */} {/* Footer */}
); }