feat: 가스켓 카테고리 개선 및 엑셀 내보내기 최적화
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

- 가스켓 카테고리 정렬 오류 수정 (FilterableHeader props 추가)
- 가스켓 엑셀 내보내기 개선:
  * 품목명을 BOM 페이지 타입과 동일하게 표시 (SPIRAL WOUND GASKET 등)
  * 재질을 재질1/재질2로 분리 (SS304/GRAPHITE → 재질1: SS304/GRAPHITE, 재질2: /SS304/SS304)
  * originalDescription에서 4개 재질 패턴 우선 추출
  * P열 납기일 규칙 준수
- 프로젝트 비활성화 기능 수정 (localStorage 영구 저장)
- 모든 카테고리 정렬 함수 안전성 강화
This commit is contained in:
hyungi
2025-10-16 15:51:24 +09:00
parent 379af6b1e3
commit a27213e0e5
7 changed files with 430 additions and 139 deletions

View File

@@ -422,37 +422,43 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
}
}
} else if (category === 'GASKET') {
// 가스켓 상세 타입 표시
// BOM 페이지의 타입을 따라가도록 - 프론트엔드 parseGasketInfo와 동일한 로직
const gasketDetails = material.gasket_details || {};
const gasketType = gasketDetails.gasket_type || '';
const gasketSubtype = gasketDetails.gasket_subtype || '';
if (gasketType === 'SPIRAL_WOUND') {
itemName = '스파이럴 워운드 가스켓';
} else if (gasketType === 'RING_JOINT') {
itemName = '링 조인트 가스켓';
} else if (gasketType === 'FULL_FACE') {
itemName = '풀 페이스 가스켓';
} else if (gasketType === 'RAISED_FACE') {
itemName = '레이즈드 페이스 가스켓';
} else if (gasketSubtype && gasketSubtype !== gasketType) {
itemName = gasketSubtype;
} else if (gasketType) {
itemName = gasketType;
// 가스켓 타입 매핑 (프론트엔드와 동일)
const gasketTypeMap = {
'SWG': 'SPIRAL WOUND GASKET',
'SPIRAL_WOUND': 'SPIRAL WOUND GASKET',
'RTJ': 'RING TYPE JOINT GASKET',
'RING_JOINT': 'RING TYPE JOINT GASKET',
'FF': 'FULL FACE GASKET',
'FULL_FACE': 'FULL FACE GASKET',
'RF': 'RAISED FACE GASKET',
'RAISED_FACE': 'RAISED FACE GASKET'
};
// Description에서 가스켓 타입 추출
const descUpper = cleanDescription.toUpperCase();
let extractedType = '';
if (descUpper.includes('SWG') || descUpper.includes('SPIRAL')) {
extractedType = 'SWG';
} else if (descUpper.includes('RTJ') || descUpper.includes('RING')) {
extractedType = 'RTJ';
} else if (descUpper.includes('FF') || descUpper.includes('FULL FACE')) {
extractedType = 'FF';
} else if (descUpper.includes('RF') || descUpper.includes('RAISED')) {
extractedType = 'RF';
}
// 풀네임으로 변환
if (gasketType && gasketTypeMap[gasketType]) {
itemName = gasketTypeMap[gasketType];
} else if (extractedType && gasketTypeMap[extractedType]) {
itemName = gasketTypeMap[extractedType];
} else {
// gasket_details가 없으면 description에서 추출
const desc = cleanDescription.toUpperCase();
if (desc.includes('SWG') || desc.includes('SPIRAL')) {
itemName = '스파이럴 워운드 가스켓';
} else if (desc.includes('RTJ') || desc.includes('RING')) {
itemName = '링 조인트 가스켓';
} else if (desc.includes('FF') || desc.includes('FULL FACE')) {
itemName = '풀 페이스 가스켓';
} else if (desc.includes('RF') || desc.includes('RAISED')) {
itemName = '레이즈드 페이스 가스켓';
} else {
itemName = '가스켓';
}
itemName = 'GASKET';
}
} else if (category === 'BOLT') {
// 볼트 상세 타입 표시
@@ -660,28 +666,33 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
detailInfo = surfaceTreatments.join(', ') || '-';
} else if (category === 'GASKET') {
// 실제 재질 구성 정보 (SS304/GRAPHITE/SS304/SS304)
if (material.gasket_details) {
const materialType = material.gasket_details.material_type || '';
const fillerMaterial = material.gasket_details.filler_material || '';
if (materialType && fillerMaterial) {
// DB에서 가져온 정보로 구성
gasketMaterial = `${materialType}/${fillerMaterial}`;
}
}
// gasket_details가 없거나 불완전하면 description에서 추출
if (!gasketMaterial) {
// SS304/GRAPHITE/SS304/SS304 패턴 추출
const fullMaterialMatch = cleanDescription.match(/SS304\/GRAPHITE\/SS304\/SS304/i);
if (fullMaterialMatch) {
gasketMaterial = 'SS304/GRAPHITE/SS304/SS304';
// 실제 재질 구성 정보 - description에서 우선 추출
// SS304/GRAPHITE/SS304/SS304 패턴 먼저 찾기
const fullMaterialMatch = cleanDescription.match(/SS304\/GRAPHITE\/SS304\/SS304/i);
if (fullMaterialMatch) {
gasketMaterial = 'SS304/GRAPHITE/SS304/SS304';
} else {
// 4개 재질 패턴 (다양한 재질 조합)
const fourMaterialMatch = cleanDescription.match(/(SS\d+|304|316|CS)\/(GRAPHITE|PTFE|VITON|EPDM)\/(SS\d+|304|316|CS)\/(SS\d+|304|316|CS)/i);
if (fourMaterialMatch) {
gasketMaterial = `${fourMaterialMatch[1]}/${fourMaterialMatch[2]}/${fourMaterialMatch[3]}/${fourMaterialMatch[4]}`;
} else {
// 간단한 패턴 (SS304/GRAPHITE)
const simpleMaterialMatch = cleanDescription.match(/(SS\d+|304|316)\s*[\/+]\s*(GRAPHITE|PTFE|VITON|EPDM)/i);
if (simpleMaterialMatch) {
gasketMaterial = `${simpleMaterialMatch[1]}/${simpleMaterialMatch[2]}`;
// DB에서 가져온 정보로 구성 (fallback)
if (material.gasket_details) {
const materialType = material.gasket_details.material_type || '';
const fillerMaterial = material.gasket_details.filler_material || '';
if (materialType && fillerMaterial) {
gasketMaterial = `${materialType}/${fillerMaterial}`;
}
}
// 마지막으로 간단한 패턴
if (!gasketMaterial) {
const simpleMaterialMatch = cleanDescription.match(/(SS\d+|304|316)\s*[\/+]\s*(GRAPHITE|PTFE|VITON|EPDM)/i);
if (simpleMaterialMatch) {
gasketMaterial = `${simpleMaterialMatch[1]}/${simpleMaterialMatch[2]}`;
}
}
}
}
@@ -794,17 +805,41 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
base['관리항목4'] = ''; // N열
base['관리항목5'] = ''; // O열
} else if (category === 'GASKET') {
// 가스켓 전용 컬럼 (F~O) - 타입 제거, 품목명에 포함됨
// 가스켓 전용 컬럼 (F~O) - 재질을 2개로 분리
// 재질 분리 로직: SS304/GRAPHITE/SS304/SS304 → 재질1: SS304/GRAPHITE, 재질2: /SS304/SS304
let material1 = '-';
let material2 = '-';
if (gasketMaterial && gasketMaterial.includes('/')) {
const materialParts = gasketMaterial.split('/');
if (materialParts.length >= 4) {
// 4개 재질인 경우: SS304/GRAPHITE/SS304/SS304
material1 = `${materialParts[0]}/${materialParts[1]}`;
material2 = `/${materialParts[2]}/${materialParts[3]}`;
} else if (materialParts.length === 3) {
// 3개 재질인 경우: SS304/GRAPHITE/SS304
material1 = `${materialParts[0]}/${materialParts[1]}`;
material2 = `/${materialParts[2]}`;
} else if (materialParts.length === 2) {
// 2개 재질인 경우: SS304/GRAPHITE
material1 = gasketMaterial;
material2 = '-';
}
} else if (gasketMaterial) {
material1 = gasketMaterial;
}
base['크기'] = material.size_spec || '-'; // F열
base['압력등급'] = pressure; // G열
base['구조'] = grade; // H열: H/F/I/O, SWG 등 (타입 정보 제거)
base['재질'] = gasketMaterial || '-'; // I열: SS304/GRAPHITE/SS304/SS304
base['두께'] = gasketThickness || '-'; // J열: 4.5mm
base['사용자요구'] = material.user_requirement || ''; // K열
base['관리항목1'] = ''; // L열
base['관리항목2'] = ''; // M열
base['관리항목3'] = ''; // N열
base['관리항목4'] = ''; // O열
base['구조'] = grade; // H열: H/F/I/O, SWG 등
base['재질1'] = material1; // I열: SS304/GRAPHITE
base['재질2'] = material2; // J열: SS304/SS304
base['두께'] = gasketThickness || '-'; // K열: 4.5mm
base['사용자요구'] = material.user_requirement || ''; // L열
base['관리항목1'] = ''; // M열
base['관리항목2'] = ''; // N열
base['관리항목3'] = ''; // O열
} else if (category === 'BOLT') {
// 볼트 전용 컬럼 (F~O) - 타입 제거, 품목명에 포함됨
base['크기'] = material.size_spec || '-'; // F열
@@ -1077,7 +1112,7 @@ export const createExcelBlob = async (materials, filename, options = {}) => {
'FITTING': ['크기', '압력등급', '스케줄', '재질', '사용자요구', '추가요청사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'FLANGE': ['크기', '페이싱', '압력등급', '스케줄', '재질', '추가요구사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'VALVE': ['크기', '압력등급', '재질', '상세내역', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'GASKET': ['크기', '압력등급', '구조', '재질', '두께', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'GASKET': ['크기', '압력등급', '구조', '재질1', '재질2', '두께', '사용자요구', '관리항목1', '관리항목2', '관리항목3'],
'BOLT': ['크기', '압력등급', '길이', '재질', '추가요구', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'SUPPORT': ['크기', '압력등급', '재질', '상세내역', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
};