Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
🎯 주요 변경사항: - 통합 BOM 페이지 (UnifiedBOMPage) 신규 개발 - 탭 구조로 업로드 → 파일 관리 → 자재 관리 워크플로우 개선 - 컴포넌트 분리로 스파게티 코드 방지 📤 업로드 탭 (BOMUploadTab): - 드래그 앤 드롭 파일 업로드 - 파일 검증 및 진행률 표시 - 업로드 완료 후 자동 Files 탭 이동 📊 파일 관리 탭 (BOMFilesTab): - 프로젝트별 BOM 파일 목록 조회 - 리비전 히스토리 표시 - BOM 선택 후 자동 Materials 탭 이동 📋 자재 관리 탭 (BOMMaterialsTab): - 기존 BOMManagementPage 래핑 - 선택된 BOM의 자재 분류 및 관리 🔧 백엔드 API 개선: - /files/project/{project_code} 엔드포인트 추가 - 한글 프로젝트 코드 URL 인코딩 지원 - 프로젝트별 파일 조회 기능 구현 🎨 대시보드 개선: - 3개 BOM 카드를 1개 통합 카드로 변경 - 기능 미리보기 태그 추가 - 더 직관적인 네비게이션 📁 코드 구조 개선: - 기존 페이지들을 _deprecated 폴더로 이동 - 탭별 컴포넌트 분리 (components/bom/tabs/) - PAGES_GUIDE.md 업데이트 ✨ 사용자 경험 개선: - 자연스러운 워크플로우 (업로드 → 선택 → 관리) - 탭 간 상태 공유 및 자동 네비게이션 - 통합된 인터페이스에서 모든 BOM 작업 처리
351 lines
12 KiB
JavaScript
351 lines
12 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import api from '../api';
|
|
|
|
const BOMRevisionPage = ({
|
|
onNavigate,
|
|
selectedProject,
|
|
user
|
|
}) => {
|
|
const [bomFiles, setBomFiles] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState('');
|
|
|
|
// BOM 파일 목록 로드 (기본 구조만)
|
|
useEffect(() => {
|
|
const loadBOMFiles = async () => {
|
|
if (!selectedProject) return;
|
|
|
|
try {
|
|
setLoading(true);
|
|
// TODO: 실제 API 구현 필요
|
|
// const response = await api.get(`/files/project/${selectedProject.job_no}`);
|
|
// setBomFiles(response.data);
|
|
|
|
// 임시 데이터
|
|
setBomFiles([
|
|
{
|
|
id: 1,
|
|
bom_name: 'Main Process BOM',
|
|
revisions: ['Rev.0', 'Rev.1', 'Rev.2'],
|
|
latest_revision: 'Rev.2',
|
|
upload_date: '2024-10-17',
|
|
status: 'Active'
|
|
},
|
|
{
|
|
id: 2,
|
|
bom_name: 'Utility BOM',
|
|
revisions: ['Rev.0', 'Rev.1'],
|
|
latest_revision: 'Rev.1',
|
|
upload_date: '2024-10-16',
|
|
status: 'Active'
|
|
}
|
|
]);
|
|
} catch (err) {
|
|
console.error('BOM 파일 로드 실패:', err);
|
|
setError('BOM 파일을 불러오는데 실패했습니다.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
loadBOMFiles();
|
|
}, [selectedProject]);
|
|
|
|
return (
|
|
<div style={{
|
|
padding: '40px',
|
|
background: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)',
|
|
minHeight: '100vh'
|
|
}}>
|
|
{/* 헤더 */}
|
|
<div style={{
|
|
background: 'rgba(255, 255, 255, 0.95)',
|
|
backdropFilter: 'blur(10px)',
|
|
borderRadius: '20px',
|
|
padding: '32px',
|
|
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
|
|
border: '1px solid rgba(255, 255, 255, 0.2)',
|
|
marginBottom: '40px'
|
|
}}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
|
<div>
|
|
<h1 style={{
|
|
fontSize: '28px',
|
|
fontWeight: '700',
|
|
color: '#0f172a',
|
|
margin: '0 0 8px 0',
|
|
letterSpacing: '-0.025em'
|
|
}}>
|
|
BOM Revision Management
|
|
</h1>
|
|
<p style={{
|
|
fontSize: '16px',
|
|
color: '#64748b',
|
|
margin: 0,
|
|
fontWeight: '400'
|
|
}}>
|
|
Project: {selectedProject?.job_name || 'No Project Selected'}
|
|
</p>
|
|
</div>
|
|
<div style={{ display: 'flex', gap: '12px' }}>
|
|
<button
|
|
onClick={() => onNavigate('bom-upload')}
|
|
style={{
|
|
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
|
|
color: 'white',
|
|
border: 'none',
|
|
borderRadius: '12px',
|
|
padding: '12px 20px',
|
|
cursor: 'pointer',
|
|
fontSize: '14px',
|
|
fontWeight: '600',
|
|
transition: 'all 0.2s ease'
|
|
}}
|
|
>
|
|
New Upload
|
|
</button>
|
|
<button
|
|
onClick={() => onNavigate('dashboard')}
|
|
style={{
|
|
background: 'white',
|
|
color: '#6b7280',
|
|
border: '1px solid #d1d5db',
|
|
borderRadius: '12px',
|
|
padding: '12px 20px',
|
|
cursor: 'pointer',
|
|
fontSize: '14px',
|
|
fontWeight: '600',
|
|
transition: 'all 0.2s ease'
|
|
}}
|
|
>
|
|
Back to Dashboard
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 프로젝트 정보 */}
|
|
<div style={{
|
|
display: 'grid',
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
|
gap: '20px'
|
|
}}>
|
|
<div style={{
|
|
background: 'linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%)',
|
|
padding: '20px',
|
|
borderRadius: '12px',
|
|
textAlign: 'center'
|
|
}}>
|
|
<div style={{ fontSize: '18px', fontWeight: '600', color: '#1d4ed8', marginBottom: '4px' }}>
|
|
{selectedProject?.official_project_code || selectedProject?.job_no || 'N/A'}
|
|
</div>
|
|
<div style={{ fontSize: '14px', color: '#1d4ed8', fontWeight: '500' }}>
|
|
Project Code
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{
|
|
background: 'linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)',
|
|
padding: '20px',
|
|
borderRadius: '12px',
|
|
textAlign: 'center'
|
|
}}>
|
|
<div style={{ fontSize: '18px', fontWeight: '600', color: '#d97706', marginBottom: '4px' }}>
|
|
{bomFiles.length}
|
|
</div>
|
|
<div style={{ fontSize: '14px', color: '#d97706', fontWeight: '500' }}>
|
|
BOM Files
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{
|
|
background: 'linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%)',
|
|
padding: '20px',
|
|
borderRadius: '12px',
|
|
textAlign: 'center'
|
|
}}>
|
|
<div style={{ fontSize: '18px', fontWeight: '600', color: '#7c3aed', marginBottom: '4px' }}>
|
|
{bomFiles.reduce((total, bom) => total + bom.revisions.length, 0)}
|
|
</div>
|
|
<div style={{ fontSize: '14px', color: '#7c3aed', fontWeight: '500' }}>
|
|
Total Revisions
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 개발 예정 배너 */}
|
|
<div style={{
|
|
background: 'rgba(255, 255, 255, 0.95)',
|
|
backdropFilter: 'blur(10px)',
|
|
borderRadius: '20px',
|
|
padding: '60px 40px',
|
|
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
|
|
border: '1px solid rgba(255, 255, 255, 0.2)',
|
|
textAlign: 'center',
|
|
marginBottom: '40px'
|
|
}}>
|
|
<div style={{ fontSize: '64px', marginBottom: '24px' }}>🚧</div>
|
|
<h2 style={{
|
|
fontSize: '32px',
|
|
fontWeight: '700',
|
|
color: '#0f172a',
|
|
margin: '0 0 16px 0'
|
|
}}>
|
|
Advanced Revision Management
|
|
</h2>
|
|
<p style={{
|
|
fontSize: '18px',
|
|
color: '#64748b',
|
|
margin: '0 0 32px 0',
|
|
maxWidth: '600px',
|
|
marginLeft: 'auto',
|
|
marginRight: 'auto',
|
|
lineHeight: '1.6'
|
|
}}>
|
|
고급 리비전 관리 기능이 개발 중입니다. 업로드 기능 완료 후 본격적인 개발이 시작됩니다.
|
|
</p>
|
|
|
|
{/* 예정 기능 미리보기 */}
|
|
<div style={{
|
|
display: 'grid',
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
|
|
gap: '24px',
|
|
maxWidth: '800px',
|
|
margin: '0 auto'
|
|
}}>
|
|
<div style={{
|
|
background: 'linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)',
|
|
padding: '24px',
|
|
borderRadius: '16px',
|
|
textAlign: 'center'
|
|
}}>
|
|
<div style={{ fontSize: '32px', marginBottom: '12px' }}>📊</div>
|
|
<h3 style={{ fontSize: '16px', fontWeight: '600', color: '#92400e', margin: '0 0 8px 0' }}>
|
|
Revision Timeline
|
|
</h3>
|
|
<p style={{ fontSize: '14px', color: '#92400e', margin: 0 }}>
|
|
시각적 리비전 히스토리
|
|
</p>
|
|
</div>
|
|
|
|
<div style={{
|
|
background: 'linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%)',
|
|
padding: '24px',
|
|
borderRadius: '16px',
|
|
textAlign: 'center'
|
|
}}>
|
|
<div style={{ fontSize: '32px', marginBottom: '12px' }}>🔍</div>
|
|
<h3 style={{ fontSize: '16px', fontWeight: '600', color: '#1d4ed8', margin: '0 0 8px 0' }}>
|
|
Diff Comparison
|
|
</h3>
|
|
<p style={{ fontSize: '14px', color: '#1d4ed8', margin: 0 }}>
|
|
리비전 간 변경사항 비교
|
|
</p>
|
|
</div>
|
|
|
|
<div style={{
|
|
background: 'linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%)',
|
|
padding: '24px',
|
|
borderRadius: '16px',
|
|
textAlign: 'center'
|
|
}}>
|
|
<div style={{ fontSize: '32px', marginBottom: '12px' }}>⏪</div>
|
|
<h3 style={{ fontSize: '16px', fontWeight: '600', color: '#059669', margin: '0 0 8px 0' }}>
|
|
Rollback System
|
|
</h3>
|
|
<p style={{ fontSize: '14px', color: '#059669', margin: 0 }}>
|
|
이전 리비전으로 롤백
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 임시 BOM 파일 목록 (기본 구조) */}
|
|
{bomFiles.length > 0 && (
|
|
<div style={{
|
|
background: 'rgba(255, 255, 255, 0.95)',
|
|
backdropFilter: 'blur(10px)',
|
|
borderRadius: '20px',
|
|
padding: '32px',
|
|
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
|
|
border: '1px solid rgba(255, 255, 255, 0.2)'
|
|
}}>
|
|
<h3 style={{
|
|
fontSize: '20px',
|
|
fontWeight: '600',
|
|
color: '#374151',
|
|
marginBottom: '24px'
|
|
}}>
|
|
Current BOM Files (Preview)
|
|
</h3>
|
|
|
|
<div style={{ display: 'grid', gap: '16px' }}>
|
|
{bomFiles.map((bom) => (
|
|
<div key={bom.id} style={{
|
|
background: 'white',
|
|
border: '1px solid #e5e7eb',
|
|
borderRadius: '12px',
|
|
padding: '20px',
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center'
|
|
}}>
|
|
<div>
|
|
<h4 style={{
|
|
fontSize: '16px',
|
|
fontWeight: '600',
|
|
color: '#374151',
|
|
margin: '0 0 8px 0'
|
|
}}>
|
|
{bom.bom_name}
|
|
</h4>
|
|
<div style={{ display: 'flex', gap: '16px', fontSize: '14px', color: '#6b7280' }}>
|
|
<span>Latest: {bom.latest_revision}</span>
|
|
<span>Revisions: {bom.revisions.length}</span>
|
|
<span>Uploaded: {bom.upload_date}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', gap: '8px' }}>
|
|
<button
|
|
onClick={() => onNavigate('bom-management', { bomId: bom.id })}
|
|
style={{
|
|
background: 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)',
|
|
color: 'white',
|
|
border: 'none',
|
|
borderRadius: '8px',
|
|
padding: '8px 16px',
|
|
cursor: 'pointer',
|
|
fontSize: '12px',
|
|
fontWeight: '500'
|
|
}}
|
|
>
|
|
Manage BOM
|
|
</button>
|
|
<button
|
|
disabled
|
|
style={{
|
|
background: '#f3f4f6',
|
|
color: '#9ca3af',
|
|
border: 'none',
|
|
borderRadius: '8px',
|
|
padding: '8px 16px',
|
|
cursor: 'not-allowed',
|
|
fontSize: '12px',
|
|
fontWeight: '500'
|
|
}}
|
|
>
|
|
View History (Soon)
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default BOMRevisionPage;
|