'use client'; /** * 작업일지 모달 * * document-system 통합 버전 (2026-01-22) * 공정별 작업일지 지원 (2026-01-29) * 공정관리 양식 매핑 연동 (2026-02-11) * - DocumentViewer 사용 * - 공정관리에서 매핑된 workLogTemplateId/Name 기반으로 콘텐츠 분기 * - 양식 미매핑 시 processType 폴백 */ import { useState, useEffect, useCallback } from 'react'; import { Loader2, Save } from 'lucide-react'; import { DocumentViewer } from '@/components/document-system'; import { Button } from '@/components/ui/button'; import { toast } from 'sonner'; import { getWorkOrderById, getMaterialInputLots } from '../WorkOrders/actions'; import { saveWorkLog } from './actions'; import type { MaterialInputLot } from '../WorkOrders/actions'; import type { WorkOrder, ProcessType } from '../WorkOrders/types'; import { WorkLogContent } from './WorkLogContent'; import { ScreenWorkLogContent, SlatWorkLogContent, BendingWorkLogContent, } from '../WorkOrders/documents'; interface WorkLogModalProps { open: boolean; onOpenChange: (open: boolean) => void; workOrderId: string | null; processType?: ProcessType; /** 공정관리에서 매핑된 작업일지 양식 ID */ workLogTemplateId?: number; /** 공정관리에서 매핑된 작업일지 양식명 (예: '스크린 작업일지') */ workLogTemplateName?: string; } /** * 양식명 → 공정 타입 매핑 * 공정관리에서 매핑된 양식명을 기반으로 콘텐츠 컴포넌트를 결정 */ function resolveProcessTypeFromTemplate(templateName?: string): ProcessType | undefined { if (!templateName) return undefined; if (templateName.includes('스크린')) return 'screen'; if (templateName.includes('슬랫')) return 'slat'; if (templateName.includes('절곡')) return 'bending'; return undefined; } export function WorkLogModal({ open, onOpenChange, workOrderId, processType, workLogTemplateId, workLogTemplateName, }: WorkLogModalProps) { const [order, setOrder] = useState(null); const [materialLots, setMaterialLots] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(null); // 목업 WorkOrder 생성 const createMockOrder = (id: string, pType?: ProcessType): WorkOrder => ({ id, workOrderNo: 'KD-WO-260129-01', lotNo: 'KD-SA-260129-01', processId: 1, processName: pType === 'slat' ? '슬랫' : pType === 'bending' ? '절곡' : '스크린', processCode: pType || 'screen', processType: pType || 'screen', status: 'in_progress', client: '(주)경동', projectName: '서울 강남 현장', dueDate: '2026-02-05', assignee: '홍길동', assignees: [{ id: '1', name: '홍길동', isPrimary: true }], orderDate: '2026-01-20', scheduledDate: '2026-01-29', shipmentDate: '2026-02-05', salesOrderDate: '2026-01-15', isAssigned: true, isStarted: true, priority: 3, priorityLabel: '긴급', shutterCount: 12, department: '생산부', items: [ { id: '1', no: 1, status: 'in_progress', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '8,260 X 8,350', quantity: 2, unit: 'EA', orderNodeId: null, orderNodeName: '' }, { id: '2', no: 2, status: 'waiting', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '6,400 X 5,200', quantity: 4, unit: 'EA', orderNodeId: null, orderNodeName: '' }, { id: '3', no: 3, status: 'completed', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12,000 X 4,500', quantity: 1, unit: 'EA', orderNodeId: null, orderNodeName: '' }, ], currentStep: 2, issues: [], note: '', }); // 모달 열릴 때 데이터 fetch useEffect(() => { if (open && workOrderId) { // 목업 ID인 경우 API 호출 생략 if (workOrderId.startsWith('mock-')) { setOrder(createMockOrder(workOrderId, processType)); setError(null); return; } setIsLoading(true); setError(null); Promise.all([ getWorkOrderById(workOrderId), getMaterialInputLots(workOrderId), ]) .then(([orderResult, lotsResult]) => { if (orderResult.success && orderResult.data) { setOrder(orderResult.data); } else { setError(orderResult.error || '데이터를 불러올 수 없습니다.'); } if (lotsResult.success) { setMaterialLots(lotsResult.data); } }) .catch(() => { setError('서버 오류가 발생했습니다.'); }) .finally(() => { setIsLoading(false); }); } else if (!open) { // 모달 닫힐 때 상태 초기화 setOrder(null); setMaterialLots([]); setError(null); } }, [open, workOrderId, processType]); // 저장 핸들러 const handleSave = useCallback(async () => { if (!workOrderId || !order) return; setIsSaving(true); try { // 현재 아이템 데이터를 table_data로 변환 const tableData = (order.items || []).map((item) => ({ id: item.id, item_name: item.productName, specification: item.specification || item.floorCode, quantity: item.quantity, unit: item.unit || 'EA', })); const result = await saveWorkLog(workOrderId, { table_data: tableData, title: workLogTemplateName || '작업일지', }); if (result.success) { toast.success('작업일지가 저장되었습니다.'); } else { toast.error(result.error || '저장에 실패했습니다.'); } } catch { toast.error('저장 중 오류가 발생했습니다.'); } finally { setIsSaving(false); } }, [workOrderId, order, workLogTemplateName]); if (!workOrderId) return null; // 로딩/에러 상태는 DocumentViewer 내부에서 처리 const subtitle = order ? `${order.processName} 생산부서` : undefined; // 양식 미매핑 안내 const renderNoTemplate = () => (

이 공정에 작업일지 양식이 매핑되지 않았습니다.

공정관리에서 작업일지 양식을 설정해주세요.

); // 공정관리 양식 매핑 기반 콘텐츠 분기 const renderContent = () => { if (!order) return null; // 1순위: 공정관리에서 매핑된 양식명으로 결정 const templateType = resolveProcessTypeFromTemplate(workLogTemplateName); // 2순위: processType 폴백 (양식 미매핑 시) const type = templateType || processType || order.processType; // 양식이 매핑되어 있지 않은 경우 안내 if (!workLogTemplateId && !processType) { return renderNoTemplate(); } switch (type) { case 'screen': return ; case 'slat': return ; case 'bending': { const lotNoMap: Record = {}; for (const lot of materialLots) { if (lot.item_code.startsWith('BD-')) { lotNoMap[lot.item_code] = lot.lot_no; } } return ; } default: return ; } }; // 양식명으로 문서 제목 결정 const documentTitle = workLogTemplateName || '작업일지'; const toolbarExtra = ( ); return ( {isLoading ? (
) : error || !order ? (

{error || '데이터를 불러올 수 없습니다.'}

) : ( renderContent() )}
); }