리비전 업로드 시 정확한 수량 차이분 계산 로직 구현
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- 기존 자재와 새 자재의 수량을 비교하여 증가분만 저장 - Rev.0: 엘보 10개, Rev.1: 엘보 12개 → Rev.1에는 2개만 저장 - 완전 신규 자재는 전체 수량 저장 - 수량 감소/동일한 자재는 저장하지 않음 - 리비전별 정확한 차이분 관리 구현
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { fetchMaterials } from '../api';
|
||||
import { exportMaterialsToExcel } from '../utils/excelExport';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { saveAs } from 'file-saver';
|
||||
import './NewMaterialsPage.css';
|
||||
|
||||
const NewMaterialsPage = ({
|
||||
@@ -16,11 +19,40 @@ const NewMaterialsPage = ({
|
||||
const [selectedCategory, setSelectedCategory] = useState('PIPE');
|
||||
const [selectedMaterials, setSelectedMaterials] = useState(new Set());
|
||||
const [viewMode, setViewMode] = useState('detailed'); // 'detailed' or 'simple'
|
||||
const [availableRevisions, setAvailableRevisions] = useState([]);
|
||||
const [currentRevision, setCurrentRevision] = useState(revision || 'Rev.0');
|
||||
|
||||
// 같은 BOM의 다른 리비전들 조회
|
||||
const loadAvailableRevisions = async () => {
|
||||
try {
|
||||
const response = await api.get('/files/', {
|
||||
params: { job_no: jobNo }
|
||||
});
|
||||
|
||||
const allFiles = Array.isArray(response.data) ? response.data : response.data?.files || [];
|
||||
const sameBomFiles = allFiles.filter(file =>
|
||||
(file.bom_name || file.original_filename) === bomName
|
||||
);
|
||||
|
||||
// 리비전별로 정렬 (최신순)
|
||||
sameBomFiles.sort((a, b) => {
|
||||
const revA = parseInt(a.revision?.replace('Rev.', '') || '0');
|
||||
const revB = parseInt(b.revision?.replace('Rev.', '') || '0');
|
||||
return revB - revA;
|
||||
});
|
||||
|
||||
setAvailableRevisions(sameBomFiles);
|
||||
console.log('📋 사용 가능한 리비전:', sameBomFiles);
|
||||
} catch (error) {
|
||||
console.error('리비전 목록 조회 실패:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 자재 데이터 로드
|
||||
useEffect(() => {
|
||||
if (fileId) {
|
||||
loadMaterials(fileId);
|
||||
loadAvailableRevisions();
|
||||
}
|
||||
}, [fileId]);
|
||||
|
||||
@@ -389,11 +421,134 @@ const NewMaterialsPage = ({
|
||||
setSelectedMaterials(newSelection);
|
||||
};
|
||||
|
||||
// 엑셀 내보내기
|
||||
// 엑셀 내보내기 - 화면에 표시된 그대로
|
||||
const exportToExcel = () => {
|
||||
const selectedData = materials.filter(m => selectedMaterials.has(m.id));
|
||||
console.log('📊 엑셀 내보내기:', selectedData.length, '개 항목');
|
||||
alert(`${selectedData.length}개 항목을 엑셀로 내보냅니다.`);
|
||||
try {
|
||||
// 내보낼 데이터 결정 (선택 항목 또는 현재 카테고리 전체)
|
||||
const dataToExport = selectedMaterials.size > 0
|
||||
? filteredMaterials.filter(m => selectedMaterials.has(m.id))
|
||||
: filteredMaterials;
|
||||
|
||||
console.log('📊 엑셀 내보내기:', dataToExport.length, '개 항목');
|
||||
|
||||
// 카테고리별 컬럼 구성
|
||||
const getExcelData = (material) => {
|
||||
const info = parseMaterialInfo(material);
|
||||
|
||||
if (selectedCategory === 'PIPE') {
|
||||
return {
|
||||
'종류': info.type,
|
||||
'타입': info.subtype,
|
||||
'크기': info.size,
|
||||
'스케줄': info.schedule,
|
||||
'재질': info.grade,
|
||||
'추가요구': '-',
|
||||
'사용자요구': '',
|
||||
'수량': `${info.quantity} ${info.unit}`,
|
||||
'상세': `단관 ${info.itemCount || 0}개 → ${info.totalLength || 0}mm`
|
||||
};
|
||||
} else if (selectedCategory === 'FLANGE' && info.isFlange) {
|
||||
return {
|
||||
'종류': info.type,
|
||||
'타입': info.subtype,
|
||||
'크기': info.size,
|
||||
'압력(파운드)': info.pressure,
|
||||
'스케줄': info.schedule,
|
||||
'재질': info.grade,
|
||||
'추가요구': '-',
|
||||
'사용자요구': '',
|
||||
'수량': `${info.quantity} ${info.unit}`
|
||||
};
|
||||
} else if (selectedCategory === 'FITTING' && info.isFitting) {
|
||||
return {
|
||||
'종류': info.type,
|
||||
'타입/상세': info.subtype,
|
||||
'크기': info.size,
|
||||
'압력': info.pressure,
|
||||
'스케줄': info.schedule,
|
||||
'재질': info.grade,
|
||||
'추가요구': '-',
|
||||
'사용자요구': '',
|
||||
'수량': `${info.quantity} ${info.unit}`
|
||||
};
|
||||
} else if (selectedCategory === 'VALVE' && info.isValve) {
|
||||
return {
|
||||
'타입': info.valveType,
|
||||
'연결방식': info.connectionType,
|
||||
'크기': info.size,
|
||||
'압력': info.pressure,
|
||||
'재질': info.grade,
|
||||
'추가요구': '-',
|
||||
'사용자요구': '',
|
||||
'수량': `${info.quantity} ${info.unit}`
|
||||
};
|
||||
} else if (selectedCategory === 'GASKET' && info.isGasket) {
|
||||
return {
|
||||
'종류': info.type,
|
||||
'타입': info.subtype,
|
||||
'크기': info.size,
|
||||
'압력': info.pressure,
|
||||
'재질': info.materialStructure,
|
||||
'상세내역': info.materialDetail,
|
||||
'두께': info.thickness,
|
||||
'추가요구': '-',
|
||||
'사용자요구': '',
|
||||
'수량': `${info.quantity} ${info.unit}`
|
||||
};
|
||||
} else if (selectedCategory === 'BOLT') {
|
||||
return {
|
||||
'종류': info.type,
|
||||
'타입': info.subtype,
|
||||
'크기': info.size,
|
||||
'스케줄': info.schedule,
|
||||
'재질': info.grade,
|
||||
'추가요구': '-',
|
||||
'사용자요구': '',
|
||||
'수량': `${info.quantity} ${info.unit}`
|
||||
};
|
||||
} else if (selectedCategory === 'UNKNOWN' && info.isUnknown) {
|
||||
return {
|
||||
'종류': info.type,
|
||||
'설명': info.description,
|
||||
'사용자요구': '',
|
||||
'수량': `${info.quantity} ${info.unit}`
|
||||
};
|
||||
} else {
|
||||
// 기본 형식
|
||||
return {
|
||||
'종류': info.type,
|
||||
'타입': info.subtype,
|
||||
'크기': info.size,
|
||||
'스케줄': info.schedule,
|
||||
'재질': info.grade,
|
||||
'추가요구': '-',
|
||||
'사용자요구': '',
|
||||
'수량': `${info.quantity} ${info.unit}`
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 엑셀 데이터 생성
|
||||
const excelData = dataToExport.map(material => getExcelData(material));
|
||||
|
||||
// 워크북 생성
|
||||
const ws = XLSX.utils.json_to_sheet(excelData);
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, selectedCategory);
|
||||
|
||||
// 파일명 생성
|
||||
const fileName = `${selectedCategory}_${jobNo || 'export'}_${new Date().toISOString().split('T')[0]}.xlsx`;
|
||||
|
||||
// 파일 저장
|
||||
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
|
||||
const data = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
saveAs(data, fileName);
|
||||
|
||||
console.log('✅ 엑셀 내보내기 성공');
|
||||
} catch (error) {
|
||||
console.error('❌ 엑셀 내보내기 실패:', error);
|
||||
alert('엑셀 내보내기에 실패했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
@@ -412,19 +567,58 @@ const NewMaterialsPage = ({
|
||||
{/* 헤더 */}
|
||||
<div className="materials-header">
|
||||
<div className="header-left">
|
||||
<button onClick={() => onNavigate('bom')} className="back-button">
|
||||
← BOM 업로드로 돌아가기
|
||||
<button
|
||||
onClick={() => {
|
||||
// 프로젝트 정보를 포함하여 BOM 페이지로 돌아가기
|
||||
const projectInfo = selectedProject || {
|
||||
job_no: jobNo,
|
||||
official_project_code: jobNo,
|
||||
job_name: bomName || filename
|
||||
};
|
||||
onNavigate('bom', {
|
||||
selectedProject: projectInfo
|
||||
});
|
||||
}}
|
||||
className="back-button-simple"
|
||||
title="BOM 업로드로 돌아가기"
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<h1>자재 목록</h1>
|
||||
{jobNo && (
|
||||
<span className="job-info">
|
||||
{jobNo} {revision && `(${revision})`}
|
||||
{jobNo} - {bomName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="header-center">
|
||||
{availableRevisions.length > 1 && (
|
||||
<div className="revision-selector">
|
||||
<label>리비전: </label>
|
||||
<select
|
||||
value={currentRevision}
|
||||
onChange={(e) => {
|
||||
const selectedRev = e.target.value;
|
||||
const selectedFile = availableRevisions.find(f => f.revision === selectedRev);
|
||||
if (selectedFile) {
|
||||
setCurrentRevision(selectedRev);
|
||||
loadMaterials(selectedFile.id);
|
||||
}
|
||||
}}
|
||||
className="revision-dropdown"
|
||||
>
|
||||
{availableRevisions.map(file => (
|
||||
<option key={file.id} value={file.revision}>
|
||||
{file.revision || 'Rev.0'} ({file.parsed_count || 0}개 자재)
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="header-right">
|
||||
<span className="material-count">
|
||||
총 {materials.length}개 자재
|
||||
총 {materials.length}개 자재 ({currentRevision})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -457,9 +651,10 @@ const NewMaterialsPage = ({
|
||||
<button
|
||||
onClick={exportToExcel}
|
||||
className="export-btn"
|
||||
disabled={selectedMaterials.size === 0}
|
||||
>
|
||||
엑셀 내보내기 ({selectedMaterials.size})
|
||||
{selectedMaterials.size > 0
|
||||
? `선택 항목 엑셀 내보내기 (${selectedMaterials.size})`
|
||||
: '전체 엑셀 내보내기'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user