'use client'; /** * 수입검사 등록 (IQC) 페이지 * IntegratedDetailTemplate 마이그레이션 (2025-01-20) * * - 검사 대상 선택 * - 검사 정보 입력 (검사일, 검사자*, LOT번호) * - 검사 항목 테이블 (겉모양, 두께, 폭, 길이) * - 종합 의견 */ import { useState, useCallback, useMemo, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; import { getTodayString } from '@/lib/utils/date'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; import { materialInspectionCreateConfig } from './inspectionConfig'; import { ContentSkeleton } from '@/components/ui/skeleton'; import { Input } from '@/components/ui/input'; import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { getReceivings } from './actions'; import type { InspectionCheckItem, ReceivingItem } from './types'; import { SuccessDialog } from './SuccessDialog'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; // LOT 번호 생성 함수 (YYMMDD-NN 형식) function generateLotNo(): string { const now = new Date(); const yy = String(now.getFullYear()).slice(-2); const mm = String(now.getMonth() + 1).padStart(2, '0'); const dd = String(now.getDate()).padStart(2, '0'); const random = String(Math.floor(Math.random() * 100)).padStart(2, '0'); return `${yy}${mm}${dd}-${random}`; } // 기본 검사 항목 const defaultInspectionItems: InspectionCheckItem[] = [ { id: '1', name: '겉모양', specification: '외관 이상 없음', method: '육안', judgment: '', remark: '' }, { id: '2', name: '두께', specification: 't 1.0', method: '계측', judgment: '', remark: '' }, { id: '3', name: '폭', specification: 'W 1,000mm', method: '계측', judgment: '', remark: '' }, { id: '4', name: '길이', specification: 'L 2,000mm', method: '계측', judgment: '', remark: '' }, ]; interface Props { id?: string; // 특정 발주건으로 바로 진입하는 경우 } export function InspectionCreate({ id }: Props) { const router = useRouter(); // 검사 대상 목록 (API에서 조회) const [inspectionTargets, setInspectionTargets] = useState([]); const [isLoadingTargets, setIsLoadingTargets] = useState(true); // 선택된 검사 대상 const [selectedTargetId, setSelectedTargetId] = useState(id || ''); // 검사 정보 const [inspectionDate, setInspectionDate] = useState(() => getTodayString()); const [inspector, setInspector] = useState(''); const [lotNo, setLotNo] = useState(() => generateLotNo()); // 검사 항목 const [inspectionItems, setInspectionItems] = useState( defaultInspectionItems.map((item) => ({ ...item })) ); // 종합 의견 const [opinion, setOpinion] = useState(''); // 유효성 검사 에러 const [validationErrors, setValidationErrors] = useState>({}); // 성공 다이얼로그 const [showSuccess, setShowSuccess] = useState(false); // 검사 대상 목록 로드 (inspection_pending 상태인 입고 건) useEffect(() => { const loadTargets = async () => { setIsLoadingTargets(true); try { const result = await getReceivings({ status: 'inspection_pending', perPage: 100 }); if (result.success) { setInspectionTargets(result.data); // 초기 선택: id가 있으면 해당 건, 없으면 첫 번째 항목 if (!selectedTargetId && result.data.length > 0) { setSelectedTargetId(result.data[0].id); } } } catch (err) { if (isNextRedirectError(err)) throw err; console.error('[InspectionCreate] loadTargets error:', err); } finally { setIsLoadingTargets(false); } }; loadTargets(); }, []); // 선택된 대상 정보 (나중에 검사 저장 시 사용) const _selectedTarget = useMemo(() => { return inspectionTargets.find((t) => t.id === selectedTargetId); }, [inspectionTargets, selectedTargetId]); // 대상 선택 핸들러 const handleTargetSelect = useCallback((targetId: string) => { setSelectedTargetId(targetId); }, []); // 판정 변경 핸들러 const handleJudgmentChange = useCallback((itemId: string, index: number, judgment: '적' | '부적') => { setInspectionItems((prev) => prev.map((item) => (item.id === itemId ? { ...item, judgment } : item)) ); // 해당 항목의 에러 클리어 setValidationErrors((prev) => { const key = `judgment_${index}`; if (prev[key]) { const { [key]: _, ...rest } = prev; return rest; } return prev; }); }, []); // 비고 변경 핸들러 const handleRemarkChange = useCallback((itemId: string, remark: string) => { setInspectionItems((prev) => prev.map((item) => (item.id === itemId ? { ...item, remark } : item)) ); }, []); // 유효성 검사 const validateForm = useCallback((): boolean => { const errors: Record = {}; // 필수 필드: 검사자 if (!inspector.trim()) { errors.inspector = '검사자는 필수 입력 항목입니다.'; } // 검사 항목 판정 확인 inspectionItems.forEach((item, index) => { if (!item.judgment) { errors[`judgment_${index}`] = `${item.name}: 판정을 선택해주세요.`; } }); setValidationErrors(errors); if (Object.keys(errors).length > 0) { const firstError = Object.values(errors)[0]; toast.error(firstError); return false; } return true; }, [inspector, inspectionItems]); // 검사 저장 const handleSubmit = useCallback(async () => { if (!validateForm()) { return; } // TODO: API 호출 setShowSuccess(true); }, [validateForm, selectedTargetId, inspectionDate, inspector, lotNo, inspectionItems, opinion]); // 취소 - 목록으로 const handleCancel = useCallback(() => { router.push('/ko/material/receiving-management'); }, [router]); // 성공 다이얼로그 닫기 const handleSuccessClose = useCallback(() => { setShowSuccess(false); router.push('/ko/material/receiving-management'); }, [router]); // ===== 폼 콘텐츠 렌더링 ===== const renderFormContent = useCallback(() => ( <>
{/* 좌측: 검사 대상 선택 */}
{isLoadingTargets ? ( ) : inspectionTargets.length === 0 ? (
검사 대기 중인 입고 건이 없습니다.
) : ( inspectionTargets.map((target) => (
handleTargetSelect(target.id)} className={`p-3 rounded-lg cursor-pointer border transition-colors ${ selectedTargetId === target.id ? 'bg-blue-50 border-blue-300' : 'bg-white border-gray-200 hover:bg-gray-50' }`} >

{target.orderNo}

{target.supplier} · {target.orderQty ?? target.receivingQty ?? '-'} {target.unit}

)) )}
{/* 우측: 검사 정보 및 항목 */}
{/* 검사 정보 */}

검사 정보

setInspectionDate(date)} />
{ setInspector(e.target.value); if (validationErrors.inspector) { setValidationErrors((prev) => { const { inspector: _, ...rest } = prev; return rest; }); } }} placeholder="검사자명 입력" className={validationErrors.inspector ? 'border-red-500' : ''} /> {validationErrors.inspector && (

{validationErrors.inspector}

)}
setLotNo(e.target.value)} />
{/* 검사 항목 */}

검사 항목

{inspectionItems.map((item, index) => { const judgmentErrorKey = `judgment_${index}`; return ( ); })}
검사항목 규격 검사방법 판정 비고
{item.name} {item.specification} {item.method} {validationErrors[judgmentErrorKey] && (

{validationErrors[judgmentErrorKey]}

)}
handleRemarkChange(item.id, e.target.value)} placeholder="비고" className="h-8" />
{/* 종합 의견 */}