import React, { useState, useRef, useEffect, useCallback } from 'react'; import { sanitizeHtml } from '@common/utils/sanitize'; import { api } from '@common/services/api'; import type { HNSSearchSubstance } from '@common/types/hns'; /* ═══ HNS 물질 데이터베이스 ═══ */ interface HNSSubstance { name: string; nameEn: string; formula: string; unNumber: string; casNumber: string; imdgClass: string; sebc: string; flashPoint: string; boilingPoint: string; specificGravity: string; vaporPressure: string; solubility: string; idlh: string; twa: string; aegl1: string; aegl2: string; aegl3: string; category: string; color: string; } const substances: HNSSubstance[] = [ { name: '톨루엔', nameEn: 'Toluene', formula: 'C₇H₈', unNumber: 'UN1294', casNumber: '108-88-3', imdgClass: 'Class 3', sebc: 'E (증발)', flashPoint: '4°C', boilingPoint: '111°C', specificGravity: '0.867', vaporPressure: '28.4 mmHg', solubility: '0.05%', idlh: '500 ppm', twa: '20 ppm', aegl1: '37 ppm', aegl2: '150 ppm', aegl3: '500 ppm', category: 'flammable_liquid', color: '#06b6d4', }, { name: '벤젠', nameEn: 'Benzene', formula: 'C₆H₆', unNumber: 'UN1114', casNumber: '71-43-2', imdgClass: 'Class 3', sebc: 'E (증발)', flashPoint: '-11°C', boilingPoint: '80°C', specificGravity: '0.879', vaporPressure: '94.8 mmHg', solubility: '0.18%', idlh: '500 ppm', twa: '0.5 ppm', aegl1: '9 ppm', aegl2: '800 ppm', aegl3: '4,000 ppm', category: 'flammable_liquid', color: '#06b6d4', }, { name: '암모니아', nameEn: 'Ammonia', formula: 'NH₃', unNumber: 'UN1005', casNumber: '7664-41-7', imdgClass: 'Class 2.3', sebc: 'G (가스)', flashPoint: '-', boilingPoint: '-33°C', specificGravity: '0.73', vaporPressure: '8,585 mmHg', solubility: '31%', idlh: '300 ppm', twa: '25 ppm', aegl1: '30 ppm', aegl2: '160 ppm', aegl3: '1,100 ppm', category: 'toxic_gas', color: '#06b6d4', }, { name: '메탄올', nameEn: 'Methanol', formula: 'CH₃OH', unNumber: 'UN1230', casNumber: '67-56-1', imdgClass: 'Class 3', sebc: 'D (용해)', flashPoint: '11°C', boilingPoint: '65°C', specificGravity: '0.791', vaporPressure: '127 mmHg', solubility: '완전 용해', idlh: '6,000 ppm', twa: '200 ppm', aegl1: '-', aegl2: '2,100 ppm', aegl3: '14,000 ppm', category: 'flammable_liquid', color: '#06b6d4', }, { name: '자일렌', nameEn: 'Xylene', formula: 'C₈H₁₀', unNumber: 'UN1307', casNumber: '1330-20-7', imdgClass: 'Class 3', sebc: 'F (부유)', flashPoint: '27°C', boilingPoint: '144°C', specificGravity: '0.864', vaporPressure: '8.8 mmHg', solubility: '0.02%', idlh: '900 ppm', twa: '100 ppm', aegl1: '130 ppm', aegl2: '920 ppm', aegl3: '2,500 ppm', category: 'flammable_liquid', color: '#06b6d4', }, { name: '스타이렌', nameEn: 'Styrene', formula: 'C₈H₈', unNumber: 'UN2055', casNumber: '100-42-5', imdgClass: 'Class 3', sebc: 'F (부유)', flashPoint: '31°C', boilingPoint: '145°C', specificGravity: '0.906', vaporPressure: '6.4 mmHg', solubility: '0.03%', idlh: '700 ppm', twa: '20 ppm', aegl1: '20 ppm', aegl2: '130 ppm', aegl3: '1,100 ppm', category: 'flammable_liquid', color: '#06b6d4', }, { name: '아세톤', nameEn: 'Acetone', formula: 'C₃H₆O', unNumber: 'UN1090', casNumber: '67-64-1', imdgClass: 'Class 3', sebc: 'E (증발)', flashPoint: '-20°C', boilingPoint: '56°C', specificGravity: '0.784', vaporPressure: '231 mmHg', solubility: '완전 용해', idlh: '2,500 ppm', twa: '500 ppm', aegl1: '200 ppm', aegl2: '3,200 ppm', aegl3: '13,000 ppm', category: 'flammable_liquid', color: '#06b6d4', }, { name: '염소', nameEn: 'Chlorine', formula: 'Cl₂', unNumber: 'UN1017', casNumber: '7782-50-5', imdgClass: 'Class 2.3', sebc: 'G (가스)', flashPoint: '-', boilingPoint: '-34°C', specificGravity: '1.56', vaporPressure: '6,627 mmHg', solubility: '0.7%', idlh: '10 ppm', twa: '0.5 ppm', aegl1: '0.5 ppm', aegl2: '2 ppm', aegl3: '20 ppm', category: 'toxic_gas', color: '#06b6d4', }, { name: '수소', nameEn: 'Hydrogen', formula: 'H₂', unNumber: 'UN1049', casNumber: '1333-74-0', imdgClass: 'Class 2.1', sebc: 'G (가스)', flashPoint: '-', boilingPoint: '-253°C', specificGravity: '0.07', vaporPressure: '-', solubility: '0.0016%', idlh: '-', twa: '-', aegl1: '-', aegl2: '-', aegl3: '-', category: 'flammable_gas', color: '#06b6d4', }, { name: 'LPG', nameEn: 'LPG (Propane/Butane)', formula: 'C₃H₈/C₄H₁₀', unNumber: 'UN1075', casNumber: '68476-85-7', imdgClass: 'Class 2.1', sebc: 'G (가스)', flashPoint: '-104°C', boilingPoint: '-42°C', specificGravity: '0.50', vaporPressure: '8,460 mmHg', solubility: '0.01%', idlh: '2,100 ppm', twa: '1,000 ppm', aegl1: '-', aegl2: '17,000 ppm', aegl3: '33,000 ppm', category: 'flammable_gas', color: '#06b6d4', }, { name: '에틸렌', nameEn: 'Ethylene', formula: 'C₂H₄', unNumber: 'UN1962', casNumber: '74-85-1', imdgClass: 'Class 2.1', sebc: 'G (가스)', flashPoint: '-', boilingPoint: '-104°C', specificGravity: '0.57', vaporPressure: '-', solubility: '0.01%', idlh: '-', twa: '-', aegl1: '-', aegl2: '-', aegl3: '-', category: 'flammable_gas', color: '#06b6d4', }, { name: '1,2-디클로로에탄', nameEn: '1,2-Dichloroethane (EDC)', formula: 'C₂H₄Cl₂', unNumber: 'UN1184', casNumber: '107-06-2', imdgClass: 'Class 3', sebc: 'S (침강)', flashPoint: '13°C', boilingPoint: '83°C', specificGravity: '1.253', vaporPressure: '87 mmHg', solubility: '0.87%', idlh: '50 ppm', twa: '1 ppm', aegl1: '-', aegl2: '20 ppm', aegl3: '200 ppm', category: 'toxic_liquid', color: '#06b6d4', }, { name: '페놀', nameEn: 'Phenol', formula: 'C₆H₅OH', unNumber: 'UN2312', casNumber: '108-95-2', imdgClass: 'Class 6.1', sebc: 'S/SD (침강/용해)', flashPoint: '79°C', boilingPoint: '182°C', specificGravity: '1.07', vaporPressure: '0.35 mmHg', solubility: '8.4%', idlh: '250 ppm', twa: '5 ppm', aegl1: '19 ppm', aegl2: '29 ppm', aegl3: '57 ppm', category: 'toxic_liquid', color: '#06b6d4', }, ]; const categories = [ { id: 'all', label: '전체', icon: '📋' }, { id: 'toxic_liquid', label: '유독성 액체', icon: '🧪' }, { id: 'toxic_gas', label: '유독성 가스', icon: '☠️' }, { id: 'flammable_liquid', label: '인화성 액체', icon: '🔥' }, { id: 'flammable_gas', label: '인화성 가스', icon: '💨' }, ]; export function HNSSubstanceView() { const [activeTab, setActiveTab] = useState(0); const [selectedCategory, setSelectedCategory] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [detailSearchName, setDetailSearchName] = useState(''); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [detailSearchCas, setDetailSearchCas] = useState(''); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [detailSearchSebc, setDetailSearchSebc] = useState('전체 거동분류'); /* Panel 3: 물질 상세검색 state */ const [hmsSearchType, setHmsSearchType] = useState< 'all' | 'abbr' | 'korName' | 'engName' | 'cas' | 'un' >('all'); const [hmsSearchInput, setHmsSearchInput] = useState(''); const [hmsFilterSebc, setHmsFilterSebc] = useState('전체 거동분류'); const [hmsSelectedId, setHmsSelectedId] = useState(null); const [hmsDetailTab, setHmsDetailTab] = useState(0); const [hmsPage, setHmsPage] = useState(1); const [hmsResults, setHmsResults] = useState([]); const [hmsTotal, setHmsTotal] = useState(0); const [hmsLoading, setHmsLoading] = useState(false); const [hmsSelectedSubstance, setHmsSelectedSubstance] = useState(null); const contentRef = useRef(null); // 검색 타입 매핑 (프론트엔드 → API) const searchTypeMap: Record = { abbr: 'abbreviation', korName: 'nameKr', engName: 'nameEn', cas: 'casNumber', un: 'unNumber', }; // HNS 물질 검색 API 호출 const fetchHnsSubstances = useCallback(async () => { setHmsLoading(true); try { const params: Record = { page: hmsPage, limit: 10 }; if (hmsSearchInput.trim()) { params.q = hmsSearchInput.trim(); if (hmsSearchType !== 'all') { params.type = searchTypeMap[hmsSearchType] || 'abbreviation'; } } if (hmsFilterSebc !== '전체 거동분류') { params.sebc = hmsFilterSebc.split(' ')[0]; } const { data } = await api.get('/hns', { params }); setHmsResults(data.items); setHmsTotal(data.total); } catch (err) { console.error('[HNS] 물질 검색 오류:', err); setHmsResults([]); setHmsTotal(0); } finally { setHmsLoading(false); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [hmsSearchInput, hmsSearchType, hmsFilterSebc, hmsPage]); // 검색 조건 변경 시 API 호출 (디바운스) useEffect(() => { const timer = setTimeout(fetchHnsSubstances, 300); return () => clearTimeout(timer); }, [fetchHnsSubstances]); // 물질 선택 시 상세 정보 조회 useEffect(() => { if (hmsSelectedId === null) { setHmsSelectedSubstance(null); return; } api .get(`/hns/${hmsSelectedId}`) .then(({ data }) => { setHmsSelectedSubstance(data); }) .catch(() => { setHmsSelectedSubstance(null); }); }, [hmsSelectedId]); const handleExportPDF = () => { if (!contentRef.current) return; const clone = contentRef.current.cloneNode(true) as HTMLElement; clone.querySelectorAll('[data-html2pdf-ignore]').forEach((el) => el.remove()); const content = sanitizeHtml(clone.innerHTML); const styles = Array.from(document.querySelectorAll('style, link[rel="stylesheet"]')) .map((el) => el.outerHTML) .join('\n'); const fullHtml = ` HNS 물질정보 ${styles} ${content}`; const blob = new Blob([fullHtml], { type: 'text/html; charset=utf-8' }); const url = URL.createObjectURL(blob); const win = window.open(url, '_blank'); if (win) { win.addEventListener('afterprint', () => URL.revokeObjectURL(url)); setTimeout(() => { win.document.title = 'HNS_물질정보'; win.print(); }, 500); } setTimeout(() => URL.revokeObjectURL(url), 30000); }; const filtered = substances.filter((s) => { const matchCategory = selectedCategory === 'all' || s.category === selectedCategory; const q = searchQuery.toLowerCase(); const matchSearch = !q || s.name.toLowerCase().includes(q) || s.nameEn.toLowerCase().includes(q) || s.unNumber.toLowerCase().includes(q) || s.casNumber.includes(q) || s.formula.toLowerCase().includes(q); return matchCategory && matchSearch; }); /* Detail search filter for Panel 3 (legacy) */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const detailFiltered = substances.filter((s) => { const qName = detailSearchName.toLowerCase(); const qCas = detailSearchCas.toLowerCase(); const matchName = !qName || s.name.toLowerCase().includes(qName) || s.nameEn.toLowerCase().includes(qName); const matchCas = !qCas || s.casNumber.includes(qCas); const matchSebc = detailSearchSebc === '전체 거동분류' || s.sebc.includes(detailSearchSebc.split(' ')[0]); return matchName && matchCas && matchSebc; }); /* Panel 3: HNS API 기반 검색 결과 */ const HMS_PER_PAGE = 10; const hmsTotalPages = Math.max(1, Math.ceil(hmsTotal / HMS_PER_PAGE)); const hmsPageData = hmsResults; const tabLabels = [ { icon: '📊', label: 'SEBC 거동분류' }, { icon: '🧪', label: '주요 물질 특성' }, { icon: '⚡', label: '위험도 기준' }, { icon: '🔍', label: '물질 상세검색' }, ]; return (
{/* 헤더 */}
🧬
HNS 물질정보 데이터베이스
SEBC 거동분류 · CHRIS/CAMEO DB · AEGL/ERPG/IDLH 기준 · 6,500+ 물질
setSearchQuery(e.target.value)} className="rounded-sm border border-stroke text-label-2 outline-none bg-bg-card px-3 py-1.5 w-[200px]" />
{/* 서브탭 */}
{tabLabels.map((tab, idx) => ( ))}
{/* ═══ MAT PANEL 0: SEBC 거동분류 ═══ */} {activeTab === 0 && (
SEBC 해양 거동 분류 체계 Standard European Behaviour Classification
HNS 물질이 해양에 유출되었을 때의{' '} 물리·화학적 거동에 따라 분류하는 국제 표준 체계입니다. 물질의 밀도, 증기압, 용해도에 따라 5개 주요 거동 유형과 혼합 유형으로 구분되며, 각 유형별로 대응 전략이 달라집니다.
{/* G: Gas */}
💨
G
Gas
기체 상태로 대기 중 확산. 증기압이 높아 빠르게 증발
대기확산 모델 적용
{/* E: Evaporator */}
🔥
E
Evaporator
해수면에서 증발. 부유 후 기화하여 독성 가스 생성
대기+해양 복합 대응
{/* F: Floater */}
🟡
F
Floater
{'해수면 위에 부유. 비중 < 1.0, 불용성 물질'}
오일펜스 유사 봉쇄
{/* D: Dissolver */}
💧
D
Dissolver
해수에 용해. 수중 확산하여 넓은 범위 오염
해양확산 모델 적용
{/* S: Sinker */}
S
Sinker
{'해저로 침강. 비중 > 1.0, 저층 오염 축적'}
저층 3D 모니터링 필수
{/* 복합 거동 */}
🔀 복합 거동 유형
GD
기체+용해
ED
증발+용해
FE
부유+증발
FED
부유+증발+용해
SD
침강+용해
)} {/* ═══ MAT PANEL 1: 주요 물질 특성 ═══ */} {activeTab === 1 && (
{/* 카테고리 필터 */}
{categories.map((cat) => ( ))}
{/* 주요 HNS 물질 카드 */}
{/* 암모니아 */} {filtered.find((s) => s.casNumber === '7664-41-7') && (
NH₃ {' '} 암모니아
G/GD 독성
CAS:{' '} 7664-41-7
분자량:{' '} 17.03
끓는점:{' '} -33.4°C
비중:{' '} 0.73
인화점:{' '} N/A (불연)
용해도:{' '} 매우 높음
AEGL-2
160 ppm
ERPG-2
150 ppm
IDLH
300 ppm
해상 유출 시 급속 기화 → 독성 가스운 형성. 물에 잘 용해되어 수중 독성도 높음. 해풍 환경에서 확산 범위 확대.
)} {/* 메탄올 */} {filtered.find((s) => s.casNumber === '67-56-1') && (
CH₃OH {' '} 메탄올
ED 인화성
CAS:{' '} 67-56-1
분자량:{' '} 32.04
끓는점:{' '} 64.7°C
비중:{' '} 0.79
인화점:{' '} 11°C
용해도:{' '} 완전 혼화
AEGL-2
2,100 ppm
ERPG-2
1,000 ppm
IDLH
6,000 ppm
해수에 완전 용해 → 수질 오염 장기화. 인화점 낮아 화재 위험. 증발 시 독성 증기 발생. 2007 온산항 FODDANGER호 95만L 유출 사고.
)} {/* 수소 */} {filtered.find((s) => s.casNumber === '1333-74-0') && (
H₂{' '} 수소
G 폭발
CAS:{' '} 1333-74-0
분자량:{' '} 2.016
끓는점:{' '} -252.9°C
비중(기체):{' '} 0.07
LFL:{' '} 4.0%
UFL:{' '} 75.0%
폭발 범위 극히 넓음(4~75%). 무색·무취로 감지 불가. 극저온 액화수소 유출 시 BLEVE 위험. 급속 상승 확산.
)} {/* LNG */} {filtered.find( (s) => s.nameEn === 'LPG (Propane/Butane)' || s.casNumber === '74-82-8', ) && (
CH₄ {' '} LNG (메탄)
G 인화/폭발
CAS:{' '} 74-82-8
분자량:{' '} 16.04
끓는점:{' '} -161.5°C
비중(액체):{' '} 0.42
LFL:{' '} 5.0%
UFL:{' '} 15.0%
극저온(-162°C) 유출 시 RPT(급속상변환폭발), Pool Fire 위험. Flash 기화 → 가연성 가스운 형성. 인천·평택항 LNG 물동량 상위.
)} {/* 페놀 */} {filtered.find((s) => s.casNumber === '108-95-2' || s.name === '페놀') && (
C₆H₅OH {' '} 페놀
S/SD 독성
CAS:{' '} 108-95-2
분자량:{' '} 94.11
끓는점:{' '} 181.7°C
비중:{' '} 1.07 (침강)
인화점:{' '} 79°C
용해도:{' '} 84 g/L
비중 1.07 → Sinker 특성으로 저층 축적. ROMS 검증 결과 저층 농도가 표층의 3.5배. 해양산업시설 배출 주요 HNS (31.8kg/일).
)} {/* 톨루엔 */} {filtered.find((s) => s.casNumber === '108-88-3') && (
C₇H₈ {' '} 톨루엔
FE 인화성
CAS:{' '} 108-88-3
분자량:{' '} 92.14
끓는점:{' '} 110.6°C
비중:{' '} 0.87 (부유)
인화점:{' '} 4°C
용해도:{' '} 0.52 g/L
해수면 부유 → 증발. 인화점 극히 낮아(4°C) 화재 위험 상시. 석유화학 산업의 대표적 HNS. 울산항 주요 취급물질.
)}
)} {/* ═══ MAT PANEL 2: 위험도 기준 ═══ */} {activeTab === 2 && (
{/* AEGL 기준 */}
🟢 AEGL (Acute Exposure Guideline Level)
미국 EPA — 일반인 급성 노출 기준 (10분, 30분, 60분, 4시간, 8시간)
AEGL-1 (불쾌감)
눈에 띄는 불쾌감, 자극 또는 비감각적 증상. 노출 중단 시 증상 소멸.
AEGL-2 (비가역적 영향)
비가역적·장기적 건강 영향 또는 대피 능력 저하.{' '} 대피 기준으로 사용.
AEGL-3 (생명 위협)
생명을 위협하는 건강 영향 또는 사망.{' '} 즉시 대피·격리 구역 설정.
{/* ERPG / IDLH */}
🔴 ERPG & IDLH
ERPG (Emergency Response Planning Guideline)
AIHA 발행 — 1시간 노출 기준
ERPG-1 — 일시적 건강 영향, 냄새 감지
ERPG-2 — 비가역적 영향, 대피 판단 기준
ERPG-3 — 생명 위협 농도
IDLH (Immediately Dangerous to Life or Health)
NIOSH — 30분 노출 시 생명·건강에 즉각적 위험
최대 허용 노출 한계로서 이 농도를 초과하면 자급식 호흡장치(SCBA) 없이는 진입 불가. WING 시스템에서 위험구역 자동 설정의 기준값으로 사용.
{/* 물질별 위험도 비교표 */}
📊 주요 HNS 물질별 위험도 기준 비교 (ppm)
물질 AEGL-1 AEGL-2 AEGL-3 ERPG-2 IDLH LFL(%) SEBC
NH₃ 암모니아 30 160 1,100 150 300 15.0 G/GD
CH₃OH 메탄올 530 2,100 14,000 1,000 6,000 6.0 ED
H₂ 수소 - - - - - 4.0 G
CH₄ LNG - - - - - 5.0 G
C₆H₅OH 페놀 19 29 57 50 250 1.8 S/SD
C₇H₈ 톨루엔 67 560 3,700 300 500 1.1 FE
※ AEGL: 60분 기준 / ERPG: 1시간 노출 / IDLH: 30분 / LFL: 폭발하한
)} {/* ═══ MAT PANEL 3: 물질 상세검색 ═══ */} {activeTab === 3 && (
{/* ── 검색창 ── */}
🔍 HNS 물질 통합 검색{' '} (화물약어·제품명·동의어·CAS·UN번호 통합)
구분:
{ setHmsSearchInput(e.target.value); setHmsPage(1); }} placeholder={ hmsSearchType === 'all' ? '물질명, 약자, CAS, UN번호 통합 검색' : hmsSearchType === 'abbr' ? '약자/제품명 입력' : hmsSearchType === 'cas' ? 'CAS번호 입력 (예: 71-43-2)' : hmsSearchType === 'un' ? 'UN번호 입력 (예: 1114)' : '검색어 입력' } className="flex-1 rounded-sm border border-stroke text-title-4 outline-none bg-bg-base px-3 py-2" />
※ 국문명·영문명 검색 시 동의어까지 검색{' '}  |  약자/제품명 검색 시{' '} 부호, 띄어쓰기 제외 후 검색  |  총{' '} 1,222종 등록
{/* ── 검색 결과 테이블 ── */}
📋 검색 결과{' '} — {hmsTotal}건 조회
{hmsLoading ? ( ) : hmsPageData.length > 0 ? ( hmsPageData.map((s: HNSSearchSubstance, idx: number) => { const isSel = hmsSelectedId === s.id; return ( { setHmsSelectedId(isSel ? null : s.id); setHmsDetailTab(0); }} style={{ borderBottom: '1px solid rgba(255,255,255,.04)', cursor: 'pointer', background: isSel ? 'rgba(6,182,212,.04)' : undefined, }} onMouseOver={(e) => { if (!isSel) e.currentTarget.style.background = 'rgba(6,182,212,.03)'; }} onMouseOut={(e) => { if (!isSel) e.currentTarget.style.background = ''; }} > ); }) ) : ( )}
No. 약자/제품명 영문명 영문명 동의어 국문명 국문 동의어 / 주요 사용처 UN번호 CAS번호
검색 중...
{(hmsPage - 1) * HMS_PER_PAGE + idx + 1} {s.abbreviation} {s.nameEn} {s.synonymsEn} { e.stopPropagation(); setHmsSelectedId(s.id); setHmsDetailTab(0); }} > {s.nameKr} {s.synonymsKr} {s.unNumber} {s.casNumber}
검색 결과가 없습니다.
{(() => { const range = 2; let start = Math.max(1, hmsPage - range); let end = Math.min(hmsTotalPages, hmsPage + range); if (end - start < range * 2) { start = Math.max(1, end - range * 2); end = Math.min(hmsTotalPages, start + range * 2); } const pages = []; for (let p = start; p <= end; p++) pages.push(p); return pages.map((p) => ( )); })()} {hmsPage} / {hmsTotalPages} 페이지
{hmsTotal.toLocaleString()}종 등록
{/* ── 물질 상세정보 패널 ── */} {hmsSelectedSubstance && ( )}
)}
); } /* ═══ HmsDetailPanel: 물질 상세정보 4-tab 패널 ═══ */ function HmsDetailPanel({ substance: s, activeTab, onTabChange, }: { substance: HNSSearchSubstance; activeTab: number; onTabChange: (t: number) => void; }) { const tabLabels = [ '📊 물질특성·위험정보', '🛡 방제거리·PPE·MSDS', '⚓ IBC CODE·EmS 대응', '🔗 화물적부도·항구별 코드', ]; const nfpa = s.nfpa; // eslint-disable-next-line @typescript-eslint/no-unused-vars const sebcColor = s.sebc.startsWith('G') ? 'var(--color-accent)' : s.sebc.startsWith('E') ? 'var(--color-accent)' : s.sebc.startsWith('F') ? 'var(--color-caution)' : s.sebc.startsWith('D') ? 'var(--color-accent)' : s.sebc.startsWith('S') ? 'var(--color-accent)' : 'var(--fg-sub)'; return (
{/* Tab Navigation */}
{tabLabels.map((label, i) => ( ))}
{/* TAB 0: 물질특성·위험정보 */} {activeTab === 0 && (
{/* Header */}
🧪
{s.nameKr}{' '} ({s.nameEn})
CAS: {s.casNumber} UN: {s.unNumber} 운송방법: {s.transportMethod} SEBC: {s.sebc}
유사명: {s.synonymsKr}  |  특성: {s.hazardClass}
{/* Left: 물리·화학적 특성 */}
⚗️ 물리·화학적 특성
{( [ ['용도', s.usage, 'var(--color-accent)'], ['상태', s.state, 'var(--color-accent)'], ['색상', s.color, 'var(--color-accent)'], ['냄새', s.odor, 'var(--color-accent)'], ['인화점', s.flashPoint, 'var(--color-accent)'], ['발화점', s.autoIgnition, 'var(--color-accent)'], ['끓는점', s.boilingPoint, 'var(--color-accent)'], ['비중 (물=1)', s.density, 'var(--color-accent)'], ['용해도', s.solubility, 'var(--color-accent)'], ['증기압', s.vaporPressure, 'var(--color-accent)'], ['증기밀도 (공기=1)', s.vaporDensity, 'var(--color-caution)'], ['폭발범위', s.explosionRange, 'var(--color-caution)'], ] as [string, string, string][] ).map(([label, value]) => (
{label}
{value}
))}
{/* Right: NFPA + 위험등급 */}
⚠️ 위험등급·농도기준
{nfpa.health} {nfpa.fire} {nfpa.special} {nfpa.reactivity}
NFPA 704
건강(적) {nfpa.health} {' '} —{' '} {nfpa.health >= 4 ? '치명적' : nfpa.health >= 3 ? '중상' : nfpa.health >= 2 ? '장해' : nfpa.health >= 1 ? '경미한 손상' : '무해'}
인화성(황) {nfpa.fire} {' '} —{' '} {nfpa.fire >= 4 ? '93°F 미만' : nfpa.fire >= 3 ? '100°F 미만' : nfpa.fire >= 2 ? '200°F 미만' : nfpa.fire >= 1 ? '200°F 이상' : '비가연'}
반응성(청) {nfpa.reactivity} {' '} —{' '} {nfpa.reactivity >= 3 ? '폭발 가능' : nfpa.reactivity >= 2 ? '격렬 반응' : nfpa.reactivity >= 1 ? '불안정 가능' : '안정'}
)} {/* TAB 1: 방제거리·PPE·MSDS */} {activeTab === 1 && (
{/* 방제거리 */}
🚧 방제거리 (ERG {s.ergNumber})
🔥 화재 시
격리거리: {s.responseDistanceFire} 이상
💨 유출 시 (비화재)
주간 방호활동거리:{' '} {s.responseDistanceSpillDay}
야간 방호활동거리:{' '} {s.responseDistanceSpillNight}
🌊 해상 유출 시
{s.marineResponse}
{/* PPE */}
🛡 개인보호장구 (PPE) 추천
🧑‍🚒
근거리
{s.ppeClose}
🦺
원거리
{s.ppeFar}
{/* MSDS */}
📄 MSDS 주요 정보
§2 유해성·위험성: {s.msds.hazard}
§4 응급조치: {s.msds.firstAid}
§5 소화방법: {s.msds.fireFighting}
§6 누출대응: {s.msds.spillResponse}
§8 노출방지: {s.msds.exposure}
§15 법적규제: {s.msds.regulation}
)} {/* TAB 2: IBC CODE·EmS 대응 */} {activeTab === 2 && (
{/* IBC CODE */}
⚓ IBC CODE 기반 주요 내용
{( [ ['위험성', s.ibcHazard], ['선박형식', s.ibcShipType], ['탱크형식', s.ibcTankType], ['탐지장비', s.ibcDetection], ['소화설비', s.ibcFireFighting], ['최소적재요건', s.ibcMinRequirement], ] as [string, string][] ).map(([label, value]) => (
{label}
{value}
))}
{/* Tank diagram SVG */}
CARGO Tank 1 CARGO Tank 2 CARGO Tank 3 {s.ibcShipType} — {s.ibcTankType}
{/* EmS */}
🆘 비상대응핸드북 (EmS) — ERG {s.ergNumber}
🔥 화재 대응
{s.emsFire}
💧 유출 대응
{s.emsSpill}
🏥 응급조치
{s.emsFirstAid}
)} {/* TAB 3: 화물적부도·항구별 코드 */} {activeTab === 3 && (
{/* 화물적부도 */}
📋 화물적부도 화물코드
클릭 시 물질검색창으로 이동
{s.cargoCodes.map((c, i) => { const srcColor = c.source === '적부도' ? 'var(--color-accent)' : c.source === '용선자' ? 'var(--color-accent)' : 'var(--color-accent)'; const srcBg = c.source === '적부도' ? 'rgba(6,182,212,.1)' : c.source === '용선자' ? 'rgba(6,182,212,.1)' : 'rgba(6,182,212,.1)'; return ( ); })}
화물코드 약자/제품명 국적/회사 출처
{c.code} {c.name} {c.company} {c.source}
{/* 항구별 코드 */}
🏗 항구별 코드
Port-MIS 위험물반입신고현황 연동
{s.portFrequency.map((p, i) => { const freqColor = p.frequency === '높음' ? 'var(--color-accent)' : p.frequency === '중간' ? 'var(--color-accent)' : 'var(--color-accent)'; const freqBg = p.frequency === '높음' ? 'rgba(6,182,212,.1)' : p.frequency === '중간' ? 'rgba(6,182,212,.1)' : 'rgba(6,182,212,.1)'; return ( ); })}
항구 청코드 최근 반입 빈도
{p.port} {p.portCode} {p.lastImport} {p.frequency}
)}
); } function InfoBoxRow({ label, value, bg, border, labelColor, valueColor, }: { label: string; value: string; bg: string; border: string; labelColor: string; valueColor: string; }) { return (
{label} {value}
); }