'use client'; /** * 제품검사 등록 페이지 * * 기획서 기반 전면 재구축: * - 기본정보 입력 * - 건축공사장, 자재유통업자, 공사시공자, 공사감리자 정보 * - 검사 정보 (검사방문요청일, 기간, 검사자, 현장주소) * - 수주 설정 정보 (수주 선택 → 규격 비교 테이블) */ import { useState, useCallback, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { Plus, Trash2, ClipboardCheck } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { getPresetStyle } from '@/lib/utils/status-config'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from '@/components/ui/accordion'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; import { qualityInspectionCreateConfig } from './inspectionConfig'; import { toast } from 'sonner'; import { createInspection } from './actions'; import { isOrderSpecSame, calculateOrderSummary } from './mockData'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; import { OrderSelectModal } from './OrderSelectModal'; import { ProductInspectionInputModal } from './ProductInspectionInputModal'; import type { InspectionFormData, OrderSettingItem, OrderSelectItem, OrderGroup, ProductInspectionData } from './types'; import { emptyConstructionSite, emptyMaterialDistributor, emptyConstructor, emptySupervisor, emptyScheduleInfo, } from './mockData'; export function InspectionCreate() { const router = useRouter(); // 폼 상태 const [formData, setFormData] = useState({ qualityDocNumber: '', siteName: '', client: '', manager: '', managerContact: '', constructionSite: { ...emptyConstructionSite }, materialDistributor: { ...emptyMaterialDistributor }, constructorInfo: { ...emptyConstructor }, supervisor: { ...emptySupervisor }, scheduleInfo: { ...emptyScheduleInfo }, orderItems: [], }); const [isSubmitting, setIsSubmitting] = useState(false); const [orderModalOpen, setOrderModalOpen] = useState(false); // 제품검사 입력 모달 const [inspectionInputOpen, setInspectionInputOpen] = useState(false); const [selectedOrderItem, setSelectedOrderItem] = useState(null); // ===== 수주 선택 처리 ===== const handleOrderSelect = useCallback((items: OrderSelectItem[]) => { const newOrderItems: OrderSettingItem[] = items.map((item) => ({ id: item.id, orderNumber: item.orderNumber, siteName: item.siteName, deliveryDate: item.deliveryDate, floor: '', symbol: '', orderWidth: 0, orderHeight: 0, constructionWidth: 0, constructionHeight: 0, changeReason: '', })); setFormData((prev) => ({ ...prev, orderItems: [...prev.orderItems, ...newOrderItems], })); }, []); // ===== 수주 항목 삭제 ===== const handleRemoveOrderItem = useCallback((itemId: string) => { setFormData((prev) => ({ ...prev, orderItems: prev.orderItems.filter((item) => item.id !== itemId), })); }, []); // ===== 폼 필드 변경 헬퍼 ===== const updateField = useCallback(( key: K, value: InspectionFormData[K] ) => { setFormData((prev) => ({ ...prev, [key]: value })); }, []); const updateNested = useCallback(( section: 'constructionSite' | 'materialDistributor' | 'constructorInfo' | 'supervisor' | 'scheduleInfo', field: string, value: string ) => { setFormData((prev) => ({ ...prev, [section]: { ...(prev[section] as unknown as Record), [field]: value, }, })); }, []); // ===== 수주 설정 요약 ===== const orderSummary = useMemo( () => calculateOrderSummary(formData.orderItems), [formData.orderItems] ); // ===== 수주 항목을 그룹별로 묶기 (아코디언용) ===== const groupOrderItems = useCallback((items: OrderSettingItem[]): OrderGroup[] => { const groups: Record = {}; items.forEach((item) => { const key = item.orderNumber; if (!groups[key]) { groups[key] = { orderNumber: item.orderNumber, siteName: item.siteName, deliveryDate: item.deliveryDate, locationCount: 0, items: [], }; } groups[key].items.push(item); groups[key].locationCount = groups[key].items.length; }); return Object.values(groups); }, []); const orderGroups = useMemo( () => groupOrderItems(formData.orderItems), [formData.orderItems, groupOrderItems] ); // ===== 제품검사 입력 핸들러 ===== const handleOpenInspectionInput = useCallback((item: OrderSettingItem) => { setSelectedOrderItem(item); setInspectionInputOpen(true); }, []); const handleInspectionComplete = useCallback((data: ProductInspectionData) => { if (!selectedOrderItem) return; setFormData((prev) => ({ ...prev, orderItems: prev.orderItems.map((item) => item.id === selectedOrderItem.id ? { ...item, inspectionData: data } : item ), })); toast.success('검사 데이터가 저장되었습니다.'); setSelectedOrderItem(null); }, [selectedOrderItem]); // ===== 시공규격/변경사유 수정 핸들러 ===== const handleUpdateOrderItemField = useCallback(( itemId: string, field: 'constructionWidth' | 'constructionHeight' | 'changeReason', value: string | number ) => { setFormData((prev) => ({ ...prev, orderItems: prev.orderItems.map((item) => item.id === itemId ? { ...item, [field]: value } : item ), })); }, []); // ===== 취소 ===== const handleCancel = useCallback(() => { router.push('/quality/inspections'); }, [router]); // ===== 등록 제출 ===== const handleSubmit = useCallback(async (): Promise<{ success: boolean; error?: string }> => { // 필수 필드 검증 if (!formData.siteName.trim()) { toast.error('현장명은 필수 입력 항목입니다.'); return { success: false, error: '현장명을 입력해주세요.' }; } if (!formData.client.trim()) { toast.error('수주처는 필수 입력 항목입니다.'); return { success: false, error: '수주처를 입력해주세요.' }; } setIsSubmitting(true); try { const result = await createInspection(formData); if (result.success) { toast.success('제품검사가 등록되었습니다.'); router.push('/quality/inspections'); return { success: true }; } return { success: false, error: result.error || '등록에 실패했습니다.' }; } catch (error) { if (isNextRedirectError(error)) throw error; return { success: false, error: '등록 중 오류가 발생했습니다.' }; } finally { setIsSubmitting(false); } }, [formData, router]); // ===== 수주 설정 아코디언 ===== const renderOrderAccordion = (groups: OrderGroup[]) => { if (groups.length === 0) { return (
수주를 선택해주세요.
); } return ( {groups.map((group) => ( {/* 상위 레벨: 수주번호, 현장명, 납품일, 개소, 삭제 */}
{group.orderNumber} {group.siteName} {group.deliveryDate} {group.locationCount}개소
{/* 하위 레벨: 테이블 */} No. 층수 부호 수주 가로 수주 세로 시공 가로 시공 세로 변경사유 검사 {group.items.map((item, index) => ( {index + 1} {item.floor || '-'} {item.symbol || '-'} {item.orderWidth} {item.orderHeight} handleUpdateOrderItemField(item.id, 'constructionWidth', Number(e.target.value)) } className="h-8 w-20 text-center" /> handleUpdateOrderItemField(item.id, 'constructionHeight', Number(e.target.value)) } className="h-8 w-20 text-center" /> handleUpdateOrderItemField(item.id, 'changeReason', e.target.value) } className="h-8" placeholder="변경사유 입력" /> ))}
))}
); }; // ===== 폼 렌더링 ===== const renderFormContent = useCallback(() => (
{/* 기본 정보 */} 기본 정보
updateField('qualityDocNumber', e.target.value)} placeholder="품질관리서 번호 입력" />
updateField('siteName', e.target.value)} placeholder="현장명 입력" />
updateField('client', e.target.value)} placeholder="수주처 입력" />
updateField('manager', e.target.value)} placeholder="담당자 입력" />
updateField('managerContact', e.target.value)} placeholder="담당자 연락처 입력" />
{/* 건축공사장 정보 */} 건축공사장 정보
updateNested('constructionSite', 'siteName', e.target.value)} placeholder="현장명 입력" />
updateNested('constructionSite', 'landLocation', e.target.value)} placeholder="대지위치 입력" />
updateNested('constructionSite', 'lotNumber', e.target.value)} placeholder="지번 입력" />
{/* 자재유통업자 정보 */} 자재유통업자 정보
updateNested('materialDistributor', 'companyName', e.target.value)} placeholder="회사명 입력" />
updateNested('materialDistributor', 'companyAddress', e.target.value)} placeholder="회사주소 입력" />
updateNested('materialDistributor', 'representativeName', e.target.value)} placeholder="대표자명 입력" />
updateNested('materialDistributor', 'phone', e.target.value)} placeholder="전화번호 입력" />
{/* 공사시공자 정보 */} 공사시공자 정보
updateNested('constructorInfo', 'companyName', e.target.value)} placeholder="회사명 입력" />
updateNested('constructorInfo', 'companyAddress', e.target.value)} placeholder="회사주소 입력" />
updateNested('constructorInfo', 'name', e.target.value)} placeholder="성명 입력" />
updateNested('constructorInfo', 'phone', e.target.value)} placeholder="전화번호 입력" />
{/* 공사감리자 정보 */} 공사감리자 정보
updateNested('supervisor', 'officeName', e.target.value)} placeholder="사무소명 입력" />
updateNested('supervisor', 'officeAddress', e.target.value)} placeholder="사무소주소 입력" />
updateNested('supervisor', 'name', e.target.value)} placeholder="성명 입력" />
updateNested('supervisor', 'phone', e.target.value)} placeholder="전화번호 입력" />
{/* 검사 정보 */} 검사 정보
updateNested('scheduleInfo', 'visitRequestDate', e.target.value)} />
updateNested('scheduleInfo', 'startDate', e.target.value)} />
updateNested('scheduleInfo', 'endDate', e.target.value)} />
updateNested('scheduleInfo', 'inspector', e.target.value)} placeholder="검사자 입력" />
{/* 현장 주소 */}
updateNested('scheduleInfo', 'sitePostalCode', e.target.value)} className="w-28" />
updateNested('scheduleInfo', 'siteAddress', e.target.value)} placeholder="주소 입력" />
updateNested('scheduleInfo', 'siteAddressDetail', e.target.value)} placeholder="상세주소 입력" />
{/* 수주 설정 정보 */}
수주 설정 정보
전체: {orderSummary.total} 일치: {orderSummary.same} 불일치: {orderSummary.changed}
{renderOrderAccordion(orderGroups)}
), [formData, orderSummary, orderGroups, updateField, updateNested, handleRemoveOrderItem, handleOpenInspectionInput, handleUpdateOrderItemField, orderModalOpen]); // 이미 선택된 수주 ID 목록 const excludeOrderIds = useMemo( () => formData.orderItems.map((item) => item.id), [formData.orderItems] ); return ( <> {/* 제품검사 입력 모달 */} ); }