"use client"; import React, { useCallback, useEffect, useState } from "react"; import { apiClient } from "@/lib/api/client"; /* ------------------------------------------------------------------ */ /* Types */ /* ------------------------------------------------------------------ */ export interface InspectionItem { id: string; inspection_item_name: string; inspection_standard: string; inspection_method: string; pass_criteria: string; is_required: string; /** User-entered measured value */ measured_value: string; /** "pass" | "fail" | null */ result: "pass" | "fail" | null; } export interface InspectionResult { items: InspectionItem[]; goodQty: number; badQty: number; remark: string; completed: boolean; inspectionNumber?: string; // 검사 완료 시 채번 받음 (재사용) } interface InspectionModalProps { open: boolean; onClose: () => void; onComplete: (result: InspectionResult) => void; onCancel?: () => void; // 취소 = 검사 무효화 (완료 → 대기) itemCode: string; itemName: string; totalQty: number; initialResult?: InspectionResult | null; } /* ------------------------------------------------------------------ */ /* Dummy inspection items (fallback) */ /* ------------------------------------------------------------------ */ const DUMMY_INSPECTION_ITEMS: InspectionItem[] = [ { id: "dummy-1", inspection_item_name: "외관 검사", inspection_standard: "스크래치, 변색, 찍힘 없음", inspection_method: "육안 검사", pass_criteria: "이상 없음", is_required: "Y", measured_value: "", result: null, }, { id: "dummy-2", inspection_item_name: "치수 검사", inspection_standard: "규격 +-0.5mm", inspection_method: "캘리퍼스 측정", pass_criteria: "허용 오차 이내", is_required: "Y", measured_value: "", result: null, }, { id: "dummy-3", inspection_item_name: "수량 검사", inspection_standard: "발주 수량과 일치", inspection_method: "전수 검사", pass_criteria: "수량 일치", is_required: "Y", measured_value: "", result: null, }, { id: "dummy-4", inspection_item_name: "포장 상태", inspection_standard: "포장 손상 없음", inspection_method: "육안 검사", pass_criteria: "이상 없음", is_required: "", measured_value: "", result: null, }, ]; /* ------------------------------------------------------------------ */ /* Component */ /* ------------------------------------------------------------------ */ export function InspectionModal({ open, onClose, onComplete, onCancel, itemCode, itemName, totalQty, initialResult, }: InspectionModalProps) { const [inspItems, setInspItems] = useState([]); const [loading, setLoading] = useState(false); const [goodQty, setGoodQty] = useState(0); const [badQty, setBadQty] = useState(0); /* NumPad state */ const [numpadOpen, setNumpadOpen] = useState(false); const [numpadTitle, setNumpadTitle] = useState(""); const [numpadValue, setNumpadValue] = useState(""); const [numpadMax, setNumpadMax] = useState(undefined); const numpadCallbackRef = React.useRef<((val: string) => void) | null>(null); const openNumpad = ( title: string, currentValue: string | number, onConfirm: (v: string) => void, max?: number, ) => { setNumpadTitle(title); setNumpadValue(String(currentValue || "")); setNumpadMax(max); numpadCallbackRef.current = onConfirm; setNumpadOpen(true); }; const [remark, setRemark] = useState(""); /* Fetch inspection items from DB */ const fetchInspectionItems = useCallback(async () => { setLoading(true); try { const res = await apiClient.get("/pop/inspection-result/info", { params: { itemCode }, }); const data = res.data?.data; if (Array.isArray(data) && data.length > 0) { setInspItems( data.map((r: Record) => ({ id: String(r.id ?? ""), inspection_item_name: String(r.inspection_item_name ?? ""), inspection_standard: String(r.inspection_standard ?? ""), inspection_method: String(r.inspection_method ?? ""), pass_criteria: String(r.pass_criteria ?? ""), is_required: String(r.is_required ?? "Y"), measured_value: "", result: null, })), ); } else { setInspItems(DUMMY_INSPECTION_ITEMS.map((i) => ({ ...i }))); } } catch { setInspItems(DUMMY_INSPECTION_ITEMS.map((i) => ({ ...i }))); } finally { setLoading(false); } }, [itemCode]); /* Init on open */ useEffect(() => { if (!open) return; if (initialResult) { setInspItems(initialResult.items.map((i) => ({ ...i }))); setGoodQty(initialResult.goodQty); setBadQty(initialResult.badQty); setRemark(initialResult.remark); } else { fetchInspectionItems(); setGoodQty(totalQty); setBadQty(0); setRemark(""); } }, [open, initialResult, fetchInspectionItems, totalQty]); /* Update item */ const updateItem = ( id: string, field: "measured_value" | "result", value: string, ) => { setInspItems((prev) => prev.map((item) => item.id === id ? { ...item, [field]: field === "result" ? item.result === value ? null : value : value, } : item, ), ); }; /* Handle good/bad qty sync */ const handleGoodQtyChange = (val: number) => { const v = Math.max(0, Math.min(val, totalQty)); setGoodQty(v); setBadQty(totalQty - v); }; const handleBadQtyChange = (val: number) => { const v = Math.max(0, Math.min(val, totalQty)); setBadQty(v); setGoodQty(totalQty - v); }; /* 검사 완료 가능 여부: 필수 항목이 모두 pass */ const canComplete = inspItems .filter((it) => it.is_required === "Y") .every((it) => it.result === "pass"); /* Complete */ const [allocating, setAllocating] = useState(false); const handleComplete = async () => { if (!canComplete) return; setAllocating(true); try { // 1. 기존 inspectionNumber 있으면 재사용, 없으면 채번 호출 let inspectionNumber = initialResult?.inspectionNumber; if (!inspectionNumber) { try { const res = await apiClient.post( "/pop/inspection-result/allocate-number", ); inspectionNumber = res.data?.data?.inspectionNumber; } catch (err) { console.error("[검사번호 채번 실패]", err); } } // 2. 결과 전달 (채번 포함) onComplete({ items: inspItems, goodQty, badQty, remark, completed: true, inspectionNumber, }); onClose(); } finally { setAllocating(false); } }; if (!open) return null; return (
{/* Overlay */}
{/* Bottom sheet */}
e.stopPropagation()} > {/* Handle bar */}
{/* Header */}

자주검사

{/* Scrollable body */}
{/* Item summary */}
{itemCode} {itemName} {totalQty.toLocaleString()} EA
{/* Inspection items section */}

검사 항목

{inspItems.length}개
{loading ? (
불러오는 중...
) : inspItems.length === 0 ? (
등록된 검사 항목이 없습니다
) : (
{inspItems.map((item) => (
{/* Item header */}
{item.inspection_item_name} {item.is_required === "Y" && ( 필수 )}
{/* Info grid */}
{item.inspection_standard && ( <> 기준 {item.inspection_standard} )} {item.inspection_method && ( <> 방법 {item.inspection_method} )} {item.pass_criteria && ( <> 판정 {item.pass_criteria} )}
{/* Input + result buttons */}
{/* Camera placeholder */}
))}
)}
{/* Final judgment section */}

종합 판정

{/* Good / Bad qty */}
EA
EA
{/* Total summary */}
전체 수량 {totalQty.toLocaleString()} EA
{/* Remark */}

비고