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: '#ef4444' }, { 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: '#a855f7' }, { 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: '#3b82f6' }, { 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: '#f97316' }, { 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: '#eab308' }, { 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: '#22c55e' }, { 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: '#ef4444' }, { 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: '#f97316' }, { 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: '#a855f7' }, { 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: '#ef4444' }, ] 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<'abbr' | 'korName' | 'engName' | 'cas' | 'un'>('abbr') 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() 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 { 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-border text-[10px] outline-none bg-bg-3 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 === 'abbr' ? '검색어 입력 (부호·띄어쓰기 제외)' : hmsSearchType === 'cas' ? 'CAS번호 입력 (예: 71-43-2)' : hmsSearchType === 'un' ? 'UN번호 입력 (예: 1114)' : '검색어 입력 (* 동의어 검색)'} className="flex-1 rounded-sm border border-border text-[11px] outline-none bg-bg-0 px-3 py-2" />
※ 국문명·영문명 검색 시 동의어까지 검색  |  약자/제품명 검색 시 부호, 띄어쓰기 제외 후 검색  |  총 1,316종 등록 (화물적부도 277종 / 해양시설 56종 / 용선자 화물코드 983종)
{/* ── 검색 결과 테이블 ── */}
📋 검색 결과 — {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(249,115,22,.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}
검색 결과가 없습니다.
{hmsTotal.toLocaleString()}종 등록 · Port-MIS 화물적부도 연동 · 해경청 물질정보집 · IBC CODE 692종
{Array.from({ length: Math.min(hmsTotalPages, 5) }, (_, i) => i + 1).map(p => ( ))}
{/* ── 물질 상세정보 패널 ── */} {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(--purple)' : s.sebc.startsWith('E') ? 'var(--red)' : s.sebc.startsWith('F') ? 'var(--yellow)' : s.sebc.startsWith('D') ? 'var(--cyan)' : s.sebc.startsWith('S') ? 'var(--green)' : 'var(--t2)' 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(--cyan)'], ['상태', s.state, 'var(--cyan)'], ['색상', s.color, 'var(--orange)'], ['냄새', s.odor, 'var(--orange)'], ['인화점', s.flashPoint, 'var(--red)'], ['발화점', s.autoIgnition, 'var(--red)'], ['끓는점', s.boilingPoint, 'var(--purple)'], ['비중 (물=1)', s.density, 'var(--purple)'], ['용해도', s.solubility, 'var(--green)'], ['증기압', s.vaporPressure, 'var(--green)'], ['증기밀도 (공기=1)', s.vaporDensity, 'var(--yellow)'], ['폭발범위', s.explosionRange, 'var(--yellow)'], ] as [string, string, string][]).map(([label, value, borderColor]) => (
{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(--orange)' : c.source === '용선자' ? 'var(--cyan)' : 'var(--green)' const srcBg = c.source === '적부도' ? 'rgba(249,115,22,.1)' : c.source === '용선자' ? 'rgba(6,182,212,.1)' : 'rgba(34,197,94,.1)' return ( ) })}
화물코드 약자/제품명 국적/회사 출처
{c.code} {c.name} {c.company} {c.source}
{/* 항구별 코드 */}
🏗 항구별 코드
Port-MIS 위험물반입신고현황 연동
{s.portFrequency.map((p, i) => { const freqColor = p.frequency === '높음' ? 'var(--red)' : p.frequency === '중간' ? 'var(--orange)' : 'var(--green)' const freqBg = p.frequency === '높음' ? 'rgba(239,68,68,.1)' : p.frequency === '중간' ? 'rgba(249,115,22,.1)' : 'rgba(34,197,94,.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}
) }