Files
TK-BOM-Project/frontend/src/pages/BOMStatusPage.jsx
Hyungi Ahn 83b90ef05c
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
feat: 자재 관리 페이지 대규모 개선
- 파이프 수량 계산 로직 수정 (단관 개수가 아닌 실제 길이 기반 계산)
- UI 전면 개편 (DevonThink 스타일의 간결하고 세련된 디자인)
- 자재별 그룹핑 로직 개선:
  * 플랜지: 동일 사양별 그룹핑, WN 스케줄 표시, ORIFICE 풀네임 표시
  * 피팅: 상세 타입 표시 (니플 길이, 엘보 각도/연결, 티 타입, 리듀서 타입 등)
  * 밸브: 동일 사양별 그룹핑, 타입/연결방식/압력 표시
  * 볼트: 크기/재질/길이별 그룹핑 (8SET → 개별 집계)
  * 가스켓: 동일 사양별 그룹핑, 재질/상세내역/두께 분리 표시
  * UNKNOWN: 원본 설명 전체 표시, 동일 항목 그룹핑
- 전체 카테고리 버튼 제거 (표시 복잡도 감소)
- 카테고리별 동적 컬럼 헤더 및 레이아웃 적용
2025-09-09 09:24:45 +09:00

300 lines
10 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { api, fetchFiles, deleteFile as deleteFileApi } from '../api';
import BOMFileUpload from '../components/BOMFileUpload';
const BOMStatusPage = ({ jobNo, jobName, onNavigate, selectedProject }) => {
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('');
useEffect(() => {
if (jobNo) {
fetchFilesList();
}
}, [jobNo]);
const fetchFilesList = async () => {
try {
setLoading(true);
const response = await api.get('/files/', {
params: { job_no: jobNo }
});
// API가 배열로 직접 반환하는 경우
if (Array.isArray(response.data)) {
setFiles(response.data);
} else if (response.data && response.data.success) {
setFiles(response.data.files || []);
} else {
setFiles([]);
}
} catch (err) {
console.error('파일 목록 로딩 실패:', err);
setError('파일 목록을 불러오는데 실패했습니다.');
} finally {
setLoading(false);
}
};
// 파일 업로드
const handleUpload = async () => {
if (!selectedFile || !bomName.trim()) {
alert('파일과 BOM 이름을 모두 입력해주세요.');
return;
}
try {
setUploading(true);
const formData = new FormData();
formData.append('file', selectedFile);
formData.append('bom_name', bomName.trim());
formData.append('job_no', jobNo);
const response = await api.post('/files/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
if (response.data && response.data.success) {
alert('파일이 성공적으로 업로드되었습니다!');
setSelectedFile(null);
setBomName('');
await fetchFilesList(); // 목록 새로고침
} else {
throw new Error(response.data?.message || '업로드 실패');
}
} catch (err) {
console.error('파일 업로드 실패:', err);
setError('파일 업로드에 실패했습니다.');
} finally {
setUploading(false);
}
};
// 파일 삭제
const handleDelete = async (fileId) => {
if (!window.confirm('정말로 이 파일을 삭제하시겠습니까?')) {
return;
}
try {
await deleteFileApi(fileId);
await fetchFilesList(); // 목록 새로고침
} catch (err) {
console.error('파일 삭제 실패:', err);
setError('파일 삭제에 실패했습니다.');
}
};
// 자재 관리 페이지로 바로 이동 (단순화)
const handleViewMaterials = (file) => {
if (onNavigate) {
onNavigate('materials', {
file_id: file.id,
jobNo: file.job_no,
bomName: file.bom_name || file.original_filename,
revision: file.revision,
filename: file.original_filename
});
}
};
return (
<div style={{
padding: '32px',
background: '#f7fafc',
minHeight: '100vh'
}}>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
{/* 헤더 */}
<div style={{ marginBottom: '24px' }}>
<button
onClick={() => {
if (onNavigate) {
onNavigate('dashboard');
}
}}
style={{
padding: '8px 16px',
background: 'white',
border: '1px solid #e2e8f0',
borderRadius: '6px',
cursor: 'pointer',
marginBottom: '16px'
}}
>
메인으로 돌아가기
</button>
<h1 style={{
fontSize: '28px',
fontWeight: '700',
color: '#2d3748',
margin: '0 0 8px 0'
}}>
📊 BOM 관리 시스템
</h1>
{jobNo && jobName && (
<h2 style={{
fontSize: '20px',
fontWeight: '600',
color: '#4299e1',
margin: '0 0 24px 0'
}}>
{jobNo} - {jobName}
</h2>
)}
</div>
{/* 파일 업로드 컴포넌트 */}
<BOMFileUpload
bomName={bomName}
setBomName={setBomName}
selectedFile={selectedFile}
setSelectedFile={setSelectedFile}
uploading={uploading}
handleUpload={handleUpload}
error={error}
/>
{/* BOM 목록 */}
<h3 style={{
fontSize: '18px',
fontWeight: '600',
color: '#2d3748',
margin: '32px 0 16px 0'
}}>
업로드된 BOM 목록
</h3>
{loading ? (
<div style={{ textAlign: 'center', padding: '40px' }}>
로딩 ...
</div>
) : (
<div style={{
background: 'white',
borderRadius: '12px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.07)',
overflow: 'hidden'
}}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ background: '#f7fafc' }}>
<th style={{ padding: '16px', textAlign: 'left', borderBottom: '1px solid #e2e8f0' }}>BOM 이름</th>
<th style={{ padding: '16px', textAlign: 'left', borderBottom: '1px solid #e2e8f0' }}>파일명</th>
<th style={{ padding: '16px', textAlign: 'center', borderBottom: '1px solid #e2e8f0' }}>리비전</th>
<th style={{ padding: '16px', textAlign: 'center', borderBottom: '1px solid #e2e8f0' }}>자재 </th>
<th style={{ padding: '16px', textAlign: 'center', borderBottom: '1px solid #e2e8f0' }}>업로드 일시</th>
<th style={{ padding: '16px', textAlign: 'center', borderBottom: '1px solid #e2e8f0' }}>작업</th>
</tr>
</thead>
<tbody>
{files.map((file) => (
<tr key={file.id} style={{ borderBottom: '1px solid #e2e8f0' }}>
<td style={{ padding: '16px' }}>
<div style={{ fontWeight: '600', color: '#2d3748' }}>
{file.bom_name || file.original_filename}
</div>
<div style={{ fontSize: '12px', color: '#718096' }}>
{file.description || ''}
</div>
</td>
<td style={{ padding: '16px', fontSize: '14px', color: '#4a5568' }}>
{file.original_filename}
</td>
<td style={{ padding: '16px', textAlign: 'center' }}>
<span style={{
background: '#e6fffa',
color: '#065f46',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
fontWeight: '600'
}}>
{file.revision || 'Rev.0'}
</span>
</td>
<td style={{ padding: '16px', textAlign: 'center' }}>
{file.parsed_count || 0}
</td>
<td style={{ padding: '16px', textAlign: 'center', fontSize: '14px', color: '#718096' }}>
{new Date(file.created_at).toLocaleDateString()}
</td>
<td style={{ padding: '16px', textAlign: 'center' }}>
<div style={{ display: 'flex', gap: '8px', justifyContent: 'center' }}>
<button
onClick={() => handleViewMaterials(file)}
style={{
padding: '6px 12px',
background: '#4299e1',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '12px',
fontWeight: '600'
}}
>
📋 자재 보기
</button>
<button
onClick={() => {
// 리비전 업로드 기능 (추후 구현)
alert('리비전 업로드 기능은 준비 중입니다.');
}}
style={{
padding: '6px 12px',
background: 'white',
color: '#4299e1',
border: '1px solid #4299e1',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '12px',
fontWeight: '600'
}}
>
📝 리비전
</button>
<button
onClick={() => handleDelete(file.id)}
style={{
padding: '6px 12px',
background: '#f56565',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '12px',
fontWeight: '600'
}}
>
🗑 삭제
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
{files.length === 0 && (
<div style={{
padding: '40px',
textAlign: 'center',
color: '#718096'
}}>
업로드된 BOM 파일이 없습니다.
</div>
)}
</div>
)}
</div>
</div>
);
};
export default BOMStatusPage;