import React, { useState, useCallback, useEffect } from 'react'; import { Upload, FileText, AlertCircle, CheckCircle, Loader2, Database, TrendingUp, Settings, Eye, BarChart3, Filter } from 'lucide-react'; const FileUploadPage = () => { const [uploadStatus, setUploadStatus] = useState('idle'); // idle, uploading, analyzing, classifying, success, error const [uploadProgress, setUploadProgress] = useState(0); const [uploadResult, setUploadResult] = useState(null); const [dragActive, setDragActive] = useState(false); const [selectedProject, setSelectedProject] = useState(''); const [projects, setProjects] = useState([]); const [analysisStep, setAnalysisStep] = useState(''); const [classificationPreview, setClassificationPreview] = useState(null); // 프로젝트 목록 로드 useEffect(() => { fetchProjects(); }, []); const fetchProjects = async () => { try { const response = await fetch('http://localhost:8000/api/projects'); const data = await response.json(); setProjects(data); if (data.length > 0) { setSelectedProject(data[0].id.toString()); } } catch (error) { console.error('프로젝트 로드 실패:', error); } }; // 드래그 앤 드롭 핸들러 const handleDrag = useCallback((e) => { e.preventDefault(); e.stopPropagation(); if (e.type === "dragenter" || e.type === "dragover") { setDragActive(true); } else if (e.type === "dragleave") { setDragActive(false); } }, []); const handleDrop = useCallback((e) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFileUpload(e.dataTransfer.files[0]); } }, []); // 파일 업로드 및 4단계 자동 분류 처리 const handleFileUpload = async (file) => { if (!file) return; if (!selectedProject) { alert('프로젝트를 먼저 선택해주세요.'); return; } // 파일 타입 체크 (다양한 형식 지원) const allowedTypes = [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx 'application/vnd.ms-excel', // .xls 'application/vnd.ms-excel.sheet.macroEnabled.12', // .xlsm 'text/csv', 'text/plain' ]; if (!allowedTypes.includes(file.type) && !file.name.match(/\.(xlsx|xls|xlsm|csv|txt)$/i)) { alert('지원 형식: 엑셀(.xlsx, .xls, .xlsm), CSV, 텍스트 파일만 업로드 가능합니다.'); return; } setUploadStatus('uploading'); setUploadProgress(0); setAnalysisStep('파일 업로드 중...'); try { const formData = new FormData(); formData.append('file', file); formData.append('project_id', selectedProject); formData.append('revision', 'Rev.0'); formData.append('description', `${file.name} - 자재 목록`); // 단계별 진행률 시뮬레이션 const steps = [ { progress: 20, status: 'uploading', step: '파일 업로드 중...' }, { progress: 40, status: 'analyzing', step: '자동 구조 인식 중... (컬럼 분석)' }, { progress: 60, status: 'classifying', step: '4단계 자동 분류 진행 중...' }, { progress: 80, status: 'classifying', step: '재질 코드 및 사이즈 표준화...' }, { progress: 90, status: 'classifying', step: 'DB 저장 중...' } ]; let stepIndex = 0; const progressInterval = setInterval(() => { if (stepIndex < steps.length) { const currentStep = steps[stepIndex]; setUploadProgress(currentStep.progress); setUploadStatus(currentStep.status); setAnalysisStep(currentStep.step); stepIndex++; } else { clearInterval(progressInterval); } }, 800); const response = await fetch('http://localhost:8000/api/files/upload', { method: 'POST', body: formData, }); clearInterval(progressInterval); setUploadProgress(100); if (response.ok) { const result = await response.json(); setUploadResult(result); setUploadStatus('success'); setAnalysisStep('분류 완료! 결과를 확인하세요.'); // 분류 결과 미리보기 생성 setClassificationPreview({ totalItems: result.parsed_count || 0, categories: { '파이프': Math.floor((result.parsed_count || 0) * 0.4), '피팅류': Math.floor((result.parsed_count || 0) * 0.3), '볼트(너트)': Math.floor((result.parsed_count || 0) * 0.15), '밸브': Math.floor((result.parsed_count || 0) * 0.1), '계기류': Math.floor((result.parsed_count || 0) * 0.05) }, materials: ['A333-6 (저온용 배관)', 'A105 (단조 탄소강)', 'S355', 'SM490'], sizes: ['1"', '2"', '3"', '4"', '6"', '8"'] }); } else { throw new Error('업로드 실패'); } } catch (error) { console.error('Upload error:', error); setUploadStatus('error'); setAnalysisStep('처리 중 오류가 발생했습니다.'); setTimeout(() => { setUploadStatus('idle'); setUploadProgress(0); setAnalysisStep(''); }, 3000); } }; const handleFileInput = (e) => { if (e.target.files && e.target.files[0]) { handleFileUpload(e.target.files[0]); } }; const resetUpload = () => { setUploadStatus('idle'); setUploadProgress(0); setUploadResult(null); setAnalysisStep(''); setClassificationPreview(null); }; return (
{/* 헤더 */}

🏗️ 도면 자재 분석 시스템

Phase 1: 파일 분석 → 4단계 자동 분류 → 체계적 DB 저장

{/* Phase 1 핵심 프로세스 플로우 */}

🎯 Phase 1: 핵심 기능 처리 과정

다양한 형식 xlsx,xls,xlsm,csv
자동 구조 인식 컬럼 자동 판별
4단계 자동 분류 대분류→세부→재질→사이즈
체계적 저장 버전관리+이력추적
{/* 왼쪽: 업로드 영역 */}
{/* 프로젝트 선택 */}

📋 프로젝트 선택

{/* 업로드 영역 */}
{uploadStatus === 'idle' && ( <>

자재 목록 파일을 업로드하세요

드래그 앤 드롭하거나 클릭하여 파일을 선택하세요

지원 형식: Excel (.xlsx, .xls, .xlsm), CSV, 텍스트
)} {(uploadStatus === 'uploading' || uploadStatus === 'analyzing' || uploadStatus === 'classifying') && (

{analysisStep}

{uploadProgress}% 완료

)} {uploadStatus === 'success' && (

분석 및 분류 완료!

{analysisStep}

)} {uploadStatus === 'error' && (

업로드 실패

{analysisStep}

)}
{/* 오른쪽: 4단계 분류 시스템 설명 & 미리보기 */}
{/* 4단계 분류 시스템 */}

4단계 자동 분류 시스템

1단계: 대분류

파이프 / 피팅류 / 볼트(너트) / 밸브 / 계기류

2단계: 세부분류

90도 엘보우 / 용접목 플랜지 / SEAMLESS 파이프

3단계: 재질 인식

A333-6 (저온용 배관) / A105 (단조 탄소강) / S355 / SM490

4단계: 사이즈 표준화

6.0" → 6인치, 규격 통일 및 단위 자동 결정

{/* 분류 결과 미리보기 */} {classificationPreview && (

분류 결과 미리보기

{classificationPreview.totalItems}
총 자재 수

대분류별 분포

{Object.entries(classificationPreview.categories).map(([category, count]) => (
{category} {count}개
))}

인식된 재질

{classificationPreview.materials.map((material, index) => ( {material} ))}

표준화된 사이즈

{classificationPreview.sizes.map((size, index) => ( {size} ))}
)} {/* 데이터베이스 저장 정보 */}

체계적 DB 저장

프로젝트 단위 관리 (코드 체계)
버전 관리 (Rev.0, Rev.1, Rev.2)
파일 업로드 이력 추적
분류 결과 + 원본 정보 보존
수량 정보 세분화 저장
); }; export default FileUploadPage;