import React, { useState, useEffect } from 'react'; import { fetchMaterials } from '../api'; import api from '../api'; import { PipeMaterialsView, FittingMaterialsView, FlangeMaterialsView, ValveMaterialsView, GasketMaterialsView, BoltMaterialsView, SupportMaterialsView, SpecialMaterialsView, UnclassifiedMaterialsView } from '../components/bom'; import './BOMManagementPage.css'; const BOMManagementPage = ({ onNavigate, selectedProject, fileId, jobNo, bomName, revision, filename, user }) => { const [materials, setMaterials] = useState([]); const [loading, setLoading] = useState(true); const [selectedCategory, setSelectedCategory] = useState('PIPE'); const [selectedMaterials, setSelectedMaterials] = useState(new Set()); const [exportHistory, setExportHistory] = useState([]); const [availableRevisions, setAvailableRevisions] = useState([]); const [currentRevision, setCurrentRevision] = useState(revision || 'Rev.0'); const [userRequirements, setUserRequirements] = useState({}); const [purchasedMaterials, setPurchasedMaterials] = useState(new Set()); const [error, setError] = useState(null); // 리비전 관련 상태 const [isRevisionMode, setIsRevisionMode] = useState(false); const [revisionData, setRevisionData] = useState(null); const [previousFileId, setPreviousFileId] = useState(null); const [changedMaterials, setChangedMaterials] = useState({}); // 자재 업데이트 함수 (브랜드, 사용자 요구사항 등) const updateMaterial = (materialId, updates) => { setMaterials(prevMaterials => prevMaterials.map(material => material.id === materialId ? { ...material, ...updates } : material ) ); }; // 카테고리 정의 const categories = [ { key: 'PIPE', label: 'Pipes', color: '#3b82f6' }, { key: 'FITTING', label: 'Fittings', color: '#10b981' }, { key: 'FLANGE', label: 'Flanges', color: '#f59e0b' }, { key: 'VALVE', label: 'Valves', color: '#ef4444' }, { key: 'GASKET', label: 'Gaskets', color: '#8b5cf6' }, { key: 'BOLT', label: 'Bolts', color: '#6b7280' }, { key: 'SUPPORT', label: 'Supports', color: '#f97316' }, { key: 'SPECIAL', label: 'Special Items', color: '#ec4899' }, { key: 'UNCLASSIFIED', label: 'Unclassified', color: '#64748b' } ]; // 자료 로드 함수들 const loadMaterials = async (id) => { try { setLoading(true); console.log('🔍 자재 데이터 로딩 중...', { file_id: id, selectedProject: selectedProject?.job_no || selectedProject?.official_project_code, jobNo }); // 구매신청된 자재 먼저 확인 const projectJobNo = selectedProject?.job_no || selectedProject?.official_project_code || jobNo; await loadPurchasedMaterials(projectJobNo); const response = await fetchMaterials({ file_id: parseInt(id), limit: 10000, exclude_requested: false, job_no: projectJobNo }); if (response.data?.materials) { const materialsData = response.data.materials; console.log(`✅ ${materialsData.length}개 원본 자재 로드 완료`); setMaterials(materialsData); setError(null); } else { console.warn('⚠️ 자재 데이터가 없습니다:', response.data); setMaterials([]); } } catch (error) { console.error('자재 로드 실패:', error); setError('자재 로드에 실패했습니다.'); } finally { setLoading(false); } }; const loadAvailableRevisions = async () => { try { const response = await api.get('/files/', { params: { job_no: jobNo } }); const allFiles = Array.isArray(response.data) ? response.data : response.data?.files || []; const sameBomFiles = allFiles.filter(file => (file.bom_name || file.original_filename) === bomName ); sameBomFiles.sort((a, b) => { const revA = parseInt(a.revision?.replace('Rev.', '') || '0'); const revB = parseInt(b.revision?.replace('Rev.', '') || '0'); return revB - revA; }); setAvailableRevisions(sameBomFiles); } catch (error) { console.error('리비전 목록 조회 실패:', error); } }; const loadPurchasedMaterials = async (jobNo) => { try { // 새로운 API로 구매신청된 자재 ID 목록 조회 const response = await api.get('/purchase-request/requested-materials', { params: { job_no: jobNo, file_id: fileId } }); if (response.data?.requested_material_ids) { const purchasedIds = new Set(response.data.requested_material_ids); setPurchasedMaterials(purchasedIds); console.log(`✅ ${purchasedIds.size}개 구매신청된 자재 ID 로드 완료`); } } catch (error) { console.error('구매신청 자재 조회 실패:', error); } }; const loadUserRequirements = async (fileId) => { try { const response = await api.get(`/files/${fileId}/user-requirements`); if (response.data?.requirements) { const reqMap = {}; response.data.requirements.forEach(req => { reqMap[req.material_id] = req.requirement; }); setUserRequirements(reqMap); } } catch (error) { console.error('사용자 요구사항 로드 실패:', error); } }; // 리비전 모드 감지 및 변경된 자재 로드 const checkAndLoadRevisionData = async () => { try { // 현재 job_no의 모든 파일 목록 확인 const filesResponse = await api.get(`/files/list?job_no=${jobNo}`); const files = filesResponse.data.files || []; if (files.length > 1) { // 파일이 여러 개 있으면 리비전 모드 활성화 setIsRevisionMode(true); // 파일들을 업로드 날짜순으로 정렬 const sortedFiles = files.sort((a, b) => new Date(a.upload_date) - new Date(b.upload_date)); // 이전 파일 ID 찾기 (현재 파일 이전 버전) const currentIndex = sortedFiles.findIndex(file => file.id === parseInt(fileId)); if (currentIndex > 0) { const previousFile = sortedFiles[currentIndex - 1]; setPreviousFileId(previousFile.id); // 변경된 자재 로드 await loadChangedMaterials(fileId, previousFile.id); } } } catch (error) { console.error('리비전 데이터 로드 실패:', error); // API 오류 시 리비전 모드 비활성화 setIsRevisionMode(false); } }; // 변경된 자재 로드 const loadChangedMaterials = async (currentFileId, previousFileId) => { try { const response = await api.get(`/simple-revision/changed-materials/${currentFileId}/${previousFileId}`); if (response.data.success) { setChangedMaterials(response.data.data.changes_by_category || {}); setRevisionData(response.data.data); console.log('✅ 변경된 자재 로드 완료:', response.data.data); } } catch (error) { console.error('변경된 자재 로드 실패:', error); } }; // 초기 로드 useEffect(() => { if (fileId) { loadMaterials(fileId); loadAvailableRevisions(); loadUserRequirements(fileId); checkAndLoadRevisionData(); // 리비전 데이터 확인 } }, [fileId]); // 자재 로드 후 선택된 카테고리가 유효한지 확인 useEffect(() => { if (materials.length > 0) { const availableCategories = categories.filter(category => { const count = getCategoryMaterials(category.key).length; return count > 0; }); // 현재 선택된 카테고리에 자재가 없으면 첫 번째 유효한 카테고리로 전환 const currentCategoryHasMaterials = getCategoryMaterials(selectedCategory).length > 0; if (!currentCategoryHasMaterials && availableCategories.length > 0) { setSelectedCategory(availableCategories[0].key); } } }, [materials, selectedCategory]); // 카테고리별 자재 필터링 (리비전 모드 지원) const getCategoryMaterials = (category) => { if (isRevisionMode && changedMaterials[category]) { // 리비전 모드: 변경된 자재만 표시 const changedMaterialIds = changedMaterials[category].changes.map(change => change.material_id); return materials.filter(material => (material.classified_category === category || material.category === category) && changedMaterialIds.includes(material.id) ); } else { // 일반 모드: 모든 자재 표시 return materials.filter(material => material.classified_category === category || material.category === category ); } }; // 리비전 액션 정보 가져오기 const getRevisionAction = (materialId, category) => { if (!isRevisionMode || !changedMaterials[category]) return null; const change = changedMaterials[category].changes.find(c => c.material_id === materialId); return change || null; }; // 카테고리별 컴포넌트 렌더링 const renderCategoryView = () => { const categoryMaterials = getCategoryMaterials(selectedCategory); const commonProps = { materials: categoryMaterials, selectedMaterials, setSelectedMaterials, userRequirements, setUserRequirements, purchasedMaterials, onPurchasedMaterialsUpdate: (materialIds) => { setPurchasedMaterials(prev => { const newSet = new Set(prev); materialIds.forEach(id => newSet.add(id)); console.log(`📦 구매신청 자재 추가: 기존 ${prev.size}개 → 신규 ${newSet.size}개`); return newSet; }); }, updateMaterial, // 자재 업데이트 함수 추가 fileId, jobNo, user, onNavigate, // 리비전 관련 props 추가 isRevisionMode, getRevisionAction: (materialId) => getRevisionAction(materialId, selectedCategory), revisionData }; switch (selectedCategory) { case 'PIPE': return ; case 'FITTING': return ; case 'FLANGE': return ; case 'VALVE': return ; case 'GASKET': return ; case 'BOLT': return ; case 'SUPPORT': return ; case 'SPECIAL': return ; case 'UNCLASSIFIED': return ; default: return
카테고리를 선택해주세요.
; } }; if (loading) { return (
Loading Materials...
); } return (
{/* 헤더 섹션 */}

BOM Materials Management

{isRevisionMode && (
📊 Revision Mode
)}

{bomName} - {currentRevision} | Project: {selectedProject?.job_name || jobNo} {isRevisionMode && revisionData && ( • {revisionData.total_changed_materials} materials changed )}

{/* 통계 정보 */}
{materials.length}
Total Materials
{getCategoryMaterials(selectedCategory).length}
{selectedCategory} Items
{selectedMaterials.size}
Selected
{purchasedMaterials.size}
Purchased
{/* 카테고리 탭 */}
{categories .filter((category) => { const count = getCategoryMaterials(category.key).length; return count > 0; // 0개인 카테고리는 숨김 }) .map((category) => { const isActive = selectedCategory === category.key; const count = getCategoryMaterials(category.key).length; const hasChanges = isRevisionMode && changedMaterials[category.key]; return ( ); })}
{/* 카테고리별 컨텐츠 */}
{error ? (
⚠️
Error Loading Materials
{error}
) : ( renderCategoryView() )}
); }; export default BOMManagementPage;