import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Box,
Card,
CardContent,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Button,
Alert,
CircularProgress,
Chip,
Divider,
FormControl,
InputLabel,
Select,
MenuItem,
Grid
} from '@mui/material';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import ShoppingCart from '@mui/icons-material/ShoppingCart';
import { Compare as CompareIcon, Download } from '@mui/icons-material';
import { api, fetchFiles } from '../api';
import { exportMaterialsToExcel } from '../utils/excelExport';
const MaterialsPage = () => {
const [materials, setMaterials] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [fileId, setFileId] = useState(null);
const [fileName, setFileName] = useState('');
const [jobNo, setJobNo] = useState('');
const [bomName, setBomName] = useState('');
const [currentRevision, setCurrentRevision] = useState('');
const [availableRevisions, setAvailableRevisions] = useState([]);
const navigate = useNavigate();
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const id = urlParams.get('file_id');
const name = urlParams.get('filename') || '';
const job_no = urlParams.get('job_no') || '';
if (id && job_no) {
setFileId(id);
setFileName(decodeURIComponent(name));
setJobNo(job_no);
loadMaterials(id);
loadAvailableRevisions(job_no, name);
} else {
setLoading(false);
setError('파일 ID 또는 Job No가 지정되지 않았습니다.');
}
}, []);
// 같은 BOM의 다른 리비전들 로드
const loadAvailableRevisions = async (job_no, filename) => {
try {
const response = await fetchFiles({ job_no });
if (Array.isArray(response.data)) {
// 같은 BOM 이름의 파일들만 필터링
const sameNameFiles = response.data.filter(file =>
file.original_filename === filename ||
file.bom_name === filename ||
file.filename === filename
);
// 리비전 순으로 정렬 (최신부터)
const sortedFiles = sameNameFiles.sort((a, b) => {
const revA = parseInt(a.revision?.replace('Rev.', '') || '0');
const revB = parseInt(b.revision?.replace('Rev.', '') || '0');
return revB - revA;
});
setAvailableRevisions(sortedFiles);
// 현재 파일 정보 설정
const currentFile = sortedFiles.find(file => file.id === parseInt(fileId));
if (currentFile) {
setCurrentRevision(currentFile.revision || 'Rev.0');
setBomName(currentFile.bom_name || currentFile.original_filename);
}
}
} catch (err) {
console.error('리비전 목록 로드 실패:', err);
}
};
const loadMaterials = async (id) => {
try {
setLoading(true);
const response = await api.get('/files/materials', {
params: { file_id: parseInt(id), limit: 10000 }
});
if (response.data && response.data.materials) {
setMaterials(response.data.materials);
} else {
setMaterials([]);
}
setError(null);
} catch (err) {
setError('자재 정보를 불러오는데 실패했습니다.');
console.error('자재 로딩 에러:', err);
} finally {
setLoading(false);
}
};
// 자재 사양서 생성 로직
const generateMaterialSpecs = (materials) => {
const specs = {};
materials.forEach(material => {
const category = material.classified_category || 'UNKNOWN';
let specKey = '';
let specData = {};
if (category === 'PIPE') {
// PIPE: 재질 + 외경 + 스케줄 + 제작방식
// 재질 정보 - pipe_details에서 이미 정제된 것만 사용
const material_spec = material.pipe_details?.material_spec || material.material_grade || '';
const outer_diameter = material.main_nom || material.pipe_details?.outer_diameter || '';
const schedule = material.pipe_details?.schedule || '';
const manufacturing = material.pipe_details?.manufacturing_method || '';
specKey = `${category}|${material_spec}|${outer_diameter}|${schedule}|${manufacturing}`;
specData = {
category: 'PIPE',
material_spec,
outer_diameter,
schedule,
manufacturing_method: manufacturing,
unit: 'mm',
isLength: true
};
} else if (category === 'FITTING') {
// FITTING: 타입 + 서브타입 + 연결방식 + 압력등급 + 사이즈 + 재질
const material_spec = material.fitting_details?.material_spec || material.material_grade || '';
const main_nom = material.main_nom || '';
const red_nom = material.red_nom || '';
const size_display = red_nom ? `${main_nom} x ${red_nom}` : main_nom;
const fitting_type = material.fitting_details?.fitting_type || 'UNKNOWN';
const fitting_subtype = material.fitting_details?.fitting_subtype || '';
const connection_method = material.fitting_details?.connection_method || '';
const pressure_rating = material.fitting_details?.pressure_rating || '';
// 전체 피팅 스펙 생성 - 중복 제거 (OLET 특별 처리)
const spec_parts = [];
// OLET 계열 특별 처리
if (fitting_type === 'OLET' && fitting_subtype && fitting_subtype !== 'UNKNOWN') {
// SOCKOLET, WELDOLET 등 서브타입만 표시 (OLET 생략)
spec_parts.push(fitting_subtype);
// OLET 계열은 연결방식이 서브타입에 이미 내포됨
// SOCKOLET = SOCKET_WELD, WELDOLET = BUTT_WELD 등
// 따라서 connection_method 생략
} else if (fitting_type === 'NIPPLE') {
// NIPPLE 특별 처리 - 스케줄 + 길이 정보 포함
spec_parts.push(fitting_type);
// 서브타입 (CLOSE, SHORT, LONG 등)
if (fitting_subtype && fitting_subtype !== 'UNKNOWN' && fitting_subtype !== fitting_type) {
spec_parts.push(fitting_subtype);
}
// NIPPLE 스케줄 정보 추가 (fitting_details에서 가져옴)
const nipple_schedule = material.fitting_details?.schedule;
if (nipple_schedule && nipple_schedule !== 'UNKNOWN') {
spec_parts.push(nipple_schedule);
}
// 연결방식
if (connection_method && connection_method !== 'UNKNOWN' &&
!spec_parts.some(part => part.includes(connection_method))) {
spec_parts.push(connection_method);
}
// NIPPLE 길이 정보 추가 (fitting_details에서 가져옴)
const length_mm = material.fitting_details?.length_mm;
if (length_mm && length_mm > 0) {
if (length_mm >= 1000) {
spec_parts.push(`${(length_mm / 1000).toFixed(2)}m`);
} else {
spec_parts.push(`${length_mm}mm`);
}
}
} else {
// 일반 피팅 처리
// 기본 타입 (CAP, TEE, ELBOW 등)
if (fitting_type && fitting_type !== 'UNKNOWN') {
spec_parts.push(fitting_type);
}
// 서브타입 (CONCENTRIC, HEXAGON 등) - 단, 타입과 중복되지 않을 때만
if (fitting_subtype && fitting_subtype !== 'UNKNOWN' && fitting_subtype !== fitting_type) {
spec_parts.push(fitting_subtype);
}
// 연결방식 (THREADED, NPT 등) - 단, 이미 포함되지 않았을 때만
if (connection_method && connection_method !== 'UNKNOWN' &&
!spec_parts.some(part => part.includes(connection_method))) {
spec_parts.push(connection_method);
}
}
// 압력등급 (3000LB, 6000LB 등) - 모든 경우에 표시
if (pressure_rating && pressure_rating !== 'UNKNOWN' &&
!spec_parts.some(part => part.includes(pressure_rating))) {
spec_parts.push(pressure_rating);
}
const full_fitting_spec = spec_parts.join(', ');
specKey = `${category}|${full_fitting_spec}|${material_spec}|${size_display}`;
specData = {
category: 'FITTING',
fitting_type,
fitting_subtype,
connection_method,
pressure_rating,
full_fitting_spec,
material_spec,
size_display,
main_nom,
red_nom,
unit: 'EA',
isLength: false
};
} else if (category === 'FLANGE') {
// FLANGE: 타입 + 압력등급 + 면가공 + 재질
const material_spec = material.flange_details?.material_spec || material.material_grade || '';
const main_nom = material.main_nom || '';
const flange_type = material.flange_details?.flange_type || 'UNKNOWN';
const pressure_rating = material.flange_details?.pressure_rating || '';
const facing_type = material.flange_details?.facing_type || '';
// 플랜지 스펙 생성
const flange_spec_parts = [];
// 플랜지 타입 (WN, BL, SO 등)
if (flange_type && flange_type !== 'UNKNOWN') {
flange_spec_parts.push(flange_type);
}
// 면 가공 (RF, FF, RTJ 등)
if (facing_type && facing_type !== 'UNKNOWN') {
flange_spec_parts.push(facing_type);
}
// 압력등급 (150LB, 300LB 등)
if (pressure_rating && pressure_rating !== 'UNKNOWN') {
flange_spec_parts.push(pressure_rating);
}
const full_flange_spec = flange_spec_parts.join(', ');
specKey = `${category}|${full_flange_spec}|${material_spec}|${main_nom}`;
specData = {
category: 'FLANGE',
flange_type,
pressure_rating,
facing_type,
full_flange_spec,
material_spec,
size_display: main_nom,
main_nom,
unit: 'EA',
isLength: false
};
} else if (category === 'GASKET') {
// GASKET: 타입 + 소재 + 압력등급 + 사이즈
const main_nom = material.main_nom || '';
const gasket_type = material.gasket_details?.gasket_type || 'UNKNOWN';
const material_type = material.gasket_details?.material_type || 'UNKNOWN';
const pressure_rating = material.gasket_details?.pressure_rating || '';
// 가스켓 재질은 gasket_details에서 가져옴
const material_spec = material_type !== 'UNKNOWN' ? material_type : (material.material_grade || 'Unknown');
// SWG 상세 정보 파싱 (additional_info에서)
let detailed_construction = 'N/A';
let face_type = '';
let thickness = material.gasket_details?.thickness || null;
// API에서 gasket_details의 추가 정보를 확인 (브라우저 콘솔에서 확인용)
if (material.gasket_details && Object.keys(material.gasket_details).length > 0) {
console.log('Gasket details:', material.gasket_details);
}
// 상세 구성 정보 생성 (Face Type + Construction)
// H/F/I/O SS304/GRAPHITE/CS/CS 형태로 표시
if (material.original_description) {
const desc = material.original_description.toUpperCase();
// H/F/I/O 다음에 오는 재질 구성만 찾기 (H/F/I/O는 제외)
const fullMatch = desc.match(/H\/F\/I\/O\s+([A-Z0-9]+\/[A-Z]+\/[A-Z0-9]+\/[A-Z0-9]+)/);
if (fullMatch) {
// H/F/I/O와 재질 구성 둘 다 있는 경우
face_type = 'H/F/I/O';
const construction = fullMatch[1];
detailed_construction = `${face_type} ${construction}`;
} else {
// H/F/I/O만 있는 경우
const faceMatch = desc.match(/H\/F\/I\/O/);
if (faceMatch) {
detailed_construction = 'H/F/I/O';
} else {
// 재질 구성만 있는 경우 (H/F/I/O 없이)
const constructionOnlyMatch = desc.match(/([A-Z0-9]+\/[A-Z]+\/[A-Z0-9]+\/[A-Z0-9]+)/);
if (constructionOnlyMatch) {
detailed_construction = constructionOnlyMatch[1];
}
}
}
}
// 가스켓 스펙 생성
const gasket_spec_parts = [];
// 가스켓 타입 (SPIRAL_WOUND, O_RING 등)
if (gasket_type && gasket_type !== 'UNKNOWN') {
gasket_spec_parts.push(gasket_type.replace('_', ' '));
}
// 소재 (GRAPHITE, PTFE 등)
if (material_type && material_type !== 'UNKNOWN') {
gasket_spec_parts.push(material_type);
}
// 압력등급 (150LB, 300LB 등)
if (pressure_rating && pressure_rating !== 'UNKNOWN') {
gasket_spec_parts.push(pressure_rating);
}
const full_gasket_spec = gasket_spec_parts.join(', ');
specKey = `${category}|${full_gasket_spec}|${material_spec}|${main_nom}|${detailed_construction}`;
specData = {
category: 'GASKET',
gasket_type,
material_type,
pressure_rating,
full_gasket_spec,
material_spec,
size_display: main_nom,
main_nom,
detailed_construction,
thickness,
unit: 'EA',
isLength: false
};
} else if (category === 'BOLT') {
// BOLT: 타입 + 재질 + 사이즈 + 길이
const material_spec = material.material_grade || '';
const main_nom = material.main_nom || '';
const bolt_type = material.bolt_details?.bolt_type || 'BOLT';
const material_standard = material.bolt_details?.material_standard || '';
const material_grade = material.bolt_details?.material_grade || '';
const thread_type = material.bolt_details?.thread_type || '';
const diameter = material.bolt_details?.diameter || main_nom;
const length = material.bolt_details?.length || '';
const pressure_rating = material.bolt_details?.pressure_rating || '';
const coating_type = material.bolt_details?.coating_type || '';
// 볼트 스펙 생성
const bolt_spec_parts = [];
// 볼트 타입 (HEX_BOLT, STUD_BOLT 등)
if (bolt_type && bolt_type !== 'UNKNOWN') {
bolt_spec_parts.push(bolt_type.replace('_', ' '));
}
// 재질 (ASTM A193, ASTM A194 등)
if (material_standard) {
bolt_spec_parts.push(material_standard);
if (material_grade && material_grade !== material_standard) {
bolt_spec_parts.push(material_grade);
}
} else if (material_spec) {
bolt_spec_parts.push(material_spec);
}
// 나사 규격 (M12, 1/2" 등)
if (diameter) {
bolt_spec_parts.push(diameter);
}
// 코팅 타입 (ELECTRO_GALVANIZED 등)
if (coating_type && coating_type !== 'PLAIN') {
bolt_spec_parts.push(coating_type.replace('_', ' '));
}
const full_bolt_spec = bolt_spec_parts.join(', ');
specKey = `${category}|${full_bolt_spec}|${diameter}|${length}|${coating_type}|${pressure_rating}`;
specData = {
category: 'BOLT',
bolt_type,
thread_type,
full_bolt_spec,
material_standard,
material_grade,
diameter,
length,
coating_type,
pressure_rating,
size_display: diameter,
main_nom: diameter,
unit: 'EA',
isLength: false
};
} else if (category === 'INSTRUMENT') {
// INSTRUMENT: 타입 + 연결사이즈 + 측정범위
const main_nom = material.main_nom || '';
const instrument_type = material.instrument_details?.instrument_type || 'INSTRUMENT';
const measurement_range = material.instrument_details?.measurement_range || '';
const signal_type = material.instrument_details?.signal_type || '';
// 계기 스펙 생성
const instrument_spec_parts = [];
// 계기 타입 (PRESSURE_GAUGE, TEMPERATURE_TRANSMITTER 등)
if (instrument_type && instrument_type !== 'UNKNOWN') {
instrument_spec_parts.push(instrument_type.replace('_', ' '));
}
// 측정 범위 (0-100 PSI, 4-20mA 등)
if (measurement_range) {
instrument_spec_parts.push(measurement_range);
}
// 연결 사이즈 (1/4", 1/2" 등)
if (main_nom) {
instrument_spec_parts.push(`${main_nom} CONNECTION`);
}
const full_instrument_spec = instrument_spec_parts.join(', ');
specKey = `${category}|${full_instrument_spec}|${main_nom}`;
specData = {
category: 'INSTRUMENT',
instrument_type,
measurement_range,
signal_type,
full_instrument_spec,
size_display: main_nom,
main_nom,
unit: 'EA',
isLength: false
};
} else if (category === 'VALVE') {
// VALVE: 타입 + 연결방식 + 압력등급 + 재질 + 사이즈
const main_nom = material.main_nom || '';
const valve_type = material.valve_details?.valve_type || 'VALVE';
const valve_subtype = material.valve_details?.valve_subtype || '';
const connection_method = material.valve_details?.connection_method || '';
const pressure_rating = material.valve_details?.pressure_rating || '';
const body_material = material.valve_details?.body_material || material.material_grade || '';
const actuator_type = material.valve_details?.actuator_type || 'MANUAL';
const fire_safe = material.valve_details?.fire_safe || false;
// 밸브 스펙 생성
const valve_spec_parts = [];
// 밸브 타입 (GATE_VALVE, BALL_VALVE 등)
if (valve_type && valve_type !== 'UNKNOWN') {
valve_spec_parts.push(valve_type.replace('_', ' '));
}
// 연결 방식 (FLANGED, THREADED, SOCKET_WELD 등)
if (connection_method && connection_method !== 'UNKNOWN') {
valve_spec_parts.push(connection_method.replace('_', ' '));
}
// 압력 등급 (150LB, 300LB 등)
if (pressure_rating && pressure_rating !== 'UNKNOWN') {
valve_spec_parts.push(pressure_rating);
}
// 작동 방식 (수동이 아닌 경우만 표시)
if (actuator_type && actuator_type !== 'MANUAL' && actuator_type !== 'UNKNOWN') {
valve_spec_parts.push(actuator_type.replace('_', ' '));
}
// 특수 기능 (Fire Safe 등)
if (fire_safe) {
valve_spec_parts.push('FIRE SAFE');
}
if (valve_subtype && valve_subtype !== 'UNKNOWN') {
valve_spec_parts.push(valve_subtype);
}
const full_valve_spec = valve_spec_parts.join(', ');
specKey = `${category}|${full_valve_spec}|${body_material}|${main_nom}`;
specData = {
category: 'VALVE',
valve_type,
valve_subtype,
connection_method,
pressure_rating,
body_material,
actuator_type,
fire_safe,
full_valve_spec,
size_display: main_nom,
main_nom,
unit: 'EA',
isLength: false
};
} else {
// 기타 자재: 기본 분류
const material_spec = material.material_grade || '';
const size_display = material.main_nom || material.size_spec || '';
specKey = `${category}|${material_spec}|${size_display}`;
specData = {
category,
material_spec,
size_display,
unit: 'EA',
isLength: false
};
}
if (!specs[specKey]) {
specs[specKey] = {
...specData,
totalQuantity: 0,
totalLength: 0,
count: 0,
items: []
};
}
specs[specKey].totalQuantity += material.quantity || 0;
specs[specKey].count += 1;
specs[specKey].items.push(material);
// PIPE의 경우 길이 합산
if (category === 'PIPE' && material.pipe_details?.length_mm) {
specs[specKey].totalLength += material.pipe_details.length_mm;
}
});
return Object.values(specs);
};
const formatLength = (lengthMm) => {
if (!lengthMm || lengthMm === 0) return '0mm';
if (lengthMm >= 1000) {
return `${(lengthMm / 1000).toFixed(2)}m (${lengthMm.toLocaleString()}mm)`;
}
return `${lengthMm.toLocaleString()}mm`;
};
const getCategoryColor = (category) => {
const colors = {
'PIPE': 'primary',
'FITTING': 'secondary',
'VALVE': 'success',
'BOLT': 'warning',
'GASKET': 'info',
'INSTRUMENT': 'error',
'UNKNOWN': 'default'
};
return colors[category] || 'default';
};
// 엑셀 내보내기 함수
const handleExportToExcel = () => {
if (materials.length === 0) {
alert('내보낼 자재 데이터가 없습니다.');
return;
}
const additionalInfo = {
filename: fileName,
jobNo: jobNo,
revision: currentRevision,
uploadDate: new Date().toLocaleDateString()
};
const baseFilename = `자재목록_${jobNo}_${currentRevision}`;
exportMaterialsToExcel(materials, baseFilename, additionalInfo);
};
if (loading) {
return (
자재 정보를 불러오는 중...
);
}
if (error) {
return (
}
onClick={() => navigate(-1)}
sx={{ mb: 2 }}
>
뒤로가기
⚠️ {error}
💡 해당 파일에 자재 정보가 없습니다.
);
}
const materialSpecs = generateMaterialSpecs(materials);
const totalSpecs = materialSpecs.length;
const categoryStats = materialSpecs.reduce((acc, spec) => {
acc[spec.category] = (acc[spec.category] || 0) + 1;
return acc;
}, {});
return (
{/* 헤더 */}
}
onClick={() => navigate(-1)}
>
뒤로가기
{/* 리비전 비교 버튼 */}
{availableRevisions.length > 1 && currentRevision !== 'Rev.0' && (
}
onClick={() => navigate(`/material-comparison?job_no=${jobNo}&revision=${currentRevision}&filename=${encodeURIComponent(fileName)}`)}
>
리비전 비교
)}
}
onClick={handleExportToExcel}
disabled={materials.length === 0}
>
엑셀 내보내기
}
onClick={() => {
const params = new URLSearchParams(window.location.search);
navigate(`/purchase-confirmation?${params.toString()}`);
}}
disabled={materialSpecs.length === 0}
sx={{ minWidth: 150 }}
>
구매 확정
{/* 리비전 선택 */}
{availableRevisions.length > 1 && (
📋 {bomName}
Job No: {jobNo} | 현재 리비전: {currentRevision}
리비전 선택
)}
📋 자재 사양서
업체 견적 요청용 자재 사양 목록
{fileName && (
파일명: {fileName}
)}
{/* 통계 요약 */}
📊 사양 요약
{Object.entries(categoryStats).map(([category, count]) => (
))}
{/* 카테고리별 자재 사양서 */}
{Object.entries(categoryStats).map(([category, count]) => {
const categorySpecs = materialSpecs.filter(spec => spec.category === category);
return (
🔧 {category} 사양 ({count}개)
{category === 'PIPE'
? '동일한 재질·외경·스케줄·제작방식의 파이프들을 그룹화하여 표시합니다.'
: '동일한 사양의 자재들을 그룹화하여 수량을 합산합니다.'
}
{category === 'PIPE' && (
<>
사양
외경
스케줄
제작방식
총 길이
>
)}
{category === 'FITTING' && (
<>
품목
재질
사이즈
수량
>
)}
{category === 'FLANGE' && (
<>
플랜지 타입
재질
사이즈
수량
>
)}
{category === 'GASKET' && (
<>
가스켓 타입
상세 구성
재질
두께
사이즈
수량
>
)}
{category === 'BOLT' && (
<>
볼트 타입
재질
사이즈
길이
코팅
압력등급
수량
>
)}
{category === 'INSTRUMENT' && (
<>
계기 타입
측정범위
연결사이즈
수량
>
)}
{category === 'VALVE' && (
<>
밸브 타입
연결방식
압력등급
재질
사이즈
작동방식
수량
>
)}
{!['PIPE', 'FITTING', 'FLANGE', 'GASKET', 'BOLT', 'INSTRUMENT', 'VALVE'].includes(category) && (
<>
재질
사이즈
수량
>
)}
개수
{categorySpecs.map((spec, index) => (
{category === 'PIPE' && (
<>
{spec.material_spec || 'Unknown'}
{spec.outer_diameter || 'Unknown'}
{spec.schedule || 'Unknown'}
{spec.manufacturing_method || 'Unknown'}
{formatLength(spec.totalLength)}
>
)}
{category === 'FITTING' && (
<>
{spec.full_fitting_spec || spec.fitting_type || 'UNKNOWN'}
{spec.material_spec || 'Unknown'}
{spec.size_display || 'Unknown'}
{spec.totalQuantity} {spec.unit}
>
)}
{category === 'FLANGE' && (
<>
{spec.full_flange_spec || spec.flange_type || 'UNKNOWN'}
{spec.material_spec || 'Unknown'}
{spec.size_display || 'Unknown'}
{spec.totalQuantity} {spec.unit}
>
)}
{category === 'GASKET' && (
<>
{spec.gasket_type?.replace('_', ' ') || 'UNKNOWN'}
{spec.detailed_construction || 'N/A'}
{spec.material_spec || 'Unknown'}
{spec.thickness ? `${spec.thickness}mm` : 'N/A'}
{spec.size_display || 'Unknown'}
{spec.totalQuantity} {spec.unit}
>
)}
{category === 'BOLT' && (
<>
{spec.bolt_type?.replace('_', ' ') || 'UNKNOWN'}
{spec.material_standard || 'Unknown'} {spec.material_grade || ''}
{spec.diameter ? (spec.diameter.includes('"') ? spec.diameter : spec.diameter.replace('0.5', '1/2"').replace('0.75', '3/4"').replace('1.0', '1"').replace('1.5', '1 1/2"')) : 'Unknown'}
{spec.length || 'N/A'}
{spec.coating_type ? spec.coating_type.replace('_', ' ') : 'N/A'}
{spec.pressure_rating || 'N/A'}
{spec.totalQuantity} {spec.unit}
>
)}
{category === 'INSTRUMENT' && (
<>
{spec.instrument_type?.replace('_', ' ') || 'UNKNOWN'}
{spec.measurement_range || 'Unknown'}
{spec.size_display || 'Unknown'}
{spec.totalQuantity} {spec.unit}
>
)}
{category === 'VALVE' && (
<>
{spec.valve_type?.replace('_', ' ') || 'VALVE'}
{spec.connection_method?.replace('_', ' ') || 'Unknown'}
{spec.pressure_rating || 'Unknown'}
{spec.body_material || 'Unknown'}
{spec.size_display || 'Unknown'}
{spec.actuator_type?.replace('_', ' ') || 'MANUAL'}
{spec.fire_safe && ' + FIRE SAFE'}
{spec.totalQuantity} {spec.unit}
>
)}
{(!['PIPE', 'FITTING', 'FLANGE', 'GASKET', 'BOLT', 'INSTRUMENT', 'VALVE'].includes(category)) && (
<>
{spec.material_spec || 'Unknown'}
{spec.size_display || 'Unknown'}
{spec.totalQuantity} {spec.unit}
>
)}
))}
);
})}
{materialSpecs.length === 0 && (
💡 해당 파일에 자재 정보가 없습니다.
)}
);
};
export default MaterialsPage;