import React, { useState, useRef, useCallback } from 'react'; import api from '../api'; const BOMUploadPage = ({ onNavigate, selectedProject, user }) => { const [dragOver, setDragOver] = useState(false); const [uploading, setUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [selectedFiles, setSelectedFiles] = useState([]); const [bomName, setBomName] = useState(''); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const fileInputRef = useRef(null); // 파일 검증 const validateFile = (file) => { const allowedTypes = [ 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'text/csv' ]; const maxSize = 50 * 1024 * 1024; // 50MB if (!allowedTypes.includes(file.type) && !file.name.match(/\.(xlsx|xls|csv)$/i)) { return '지원되지 않는 파일 형식입니다. Excel 또는 CSV 파일만 업로드 가능합니다.'; } if (file.size > maxSize) { return '파일 크기가 너무 큽니다. 50MB 이하의 파일만 업로드 가능합니다.'; } return null; }; // 파일 선택 처리 const handleFileSelect = useCallback((files) => { const fileList = Array.from(files); const validFiles = []; const errors = []; fileList.forEach(file => { const error = validateFile(file); if (error) { errors.push(`${file.name}: ${error}`); } else { validFiles.push(file); } }); if (errors.length > 0) { setError(errors.join('\n')); return; } setSelectedFiles(validFiles); setError(''); // 첫 번째 파일명을 기본 BOM 이름으로 설정 (확장자 제거) if (validFiles.length > 0 && !bomName) { const fileName = validFiles[0].name; const nameWithoutExt = fileName.replace(/\.[^/.]+$/, ''); setBomName(nameWithoutExt); } }, [bomName]); // 드래그 앤 드롭 처리 const handleDragOver = useCallback((e) => { e.preventDefault(); setDragOver(true); }, []); const handleDragLeave = useCallback((e) => { e.preventDefault(); setDragOver(false); }, []); const handleDrop = useCallback((e) => { e.preventDefault(); setDragOver(false); handleFileSelect(e.dataTransfer.files); }, [handleFileSelect]); // 파일 선택 버튼 클릭 const handleFileButtonClick = () => { fileInputRef.current?.click(); }; // 파일 업로드 const handleUpload = async () => { if (selectedFiles.length === 0) { setError('업로드할 파일을 선택해주세요.'); return; } if (!bomName.trim()) { setError('BOM 이름을 입력해주세요.'); return; } if (!selectedProject) { setError('프로젝트를 선택해주세요.'); return; } try { setUploading(true); setUploadProgress(0); setError(''); setSuccess(''); for (let i = 0; i < selectedFiles.length; i++) { const file = selectedFiles[i]; const formData = new FormData(); formData.append('file', file); formData.append('job_no', selectedProject.official_project_code || selectedProject.job_no); formData.append('bom_name', bomName.trim()); formData.append('revision', 'Rev.0'); // 새 업로드는 항상 Rev.0 const response = await api.post('/files/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: (progressEvent) => { const progress = Math.round( ((i * 100) + (progressEvent.loaded * 100) / progressEvent.total) / selectedFiles.length ); setUploadProgress(progress); } }); if (!response.data?.success) { throw new Error(response.data?.message || '업로드 실패'); } } setSuccess(`${selectedFiles.length}개 파일이 성공적으로 업로드되었습니다!`); // 3초 후 BOM 관리 페이지로 이동 setTimeout(() => { if (onNavigate) { onNavigate('bom-management', { file_id: response.data.file_id, jobNo: selectedProject.official_project_code || selectedProject.job_no, bomName: bomName.trim(), revision: 'Rev.0' }); } }, 3000); } catch (err) { console.error('업로드 실패:', err); setError(`업로드 실패: ${err.response?.data?.detail || err.message}`); } finally { setUploading(false); setUploadProgress(0); } }; // 파일 제거 const removeFile = (index) => { const newFiles = selectedFiles.filter((_, i) => i !== index); setSelectedFiles(newFiles); if (newFiles.length === 0) { setBomName(''); } }; // 파일 크기 포맷팅 const formatFileSize = (bytes) => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; return (
{/* 헤더 */}

BOM File Upload

Project: {selectedProject?.job_name || 'No Project Selected'}

{/* 프로젝트 정보 */}
{selectedProject?.official_project_code || selectedProject?.job_no || 'N/A'}
Project Code
{user?.username || 'Unknown'}
Uploaded by
{/* 업로드 영역 */}
{/* BOM 이름 입력 */}
setBomName(e.target.value)} placeholder="Enter BOM name..." style={{ width: '100%', padding: '12px 16px', border: '2px solid #e5e7eb', borderRadius: '12px', fontSize: '16px', transition: 'border-color 0.2s ease', outline: 'none' }} onFocus={(e) => e.target.style.borderColor = '#3b82f6'} onBlur={(e) => e.target.style.borderColor = '#e5e7eb'} />
{/* 파일 드롭 영역 */}
{dragOver ? '📁' : '📄'}

{dragOver ? 'Drop files here' : 'Upload BOM Files'}

Drag and drop your Excel or CSV files here, or click to browse

📋 Supported: .xlsx, .xls, .csv (Max 50MB)
{/* 숨겨진 파일 입력 */} handleFileSelect(e.target.files)} style={{ display: 'none' }} /> {/* 선택된 파일 목록 */} {selectedFiles.length > 0 && (

Selected Files ({selectedFiles.length})

{selectedFiles.map((file, index) => (
📄
{file.name}
{formatFileSize(file.size)}
))}
)} {/* 업로드 진행률 */} {uploading && (
Uploading... {uploadProgress}%
)} {/* 에러 메시지 */} {error && (
⚠️
{error}
)} {/* 성공 메시지 */} {success && (
{success}
)} {/* 업로드 버튼 */}
{/* 가이드 정보 */}

📋 Upload Guidelines

✅ Supported Formats

  • Excel files (.xlsx, .xls)
  • CSV files (.csv)
  • Maximum file size: 50MB

📊 Required Columns

  • Description (자재명/품명)
  • Quantity (수량)
  • Size information (사이즈)

⚡ Auto Processing

  • Automatic material classification
  • WELD GAP items excluded
  • Ready for BOM management
); }; export default BOMUploadPage;