Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- 파이프 수량 계산 로직 수정 (단관 개수가 아닌 실제 길이 기반 계산) - UI 전면 개편 (DevonThink 스타일의 간결하고 세련된 디자인) - 자재별 그룹핑 로직 개선: * 플랜지: 동일 사양별 그룹핑, WN 스케줄 표시, ORIFICE 풀네임 표시 * 피팅: 상세 타입 표시 (니플 길이, 엘보 각도/연결, 티 타입, 리듀서 타입 등) * 밸브: 동일 사양별 그룹핑, 타입/연결방식/압력 표시 * 볼트: 크기/재질/길이별 그룹핑 (8SET → 개별 집계) * 가스켓: 동일 사양별 그룹핑, 재질/상세내역/두께 분리 표시 * UNKNOWN: 원본 설명 전체 표시, 동일 항목 그룹핑 - 전체 카테고리 버튼 제거 (표시 복잡도 감소) - 카테고리별 동적 컬럼 헤더 및 레이아웃 적용
300 lines
10 KiB
JavaScript
300 lines
10 KiB
JavaScript
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; |