import React, { useState, useEffect } from 'react'; import { uploadFile as uploadFileApi, fetchFiles as fetchFilesApi, deleteFile as deleteFileApi, api } from '../api'; import BOMFileUpload from '../components/BOMFileUpload'; import BOMFileTable from '../components/BOMFileTable'; import RevisionUploadDialog from '../components/RevisionUploadDialog'; const BOMStatusPage = ({ jobNo, jobName, onNavigate }) => { const [files, setFiles] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [uploading, setUploading] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [bomName, setBomName] = useState(''); const [revisionDialog, setRevisionDialog] = useState({ open: false, bomName: '', parentId: null }); const [revisionFile, setRevisionFile] = useState(null); const [purchaseModal, setPurchaseModal] = useState({ open: false, data: null, fileInfo: null }); // 카테고리별 색상 함수 const getCategoryColor = (category) => { const colors = { 'pipe': '#4299e1', 'fitting': '#48bb78', 'valve': '#ed8936', 'flange': '#9f7aea', 'bolt': '#38b2ac', 'gasket': '#f56565', 'instrument': '#d69e2e', 'material': '#718096', 'integrated': '#319795', 'unknown': '#a0aec0' }; return colors[category?.toLowerCase()] || colors.unknown; }; // 파일 목록 불러오기 const fetchFiles = async () => { setLoading(true); setError(''); try { console.log('fetchFiles 호출 - jobNo:', jobNo); const response = await fetchFilesApi({ job_no: jobNo }); console.log('API 응답:', response); if (response.data && response.data.data && Array.isArray(response.data.data)) { setFiles(response.data.data); } else if (response.data && Array.isArray(response.data)) { setFiles(response.data); } else if (response.data && Array.isArray(response.data.files)) { setFiles(response.data.files); } else { setFiles([]); } } catch (err) { console.error('파일 목록 불러오기 실패:', err); setError('파일 목록을 불러오는데 실패했습니다.'); } finally { setLoading(false); } }; useEffect(() => { if (jobNo) { fetchFiles(); } }, [jobNo]); // 파일 업로드 const handleUpload = async () => { if (!selectedFile || !bomName.trim()) { setError('파일과 BOM 이름을 모두 입력해주세요.'); return; } setUploading(true); setError(''); try { const formData = new FormData(); formData.append('file', selectedFile); formData.append('job_no', jobNo); formData.append('bom_name', bomName.trim()); const uploadResult = await uploadFileApi(formData); // 업로드 성공 후 파일 목록 새로고침 await fetchFiles(); // 업로드 완료 후 자동으로 구매 수량 계산 실행 if (uploadResult && uploadResult.file_id) { // 잠시 후 구매 수량 계산 페이지로 이동 setTimeout(async () => { try { // 구매 수량 계산 API 호출 const response = await fetch(`/api/purchase/calculate?job_no=${jobNo}&revision=Rev.0&file_id=${uploadResult.file_id}`); const purchaseData = await response.json(); if (purchaseData.success) { // 구매 수량 계산 결과를 모달로 표시하거나 별도 페이지로 이동 alert(`업로드 및 분류 완료!\n구매 수량이 계산되었습니다.\n\n파이프: ${purchaseData.purchase_items?.filter(item => item.category === 'PIPE').length || 0}개 항목\n기타 자재: ${purchaseData.purchase_items?.filter(item => item.category !== 'PIPE').length || 0}개 항목`); } } catch (error) { console.error('구매 수량 계산 실패:', error); } }, 2000); // 2초 후 실행 (분류 완료 대기) } // 폼 초기화 setSelectedFile(null); setBomName(''); document.getElementById('file-input').value = ''; } catch (err) { console.error('파일 업로드 실패:', err); setError('파일 업로드에 실패했습니다.'); } finally { setUploading(false); } }; // 파일 삭제 const handleDelete = async (fileId) => { if (!window.confirm('정말로 이 파일을 삭제하시겠습니까?')) { return; } try { await deleteFileApi(fileId); await fetchFiles(); // 목록 새로고침 } catch (err) { console.error('파일 삭제 실패:', err); setError('파일 삭제에 실패했습니다.'); } }; // 자재 확인 페이지로 이동 // 구매 수량 계산 (자재 목록 페이지 거치지 않음) const handleViewMaterials = async (file) => { try { setLoading(true); // 구매 수량 계산 API 호출 console.log('구매 수량 계산 API 호출:', { job_no: file.job_no, revision: file.revision || 'Rev.0', file_id: file.id }); const response = await api.get(`/purchase/items/calculate?job_no=${file.job_no}&revision=${file.revision || 'Rev.0'}&file_id=${file.id}`); console.log('구매 수량 계산 응답:', response.data); const purchaseData = response.data; if (purchaseData.success && purchaseData.items) { // 구매 수량 계산 결과를 모달로 표시 setPurchaseModal({ open: true, data: purchaseData.items, fileInfo: file }); } else { alert('구매 수량 계산에 실패했습니다.'); } } catch (error) { console.error('구매 수량 계산 오류:', error); alert('구매 수량 계산 중 오류가 발생했습니다.'); } finally { setLoading(false); } }; // 리비전 업로드 다이얼로그 열기 const openRevisionDialog = (bomName, parentId) => { setRevisionDialog({ open: true, bomName, parentId }); }; // 리비전 업로드 const handleRevisionUpload = async () => { if (!revisionFile || !revisionDialog.bomName) { setError('파일을 선택해주세요.'); return; } setUploading(true); setError(''); try { const formData = new FormData(); formData.append('file', revisionFile); formData.append('job_no', jobNo); formData.append('bom_name', revisionDialog.bomName); formData.append('parent_id', revisionDialog.parentId); await uploadFileApi(formData); // 업로드 성공 후 파일 목록 새로고침 await fetchFiles(); // 다이얼로그 닫기 setRevisionDialog({ open: false, bomName: '', parentId: null }); setRevisionFile(null); } catch (err) { console.error('리비전 업로드 실패:', err); setError('리비전 업로드에 실패했습니다.'); } finally { setUploading(false); } }; // BOM별로 그룹화 const groupFilesByBOM = () => { const grouped = {}; files.forEach(file => { const bomKey = file.bom_name || file.original_filename || file.filename; if (!grouped[bomKey]) { grouped[bomKey] = []; } grouped[bomKey].push(file); }); // 각 그룹을 리비전 순으로 정렬 Object.keys(grouped).forEach(key => { grouped[key].sort((a, b) => { const revA = parseInt(a.revision?.replace('Rev.', '') || '0'); const revB = parseInt(b.revision?.replace('Rev.', '') || '0'); return revB - revA; // 최신 리비전이 먼저 오도록 }); }); return grouped; }; return (
| 카테고리 | 사양 | 사이즈 | 재질 | BOM 수량 | 구매 수량 | 단위 | 비고 |
|---|---|---|---|---|---|---|---|
| {item.category} | {item.specification} | {/* PIPE는 사양에 모든 정보가 포함되므로 사이즈 컬럼 비움 */} {item.category !== 'PIPE' && ( {item.size_spec || '-'} )} {item.category === 'PIPE' && ( 사양에 포함 )} | {/* PIPE는 사양에 모든 정보가 포함되므로 재질 컬럼 비움 */} {item.category !== 'PIPE' && ( {item.material_spec || '-'} )} {item.category === 'PIPE' && ( 사양에 포함 )} | {item.category === 'PIPE' ? `${Math.round(item.bom_quantity)}mm` : item.bom_quantity } | {item.category === 'PIPE' ? `${item.pipes_count}본 (${Math.round(item.calculated_qty)}mm)` : item.calculated_qty } | {item.unit} |
{item.category === 'PIPE' && (
절단수: {item.cutting_count}회
절단손실: {item.cutting_loss}mm
활용률: {Math.round(item.utilization_rate)}%
여유율: {Math.round((item.safety_factor - 1) * 100)}%
)}
|