wing-ops/frontend/src/tabs/hns/components/HNSSubstanceView.tsx
Nan Kyung Lee 8f98f63aa5 feat(aerial): CCTV 실시간 HLS 스트림 + HNS 분석 고도화
CCTV 실시간 영상:
- CCTVPlayer 컴포넌트 (hls.js 기반 HLS/MJPEG/MP4 재생)
- 백엔드 HLS 프록시 엔드포인트 (CORS 우회, m3u8 URL 재작성)
- KHOA 15개 + KBS 6개 실제 해안 CCTV 연동
- Vite dev proxy, 스트림 타입 자동 감지 유틸리티

HNS 분석:
- HNS 시나리오 저장/불러오기/재계산 기능
- 물질 DB 검색 및 상세 정보 연동
- 좌표/파라미터 입력 UI 개선
- Python 확산 모델 스크립트 (hns_dispersion.py)

공통:
- 3D 지도 토글, 보고서 생성 개선
- useSubMenu 훅, mapUtils 확장
- ESLint set-state-in-effect 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:21:41 +09:00

1024 lines
83 KiB
TypeScript
Executable File
Raw Blame 히스토리

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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' },
{ 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: '#22c55e' },
]
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<number | null>(null)
const [hmsDetailTab, setHmsDetailTab] = useState(0)
const [hmsPage, setHmsPage] = useState(1)
const [hmsResults, setHmsResults] = useState<HNSSearchSubstance[]>([])
const [hmsTotal, setHmsTotal] = useState(0)
const [hmsLoading, setHmsLoading] = useState(false)
const [hmsSelectedSubstance, setHmsSelectedSubstance] = useState<HNSSearchSubstance | null>(null)
const contentRef = useRef<HTMLDivElement>(null)
// 검색 타입 매핑 (프론트엔드 → API)
const searchTypeMap: Record<string, string> = {
abbr: 'abbreviation', korName: 'nameKr', engName: 'nameEn', cas: 'casNumber', un: 'unNumber',
}
// HNS 물질 검색 API 호출
const fetchHnsSubstances = useCallback(async () => {
setHmsLoading(true)
try {
const params: Record<string, string | number> = { 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 = `<!DOCTYPE html>
<html><head><meta charset="utf-8"/>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'none';"/>
<title>HNS 물질정보</title>
${styles}
<style>
:root { --t1: #ffffff !important; --t2: #d0d6e6 !important; --t3: #a8b0c8 !important; }
@media print { @page { size: A4; margin: 10mm; } body { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; } }
body { background: var(--bg0); color: var(--t1); font-family: 'Noto Sans KR', sans-serif; padding: 20px 24px; }
</style>
</head><body>${content}</body></html>`
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 (
<div className="flex flex-col h-full w-full flex-1 overflow-hidden bg-bg-0">
<div ref={contentRef} className="flex-1 overflow-y-auto p-5" style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
{/* 헤더 */}
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-3">
<div className="flex items-center justify-center text-[20px] shrink-0" style={{ width: 42, height: 42, borderRadius: 10, background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(239,68,68,.15))', border: '1px solid rgba(249,115,22,.3)' }}>🧬</div>
<div>
<div className="text-base font-bold">HNS </div>
<div className="text-[10px] text-text-3 mt-0.5">SEBC · CHRIS/CAMEO DB · AEGL/ERPG/IDLH · 6,500+ </div>
</div>
</div>
<div className="flex gap-1.5" data-html2pdf-ignore>
<input
type="text"
placeholder="물질명 또는 CAS 번호 검색..."
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
className="rounded-sm border border-border text-[10px] outline-none bg-bg-3 px-3 py-1.5 w-[200px]"
/>
<button
onClick={handleExportPDF}
className="rounded-sm text-[10px] font-semibold cursor-pointer text-status-orange px-3.5 py-1.5"
style={{ border: '1px solid rgba(249,115,22,.3)', background: 'rgba(249,115,22,.08)' }}
>📥 DB </button>
</div>
</div>
{/* 서브탭 */}
<div className="flex rounded-md border border-border mb-5 bg-bg-3 p-1 gap-[3px]" data-html2pdf-ignore>
{tabLabels.map((tab, idx) => (
<button
key={idx}
onClick={() => setActiveTab(idx)}
className={activeTab === idx ? 'wx-tbtn on' : 'wx-tbtn'}
style={{ flex: 1, padding: 8, fontSize: 11 }}
>{tab.icon} {tab.label}</button>
))}
</div>
{/* ═══ MAT PANEL 0: SEBC 거동분류 ═══ */}
{activeTab === 0 && (
<div>
<div className="rounded-[12px] p-4 mb-4 relative overflow-hidden" style={{ background: 'linear-gradient(135deg,rgba(249,115,22,.05),rgba(6,182,212,.03))', border: '1px solid rgba(249,115,22,.2)' }}>
<div className="absolute top-0 left-0 right-0" style={{ height: 3, background: 'linear-gradient(90deg,var(--orange),var(--cyan),var(--green),var(--purple))' }} />
<div className="flex items-center gap-2 mb-3">
<span className="text-[13px] font-bold text-status-orange">SEBC </span>
<span className="text-[8px] font-semibold text-status-orange py-[2px] px-2 rounded-md" style={{ background: 'rgba(249,115,22,.1)' }}>Standard European Behaviour Classification</span>
</div>
<div className="text-[10px] text-text-2 leading-[1.7] mb-[14px]">
HNS <b className="text-status-orange">· </b> . , , 5 , .
</div>
<div className="grid mb-[14px]" style={{ gridTemplateColumns: 'repeat(5,1fr)', gap: 8 }}>
{/* G: Gas */}
<div className="p-3 rounded-md text-center" style={{ background: 'rgba(168,85,247,.06)', border: '1px solid rgba(168,85,247,.2)' }}>
<div className="flex items-center justify-center text-[18px] mx-auto mb-2" style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(168,85,247,.15)' }}>💨</div>
<div className="text-[13px] font-mono font-extrabold text-primary-purple">G</div>
<div className="text-[11px] font-bold my-1">Gas</div>
<div className="text-[8px] text-text-2 leading-normal"> . </div>
<div className="mt-1.5 text-[7px] font-semibold text-primary-purple p-[3px]" style={{ background: 'rgba(168,85,247,.08)', borderRadius: 3 }}> </div>
</div>
{/* E: Evaporator */}
<div className="p-3 rounded-md text-center" style={{ background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.2)' }}>
<div className="flex items-center justify-center text-[18px] mx-auto mb-2" style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(239,68,68,.15)' }}>🔥</div>
<div className="text-[13px] font-mono font-extrabold text-status-red">E</div>
<div className="text-[11px] font-bold my-1">Evaporator</div>
<div className="text-[8px] text-text-2 leading-normal"> . </div>
<div className="mt-1.5 text-[7px] font-semibold text-status-red p-[3px]" style={{ background: 'rgba(239,68,68,.08)', borderRadius: 3 }}>+ </div>
</div>
{/* F: Floater */}
<div className="p-3 rounded-md text-center" style={{ background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.2)' }}>
<div className="flex items-center justify-center text-[18px] mx-auto mb-2" style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(234,179,8,.15)' }}>🟡</div>
<div className="text-[13px] font-mono font-extrabold text-status-yellow">F</div>
<div className="text-[11px] font-bold my-1">Floater</div>
<div className="text-[8px] text-text-2 leading-normal">{'해수면 위에 부유. 비중 < 1.0, 불용성 물질'}</div>
<div className="mt-1.5 text-[7px] font-semibold text-status-yellow p-[3px]" style={{ background: 'rgba(234,179,8,.08)', borderRadius: 3 }}> </div>
</div>
{/* D: Dissolver */}
<div className="p-3 rounded-md text-center" style={{ background: 'rgba(6,182,212,.06)', border: '1px solid rgba(6,182,212,.2)' }}>
<div className="flex items-center justify-center text-[18px] mx-auto mb-2" style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(6,182,212,.15)' }}>💧</div>
<div className="text-[13px] font-mono font-extrabold text-primary-cyan">D</div>
<div className="text-[11px] font-bold my-1">Dissolver</div>
<div className="text-[8px] text-text-2 leading-normal"> . </div>
<div className="mt-1.5 text-[7px] font-semibold text-primary-cyan p-[3px]" style={{ background: 'rgba(6,182,212,.08)', borderRadius: 3 }}> </div>
</div>
{/* S: Sinker */}
<div className="p-3 rounded-md text-center" style={{ background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.2)' }}>
<div className="flex items-center justify-center text-[18px] mx-auto mb-2" style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(34,197,94,.15)' }}></div>
<div className="text-[13px] font-mono font-extrabold text-status-green">S</div>
<div className="text-[11px] font-bold my-1">Sinker</div>
<div className="text-[8px] text-text-2 leading-normal">{'해저로 침강. 비중 > 1.0, 저층 오염 축적'}</div>
<div className="mt-1.5 text-[7px] font-semibold text-status-green p-[3px]" style={{ background: 'rgba(34,197,94,.08)', borderRadius: 3 }}> 3D </div>
</div>
</div>
{/* 복합 거동 */}
<div className="rounded-md p-3 border border-border bg-bg-3">
<div className="text-[10px] font-bold mb-2">🔀 </div>
<div className="grid text-center text-[8px]" style={{ gridTemplateColumns: 'repeat(5,1fr)', gap: 6 }}>
<div className="rounded p-1.5 bg-bg-0"><span className="font-bold text-primary-purple">GD</span><br/><span className="text-text-3">+</span></div>
<div className="rounded p-1.5 bg-bg-0"><span className="font-bold text-status-red">ED</span><br/><span className="text-text-3">+</span></div>
<div className="rounded p-1.5 bg-bg-0"><span className="font-bold text-status-yellow">FE</span><br/><span className="text-text-3">+</span></div>
<div className="rounded p-1.5 bg-bg-0"><span className="font-bold text-primary-cyan">FED</span><br/><span className="text-text-3">++</span></div>
<div className="rounded p-1.5 bg-bg-0"><span className="font-bold text-status-green">SD</span><br/><span className="text-text-3">+</span></div>
</div>
</div>
</div>
</div>
)}
{/* ═══ MAT PANEL 1: 주요 물질 특성 ═══ */}
{activeTab === 1 && (
<div>
{/* 카테고리 필터 */}
<div className="flex gap-1.5 mb-[14px]" data-html2pdf-ignore>
{categories.map(cat => (
<button key={cat.id} onClick={() => setSelectedCategory(cat.id)}
className="rounded-sm text-[10px] font-semibold cursor-pointer"
style={{
padding: '6px 12px',
border: selectedCategory === cat.id ? '1px solid var(--orange)' : '1px solid var(--bd)',
color: selectedCategory === cat.id ? 'var(--orange)' : 'var(--t3)',
background: selectedCategory === cat.id ? 'rgba(249,115,22,.08)' : 'var(--bg3)',
}}
>{cat.icon} {cat.label}</button>
))}
</div>
{/* 주요 HNS 물질 카드 */}
<div className="grid grid-cols-2 gap-[14px] mb-4">
{/* 암모니아 */}
{filtered.find(s => s.casNumber === '7664-41-7') && (
<div className="rounded-[10px] p-[14px] border border-border bg-bg-3" style={{ borderLeft: '4px solid var(--purple)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono font-extrabold text-primary-purple">NH</span> <span className="text-xs font-bold"></span></div>
<div className="flex gap-1">
<span className="text-[8px] font-semibold text-primary-purple px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(168,85,247,.1)' }}>G/GD</span>
<span className="text-[8px] font-semibold text-status-red px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(239,68,68,.1)' }}></span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">CAS:</span> <span className="font-mono">7664-41-7</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">17.03</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan">-33.4°C</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">0.73</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">N/A ()</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan"> </span></div>
</div>
<div className="grid text-center text-[7px]" style={{ gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }}>
<div style={{ padding: 4, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 3 }}><span className="text-status-green">AEGL-2</span><br/><b>160 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(249,115,22,.06)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 3 }}><span className="text-status-orange">ERPG-2</span><br/><b>150 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 3 }}><span className="text-status-red">IDLH</span><br/><b>300 ppm</b></div>
</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal"> . . .</div>
</div>
)}
{/* 메탄올 */}
{filtered.find(s => s.casNumber === '67-56-1') && (
<div className="rounded-[10px] p-[14px] border border-border bg-bg-3" style={{ borderLeft: '4px solid var(--cyan)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono font-extrabold text-primary-cyan">CHOH</span> <span className="text-xs font-bold"></span></div>
<div className="flex gap-1">
<span className="text-[8px] font-semibold text-primary-cyan px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}>ED</span>
<span className="text-[8px] font-semibold text-status-orange px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(249,115,22,.1)' }}></span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">CAS:</span> <span className="font-mono">67-56-1</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">32.04</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan">64.7°C</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">0.79</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-status-orange">11°C</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan"> </span></div>
</div>
<div className="grid text-center text-[7px]" style={{ gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }}>
<div style={{ padding: 4, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 3 }}><span className="text-status-green">AEGL-2</span><br/><b>2,100 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(249,115,22,.06)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 3 }}><span className="text-status-orange">ERPG-2</span><br/><b>1,000 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 3 }}><span className="text-status-red">IDLH</span><br/><b>6,000 ppm</b></div>
</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal"> . . . 2007 FODDANGER호 95L .</div>
</div>
)}
{/* 수소 */}
{filtered.find(s => s.casNumber === '1333-74-0') && (
<div className="rounded-[10px] p-[14px] border border-border bg-bg-3" style={{ borderLeft: '4px solid var(--red)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono font-extrabold text-status-red">H</span> <span className="text-xs font-bold"></span></div>
<div className="flex gap-1">
<span className="text-[8px] font-semibold text-primary-purple px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(168,85,247,.1)' }}>G</span>
<span className="text-[8px] font-semibold text-status-red px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(239,68,68,.1)' }}></span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">CAS:</span> <span className="font-mono">1333-74-0</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">2.016</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan">-252.9°C</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">():</span> <span className="font-mono">0.07</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">LFL:</span> <span className="font-mono text-status-red">4.0%</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">UFL:</span> <span className="font-mono text-status-red">75.0%</span></div>
</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal"> (4~75%). · . BLEVE . .</div>
</div>
)}
{/* LNG */}
{filtered.find(s => s.nameEn === 'LPG (Propane/Butane)' || s.casNumber === '74-82-8') && (
<div className="rounded-[10px] p-[14px] border border-border bg-bg-3" style={{ borderLeft: '4px solid var(--orange)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono font-extrabold text-status-orange">CH</span> <span className="text-xs font-bold">LNG ()</span></div>
<div className="flex gap-1">
<span className="text-[8px] font-semibold text-primary-purple px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(168,85,247,.1)' }}>G</span>
<span className="text-[8px] font-semibold text-status-orange px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(249,115,22,.1)' }}>/</span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">CAS:</span> <span className="font-mono">74-82-8</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">16.04</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan">-161.5°C</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">():</span> <span className="font-mono">0.42</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">LFL:</span> <span className="font-mono text-status-orange">5.0%</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">UFL:</span> <span className="font-mono text-status-orange">15.0%</span></div>
</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal">(-162°C) RPT(), Pool Fire . Flash . · LNG .</div>
</div>
)}
{/* 페놀 */}
{filtered.find(s => s.casNumber === '108-95-2' || s.name === '페놀') && (
<div className="rounded-[10px] p-[14px] border border-border bg-bg-3" style={{ borderLeft: '4px solid var(--green)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono font-extrabold text-status-green">CHOH</span> <span className="text-xs font-bold"></span></div>
<div className="flex gap-1">
<span className="text-[8px] font-semibold text-status-green px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(34,197,94,.1)' }}>S/SD</span>
<span className="text-[8px] font-semibold text-status-red px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(239,68,68,.1)' }}></span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">CAS:</span> <span className="font-mono">108-95-2</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">94.11</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">181.7°C</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-status-green">1.07 ()</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-status-orange">79°C</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan">84 g/L</span></div>
</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal"> 1.07 <b className="text-status-green">Sinker </b> . ROMS 3.5. HNS (31.8kg/).</div>
</div>
)}
{/* 톨루엔 */}
{filtered.find(s => s.casNumber === '108-88-3') && (
<div className="rounded-[10px] p-[14px] border border-border bg-bg-3" style={{ borderLeft: '4px solid var(--yellow)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono font-extrabold text-status-yellow">CH</span> <span className="text-xs font-bold"></span></div>
<div className="flex gap-1">
<span className="text-[8px] font-semibold text-status-yellow px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(234,179,8,.1)' }}>FE</span>
<span className="text-[8px] font-semibold text-status-orange px-1.5 py-0.5" style={{ borderRadius: 3, background: 'rgba(249,115,22,.1)' }}></span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">CAS:</span> <span className="font-mono">108-88-3</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">92.14</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">110.6°C</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-status-yellow">0.87 ()</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono text-status-orange">4°C</span></div>
<div className="rounded bg-bg-0 px-1.5 py-1"><span className="text-text-3">:</span> <span className="font-mono">0.52 g/L</span></div>
</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal"> . (4°C) . HNS. .</div>
</div>
)}
</div>
</div>
)}
{/* ═══ MAT PANEL 2: 위험도 기준 ═══ */}
{activeTab === 2 && (
<div>
<div className="grid grid-cols-2 gap-4 mb-4">
{/* AEGL 기준 */}
<div className="rounded-[10px] p-4 border border-border bg-bg-3" style={{ borderTop: '3px solid var(--green)' }}>
<div className="text-[13px] font-bold text-status-green mb-[10px]">🟢 AEGL (Acute Exposure Guideline Level)</div>
<div className="text-[9px] text-text-3 mb-[10px]"> EPA (10, 30, 60, 4, 8)</div>
<div className="flex flex-col gap-1.5">
<div className="rounded-sm px-2.5 py-2" style={{ background: 'rgba(34,197,94,.04)', border: '1px solid rgba(34,197,94,.12)', borderLeft: '4px solid var(--green)' }}>
<div className="text-[11px] font-bold text-status-green">AEGL-1 ()</div>
<div className="text-[9px] text-text-2 leading-normal"> , . .</div>
</div>
<div className="rounded-sm px-2.5 py-2" style={{ background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderLeft: '4px solid var(--orange)' }}>
<div className="text-[11px] font-bold text-status-orange">AEGL-2 ( )</div>
<div className="text-[9px] text-text-2 leading-normal">· . <b className="text-status-orange"> </b>.</div>
</div>
<div className="rounded-sm px-2.5 py-2" style={{ background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.12)', borderLeft: '4px solid var(--red)' }}>
<div className="text-[11px] font-bold text-status-red">AEGL-3 ( )</div>
<div className="text-[9px] text-text-2 leading-normal"> . <b className="text-status-red"> · </b>.</div>
</div>
</div>
</div>
{/* ERPG / IDLH */}
<div className="rounded-[10px] p-4 border border-border bg-bg-3" style={{ borderTop: '3px solid var(--red)' }}>
<div className="text-[13px] font-bold text-status-red mb-[10px]">🔴 ERPG &amp; IDLH</div>
<div className="flex flex-col gap-2">
<div>
<div className="text-[10px] font-bold text-status-orange mb-1">ERPG (Emergency Response Planning Guideline)</div>
<div className="text-[9px] text-text-3 mb-1.5">AIHA 1 </div>
<div className="flex flex-col gap-1">
<div className="text-[8px] rounded px-2 py-1.5" style={{ background: 'rgba(34,197,94,.03)', border: '1px solid rgba(34,197,94,.1)', borderLeft: '3px solid var(--green)' }}><b className="text-status-green">ERPG-1</b> , </div>
<div className="text-[8px] rounded px-2 py-1.5" style={{ background: 'rgba(249,115,22,.03)', border: '1px solid rgba(249,115,22,.1)', borderLeft: '3px solid var(--orange)' }}><b className="text-status-orange">ERPG-2</b> , </div>
<div className="text-[8px] rounded px-2 py-1.5" style={{ background: 'rgba(239,68,68,.03)', border: '1px solid rgba(239,68,68,.1)', borderLeft: '3px solid var(--red)' }}><b className="text-status-red">ERPG-3</b> </div>
</div>
</div>
<div className="mt-1 rounded-sm p-2.5" style={{ background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.15)' }}>
<div className="text-[10px] font-bold text-status-red mb-1">IDLH (Immediately Dangerous to Life or Health)</div>
<div className="text-[9px] text-text-3 mb-1">NIOSH 30 · </div>
<div className="text-[9px] text-text-2 leading-normal"><b className="text-status-red"> </b> (SCBA) . WING .</div>
</div>
</div>
</div>
</div>
{/* 물질별 위험도 비교표 */}
<div className="rounded-[10px] p-4 border border-border bg-bg-3">
<div className="text-xs font-bold mb-3">📊 HNS (ppm)</div>
<table className="w-full border-collapse text-[9px]">
<thead>
<tr style={{ background: 'rgba(168,85,247,.06)' }}>
<th className="text-primary-purple text-left" style={{ padding: '7px 8px', borderBottom: '2px solid var(--bdL)' }}></th>
<th className="text-status-green text-center" style={{ padding: '7px 8px', borderBottom: '2px solid var(--bdL)' }}>AEGL-1</th>
<th className="text-status-orange text-center" style={{ padding: '7px 8px', borderBottom: '2px solid var(--bdL)' }}>AEGL-2</th>
<th className="text-status-red text-center" style={{ padding: '7px 8px', borderBottom: '2px solid var(--bdL)' }}>AEGL-3</th>
<th className="text-status-orange text-center" style={{ padding: '7px 8px', borderBottom: '2px solid var(--bdL)' }}>ERPG-2</th>
<th className="text-status-red text-center" style={{ padding: '7px 8px', borderBottom: '2px solid var(--bdL)' }}>IDLH</th>
<th className="text-status-orange text-center" style={{ padding: '7px 8px', borderBottom: '2px solid var(--bdL)' }}>LFL(%)</th>
<th className="text-text-2 text-center" style={{ padding: '7px 8px', borderBottom: '2px solid var(--bdL)' }}>SEBC</th>
</tr>
</thead>
<tbody>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td className="font-semibold text-primary-purple py-1.5 px-2">NH </td>
<td className="text-center font-mono text-status-green">30</td>
<td className="text-center font-mono text-status-orange">160</td>
<td className="text-center font-mono text-status-red">1,100</td>
<td className="text-center font-mono">150</td>
<td className="text-center font-mono text-status-red">300</td>
<td className="text-center font-mono">15.0</td>
<td className="text-center font-semibold text-primary-purple">G/GD</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td className="font-semibold text-primary-cyan py-1.5 px-2">CHOH </td>
<td className="text-center font-mono text-status-green">530</td>
<td className="text-center font-mono text-status-orange">2,100</td>
<td className="text-center font-mono text-status-red">14,000</td>
<td className="text-center font-mono">1,000</td>
<td className="text-center font-mono text-status-red">6,000</td>
<td className="text-center font-mono">6.0</td>
<td className="text-center font-semibold text-primary-cyan">ED</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td className="font-semibold text-status-red py-1.5 px-2">H </td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center font-mono font-bold text-status-red">4.0</td>
<td className="text-center font-semibold text-primary-purple">G</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td className="font-semibold text-status-orange py-1.5 px-2">CH LNG</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center font-mono text-status-orange">5.0</td>
<td className="text-center font-semibold text-primary-purple">G</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td className="font-semibold text-status-green py-1.5 px-2">CHOH </td>
<td className="text-center font-mono text-status-green">19</td>
<td className="text-center font-mono text-status-orange">29</td>
<td className="text-center font-mono text-status-red">57</td>
<td className="text-center font-mono">50</td>
<td className="text-center font-mono text-status-red">250</td>
<td className="text-center font-mono">1.8</td>
<td className="text-center font-semibold text-status-green">S/SD</td>
</tr>
<tr>
<td className="font-semibold text-status-yellow py-1.5 px-2">CH </td>
<td className="text-center font-mono text-status-green">67</td>
<td className="text-center font-mono text-status-orange">560</td>
<td className="text-center font-mono text-status-red">3,700</td>
<td className="text-center font-mono">300</td>
<td className="text-center font-mono text-status-red">500</td>
<td className="text-center font-mono">1.1</td>
<td className="text-center font-semibold text-status-yellow">FE</td>
</tr>
</tbody>
</table>
<div className="mt-2 text-[7px] text-text-3"> AEGL: 60분 / ERPG: 1시간 / IDLH: 30분 / LFL: 폭발하한</div>
</div>
</div>
)}
{/* ═══ MAT PANEL 3: 물질 상세검색 ═══ */}
{activeTab === 3 && (
<div>
{/* ── 검색창 ── */}
<div className="rounded-[10px] p-4 mb-4 border border-border bg-bg-3">
<div className="flex items-center justify-between mb-[14px]">
<div className="text-[13px] font-bold">🔍 HNS <span className="text-[9px] font-normal text-text-3">(···CAS·UN번호 )</span></div>
<div className="flex gap-1">
<button className="text-[9px] font-semibold cursor-pointer text-status-orange rounded px-2.5 py-[5px]" style={{ border: '1px solid rgba(249,115,22,.25)', background: 'rgba(249,115,22,.08)' }}>📥 DB </button>
<button className="text-[9px] font-semibold cursor-pointer text-primary-cyan rounded px-2.5 py-[5px]" style={{ border: '1px solid rgba(6,182,212,.25)', background: 'rgba(6,182,212,.08)' }}>🔗 Port-MIS </button>
</div>
</div>
<div className="flex gap-2 mb-[10px] items-center">
<div className="shrink-0 flex items-center gap-1">
<span className="text-[9px] font-semibold text-text-3">:</span>
<select value={hmsSearchType} onChange={e => { setHmsSearchType(e.target.value as typeof hmsSearchType); setHmsPage(1) }} className="rounded-sm border border-border text-[12px] outline-none bg-bg-0 px-3 py-2" style={{ minWidth: 140 }}>
<option value="all"> </option>
<option value="abbr">/</option>
<option value="korName"></option>
<option value="engName"></option>
<option value="cas">CAS번호</option>
<option value="un">UN번호</option>
</select>
</div>
<input type="text" value={hmsSearchInput} onChange={e => { 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-border text-[13px] outline-none bg-bg-0 px-3 py-2" />
<button onClick={() => setHmsPage(1)} className="text-[13px] font-bold cursor-pointer shrink-0 rounded-sm text-white px-5 py-2" style={{ border: 'none', background: 'linear-gradient(135deg,var(--orange),var(--red))' }}>🔎 </button>
</div>
<div className="text-[8px] text-text-3 leading-[1.6]">
· <b className="text-status-orange"> </b> &nbsp;|&nbsp; / <b className="text-status-orange">, </b> &nbsp;|&nbsp; <b className="text-primary-cyan">1,222</b>
</div>
</div>
{/* ── 검색 결과 테이블 ── */}
<div className="rounded-[10px] p-4 mb-4 border border-border bg-bg-3">
<div className="flex items-center justify-between mb-[10px]">
<div className="text-[13px] font-bold">📋 <span className="text-[11px] font-normal text-text-3"> {hmsTotal} </span></div>
<select value={hmsFilterSebc} onChange={e => { setHmsFilterSebc(e.target.value); setHmsPage(1) }} className="rounded border border-border text-[11px] text-text-2 outline-none bg-bg-0 px-2.5 py-1">
<option> </option><option>G (Gas)</option><option>E (Evaporator)</option><option>F (Floater)</option><option>D (Dissolver)</option><option>S (Sinker)</option>
</select>
</div>
<div className="overflow-x-auto">
<table className="w-full border-collapse text-[11px]">
<thead>
<tr style={{ background: 'rgba(249,115,22,.06)' }}>
<th className="text-status-orange text-left" style={{ padding: '8px 8px', borderBottom: '2px solid var(--bdL)', width: 36 }}>No.</th>
<th className="text-status-orange text-left" style={{ padding: '8px 8px', borderBottom: '2px solid var(--bdL)' }}>/</th>
<th className="text-text-2 text-left" style={{ padding: '8px 8px', borderBottom: '2px solid var(--bdL)' }}></th>
<th className="text-text-2 text-left" style={{ padding: '8px 8px', borderBottom: '2px solid var(--bdL)' }}> </th>
<th className="text-primary-cyan text-left" style={{ padding: '8px 8px', borderBottom: '2px solid var(--bdL)' }}></th>
<th className="text-text-2 text-left text-[10px]" style={{ padding: '8px 8px', borderBottom: '2px solid var(--bdL)' }}> / </th>
<th className="text-text-2 text-center" style={{ padding: '8px 8px', borderBottom: '2px solid var(--bdL)', width: 68 }}>UN번호</th>
<th className="text-text-2 text-center" style={{ padding: '8px 8px', borderBottom: '2px solid var(--bdL)', width: 80 }}>CAS번호</th>
</tr>
</thead>
<tbody>
{hmsLoading ? (
<tr><td colSpan={8} className="text-center text-text-3 py-5 px-2"> ...</td></tr>
) : hmsPageData.length > 0 ? hmsPageData.map((s: HNSSearchSubstance, idx: number) => {
const isSel = hmsSelectedId === s.id
return (
<tr key={s.id} onClick={() => { 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 = '' }}
>
<td className="font-mono text-text-3 px-2 py-2">{(hmsPage - 1) * HMS_PER_PAGE + idx + 1}</td>
<td className="font-semibold font-mono text-status-orange px-2 py-2">{s.abbreviation}</td>
<td className="px-2 py-2">{s.nameEn}</td>
<td className="text-text-3 text-[10px] px-2 py-2">{s.synonymsEn}</td>
<td className="font-semibold px-2 py-2"><span className="text-primary-cyan underline cursor-pointer" onClick={e => { e.stopPropagation(); setHmsSelectedId(s.id); setHmsDetailTab(0) }}>{s.nameKr}</span></td>
<td className="text-text-3 text-[10px] px-2 py-2">{s.synonymsKr}</td>
<td className="text-center font-mono">{s.unNumber}</td>
<td className="text-center font-mono">{s.casNumber}</td>
</tr>
)
}) : (
<tr><td colSpan={8} className="text-center text-text-3 py-5 px-2"> .</td></tr>
)}
</tbody>
</table>
</div>
<div className="mt-[10px] text-center text-[11px] text-text-3">
<div className="flex items-center justify-center gap-1 mb-1.5">
<button onClick={() => setHmsPage(1)} disabled={hmsPage <= 1} className="rounded cursor-pointer border border-border text-[11px] text-text-2 bg-bg-0 px-2.5 py-1" style={{ opacity: hmsPage <= 1 ? 0.4 : 1 }}></button>
<button onClick={() => setHmsPage(p => Math.max(1, p - 1))} disabled={hmsPage <= 1} className="rounded cursor-pointer border border-border text-[11px] text-text-2 bg-bg-0 px-3 py-1" style={{ opacity: hmsPage <= 1 ? 0.4 : 1 }}></button>
{(() => {
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 => (
<button key={p} onClick={() => setHmsPage(p)} className="rounded cursor-pointer border border-border text-[11px] px-3 py-1" style={{ background: p === hmsPage ? 'rgba(249,115,22,.15)' : 'var(--bg0)', color: p === hmsPage ? 'var(--orange)' : 'var(--t2)', fontWeight: p === hmsPage ? 700 : 400 }}>{p}</button>
));
})()}
<button onClick={() => setHmsPage(p => Math.min(hmsTotalPages, p + 1))} disabled={hmsPage >= hmsTotalPages} className="rounded cursor-pointer border border-border text-[11px] text-text-2 bg-bg-0 px-3 py-1" style={{ opacity: hmsPage >= hmsTotalPages ? 0.4 : 1 }}></button>
<button onClick={() => setHmsPage(hmsTotalPages)} disabled={hmsPage >= hmsTotalPages} className="rounded cursor-pointer border border-border text-[11px] text-text-2 bg-bg-0 px-2.5 py-1" style={{ opacity: hmsPage >= hmsTotalPages ? 0.4 : 1 }}></button>
<span className="ml-2 text-text-3">{hmsPage} / {hmsTotalPages} </span>
</div>
<span> <b className="text-status-orange">{hmsTotal.toLocaleString()}</b> </span>
</div>
</div>
{/* ── 물질 상세정보 패널 ── */}
{hmsSelectedSubstance && <HmsDetailPanel substance={hmsSelectedSubstance} activeTab={hmsDetailTab} onTabChange={setHmsDetailTab} />}
</div>
)}
</div>
</div>
)
}
/* ═══ 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 (
<div className="rounded-[10px] overflow-hidden bg-bg-3" style={{ border: '1px solid rgba(6,182,212,.25)', boxShadow: '0 4px 20px rgba(6,182,212,.08)' }}>
{/* Tab Navigation */}
<div className="flex border-b border-border" style={{ background: 'linear-gradient(90deg,rgba(6,182,212,.06),rgba(249,115,22,.04))' }}>
{tabLabels.map((label, i) => (
<button key={i} onClick={() => onTabChange(i)} style={{ flex: 1, padding: 10, fontSize: 10, fontWeight: activeTab === i ? 700 : 600, cursor: 'pointer', border: 'none', borderBottom: `2px solid ${activeTab === i ? 'var(--cyan)' : 'transparent'}`, background: activeTab === i ? 'rgba(6,182,212,.06)' : 'transparent', color: activeTab === i ? 'var(--cyan)' : 'var(--t3)' }}>{label}</button>
))}
</div>
{/* TAB 0: 물질특성·위험정보 */}
{activeTab === 0 && (
<div className="p-4">
{/* Header */}
<div className="flex items-center gap-[14px] mb-4 pb-[14px] border-b border-border">
<div className="flex items-center justify-center text-[22px] shrink-0" style={{ width: 52, height: 52, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.15),rgba(249,115,22,.1))', border: '1px solid rgba(6,182,212,.25)' }}>🧪</div>
<div className="flex-1">
<div style={{ fontSize: 18, fontWeight: 800 }}>{s.nameKr} <span className="text-xs font-normal text-text-3">({s.nameEn})</span></div>
<div className="flex flex-wrap gap-1.5 mt-1.5">
<span className="text-[9px] font-semibold font-mono text-primary-cyan rounded py-[2px] px-2" style={{ background: 'rgba(6,182,212,.1)', border: '1px solid rgba(6,182,212,.2)' }}>CAS: {s.casNumber}</span>
<span className="text-[9px] font-semibold font-mono text-status-orange rounded py-[2px] px-2" style={{ background: 'rgba(249,115,22,.1)', border: '1px solid rgba(249,115,22,.2)' }}>UN: {s.unNumber}</span>
<span className="text-[9px] font-semibold text-primary-purple rounded py-[2px] px-2" style={{ background: 'rgba(168,85,247,.1)', border: '1px solid rgba(168,85,247,.2)' }}>: {s.transportMethod}</span>
<span className="text-[9px] font-semibold text-status-green rounded py-[2px] px-2" style={{ background: 'rgba(34,197,94,.1)', border: '1px solid rgba(34,197,94,.2)' }}>SEBC: {s.sebc}</span>
</div>
<div className="text-[9px] text-text-3 mt-1"><b>:</b> {s.synonymsKr} &nbsp;|&nbsp; <b>:</b> {s.hazardClass}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-[14px]">
{/* Left: 물리·화학적 특성 */}
<div>
<div className="text-[11px] font-bold text-status-orange mb-2"> · </div>
<div className="grid grid-cols-2 gap-1 text-[9px]">
{([
['용도', 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]) => (
<div key={label} style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: 4, borderLeft: `3px solid ${borderColor}` }}>
<span className="text-text-3">{label}</span><br />
<b style={{ color: label.includes('인화') || label.includes('폭발') ? 'var(--red)' : label.includes('용해') ? 'var(--cyan)' : 'var(--t1)' }}>{value}</b>
</div>
))}
</div>
</div>
{/* Right: NFPA + 위험등급 */}
<div>
<div className="text-[11px] font-bold text-status-red mb-2"> ·</div>
<div className="flex gap-[14px] items-start mb-3">
<div className="relative shrink-0" style={{ width: 80, height: 80 }}>
<svg viewBox="0 0 100 100" width={80} height={80}>
<polygon points="50,5 95,50 50,95 5,50" fill="none" stroke="rgba(255,255,255,.15)" strokeWidth={1.5} />
<polygon points="50,5 72,28 50,50 28,28" fill="rgba(239,68,68,.2)" stroke="rgba(239,68,68,.5)" strokeWidth={1} />
<text x={50} y={32} textAnchor="middle" fill="#f87171" fontSize={16} fontWeight={800} fontFamily="monospace">{nfpa.health}</text>
<polygon points="72,28 95,50 72,72 50,50" fill="rgba(234,179,8,.2)" stroke="rgba(234,179,8,.5)" strokeWidth={1} />
<text x={77} y={55} textAnchor="middle" fill="#fbbf24" fontSize={16} fontWeight={800} fontFamily="monospace">{nfpa.fire}</text>
<polygon points="50,50 72,72 50,95 28,72" fill="rgba(255,255,255,.08)" stroke="rgba(255,255,255,.2)" strokeWidth={1} />
<text x={50} y={78} textAnchor="middle" fill="#e2e8f0" fontSize={12} fontWeight={700} fontFamily="sans-serif">{nfpa.special}</text>
<polygon points="5,50 28,28 50,50 28,72" fill="rgba(59,130,246,.2)" stroke="rgba(59,130,246,.5)" strokeWidth={1} />
<text x={23} y={55} textAnchor="middle" fill="#60a5fa" fontSize={16} fontWeight={800} fontFamily="monospace">{nfpa.reactivity}</text>
</svg>
<div className="text-center text-[7px] font-semibold text-text-3 mt-0.5">NFPA 704</div>
</div>
<div className="flex-1 flex flex-col gap-1 text-[8px]">
<div style={{ padding: '4px 8px', background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 4 }}><span style={{ color: 'var(--red)', fontWeight: 700 }}>() {nfpa.health}</span> {nfpa.health >= 4 ? '치명적' : nfpa.health >= 3 ? '중상' : nfpa.health >= 2 ? '장해' : nfpa.health >= 1 ? '경미한 손상' : '무해'}</div>
<div style={{ padding: '4px 8px', background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.12)', borderRadius: 4 }}><span style={{ color: '#fbbf24', fontWeight: 700 }}>() {nfpa.fire}</span> {nfpa.fire >= 4 ? '93°F 미만' : nfpa.fire >= 3 ? '100°F 미만' : nfpa.fire >= 2 ? '200°F 미만' : nfpa.fire >= 1 ? '200°F 이상' : '비가연'}</div>
<div style={{ padding: '4px 8px', background: 'rgba(59,130,246,.06)', border: '1px solid rgba(59,130,246,.12)', borderRadius: 4 }}><span style={{ color: '#60a5fa', fontWeight: 700 }}>() {nfpa.reactivity}</span> {nfpa.reactivity >= 3 ? '폭발 가능' : nfpa.reactivity >= 2 ? '격렬 반응' : nfpa.reactivity >= 1 ? '불안정 가능' : '안정'}</div>
</div>
</div>
<div className="flex flex-col gap-1 text-[9px]">
<InfoBoxRow label="위험 분류" value={s.hazardClass} bg="rgba(239,68,68,.06)" border="rgba(239,68,68,.12)" labelColor="var(--red)" valueColor="var(--red)" />
<InfoBoxRow label="ERG 번호" value={s.ergNumber} bg="rgba(249,115,22,.06)" border="rgba(249,115,22,.12)" labelColor="var(--orange)" valueColor="var(--orange)" />
<InfoBoxRow label="IDLH" value={s.idlh} bg="rgba(168,85,247,.06)" border="rgba(168,85,247,.12)" labelColor="var(--purple)" valueColor="var(--red)" />
<InfoBoxRow label="AEGL-2 (60분)" value={s.aegl2} bg="rgba(34,197,94,.06)" border="rgba(34,197,94,.12)" labelColor="var(--green)" valueColor="var(--orange)" />
<InfoBoxRow label="ERPG-2" value={s.erpg2} bg="rgba(6,182,212,.06)" border="rgba(6,182,212,.12)" labelColor="var(--cyan)" valueColor="var(--orange)" />
</div>
</div>
</div>
</div>
)}
{/* TAB 1: 방제거리·PPE·MSDS */}
{activeTab === 1 && (
<div className="p-4">
<div className="grid grid-cols-2 gap-[14px]">
{/* 방제거리 */}
<div className="rounded-md overflow-hidden" style={{ border: '1px solid rgba(239,68,68,.2)' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(239,68,68,.08),transparent)', borderBottom: '1px solid rgba(239,68,68,.12)' }}>
<div className="text-xs font-bold text-status-red">🚧 (ERG {s.ergNumber})</div>
</div>
<div className="p-3 flex flex-col gap-2">
<div className="rounded-sm" style={{ padding: 10, background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)' }}>
<div className="text-[10px] font-bold text-status-orange mb-1">🔥 </div>
<div className="text-[9px] text-text-2 leading-[1.6]">: <b className="text-status-red">{s.responseDistanceFire}</b> </div>
</div>
<div className="rounded-sm" style={{ padding: 10, background: 'rgba(168,85,247,.04)', border: '1px solid rgba(168,85,247,.12)' }}>
<div className="text-[10px] font-bold text-primary-purple mb-1">💨 ()</div>
<div className="text-[9px] text-text-2 leading-[1.6]"> : <b className="text-primary-purple">{s.responseDistanceSpillDay}</b><br /> : <b className="text-status-red">{s.responseDistanceSpillNight}</b></div>
</div>
<div className="rounded-sm" style={{ padding: 10, background: 'rgba(6,182,212,.04)', border: '1px solid rgba(6,182,212,.12)' }}>
<div className="text-[10px] font-bold text-primary-cyan mb-1">🌊 </div>
<div className="text-[9px] text-text-2 leading-[1.6]">{s.marineResponse}</div>
</div>
</div>
</div>
<div>
{/* PPE */}
<div className="rounded-md overflow-hidden mb-3" style={{ border: '1px solid rgba(34,197,94,.2)' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(34,197,94,.08),transparent)', borderBottom: '1px solid rgba(34,197,94,.12)' }}>
<div className="text-xs font-bold text-status-green">🛡 (PPE) </div>
</div>
<div className="grid grid-cols-2 gap-1.5 text-[9px] p-3">
<div className="text-center rounded" style={{ padding: 8, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.1)' }}>
<div className="text-base mb-[3px]">🧑🚒</div>
<div className="font-bold text-status-red"></div>
<div className="text-[8px] text-text-3 mt-0.5">{s.ppeClose}</div>
</div>
<div className="text-center rounded" style={{ padding: 8, background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.1)' }}>
<div className="text-base mb-[3px]">🦺</div>
<div className="font-bold text-status-orange"></div>
<div className="text-[8px] text-text-3 mt-0.5">{s.ppeFar}</div>
</div>
</div>
</div>
{/* MSDS */}
<div className="rounded-md overflow-hidden" style={{ border: '1px solid rgba(59,130,246,.2)' }}>
<div className="flex items-center justify-between" style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(59,130,246,.08),transparent)', borderBottom: '1px solid rgba(59,130,246,.12)' }}>
<div className="text-xs font-bold" style={{ color: '#60a5fa' }}>📄 MSDS </div>
<button className="text-[8px] font-semibold cursor-pointer rounded" style={{ padding: '3px 10px', background: 'rgba(59,130,246,.1)', border: '1px solid rgba(59,130,246,.2)', color: '#60a5fa' }}>📥 </button>
</div>
<div className="text-[8px] text-text-2 leading-[1.7] p-2.5">
<b>§2 ·:</b> {s.msds.hazard}<br />
<b>§4 :</b> {s.msds.firstAid}<br />
<b>§5 :</b> {s.msds.fireFighting}<br />
<b>§6 :</b> {s.msds.spillResponse}<br />
<b>§8 :</b> {s.msds.exposure}<br />
<b>§15 :</b> {s.msds.regulation}
</div>
</div>
</div>
</div>
</div>
)}
{/* TAB 2: IBC CODE·EmS 대응 */}
{activeTab === 2 && (
<div className="p-4">
<div className="grid grid-cols-2 gap-[14px]">
{/* IBC CODE */}
<div className="rounded-md overflow-hidden" style={{ border: '1px solid rgba(249,115,22,.2)' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(249,115,22,.08),transparent)', borderBottom: '1px solid rgba(249,115,22,.12)' }}>
<div className="text-xs font-bold text-status-orange"> IBC CODE </div>
</div>
<div className="p-3 flex flex-col gap-2 text-[9px]">
<div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 4 }}>
{([
['위험성', s.ibcHazard],
['선박형식', s.ibcShipType],
['탱크형식', s.ibcTankType],
['탐지장비', s.ibcDetection],
['소화설비', s.ibcFireFighting],
['최소적재요건', s.ibcMinRequirement],
] as [string, string][]).map(([label, value]) => (
<React.Fragment key={label}>
<div className="rounded text-text-3" style={{ padding: '6px 8px', background: 'var(--bg0)' }}>{label}</div>
<div className="rounded font-semibold" style={{ padding: '6px 8px', background: 'var(--bg0)' }}>{value}</div>
</React.Fragment>
))}
</div>
{/* Tank diagram SVG */}
<div className="rounded-sm text-center" style={{ padding: 10, background: 'var(--bg0)' }}>
<svg viewBox="0 0 200 80" width={200} height={80} style={{ display: 'inline-block' }}>
<rect x={10} y={10} width={180} height={50} rx={4} fill="none" stroke="rgba(249,115,22,.4)" strokeWidth={1.5} />
<line x1={70} y1={10} x2={70} y2={60} stroke="rgba(249,115,22,.2)" strokeWidth={1} strokeDasharray="3" />
<line x1={130} y1={10} x2={130} y2={60} stroke="rgba(249,115,22,.2)" strokeWidth={1} strokeDasharray="3" />
<text x={40} y={38} textAnchor="middle" fill="var(--orange)" fontSize={7} fontFamily="var(--fK)">CARGO</text>
<text x={40} y={48} textAnchor="middle" fill="var(--t3)" fontSize={6} fontFamily="var(--fM)">Tank 1</text>
<text x={100} y={38} textAnchor="middle" fill="var(--orange)" fontSize={7} fontFamily="var(--fK)">CARGO</text>
<text x={100} y={48} textAnchor="middle" fill="var(--t3)" fontSize={6} fontFamily="var(--fM)">Tank 2</text>
<text x={160} y={38} textAnchor="middle" fill="var(--orange)" fontSize={7} fontFamily="var(--fK)">CARGO</text>
<text x={160} y={48} textAnchor="middle" fill="var(--t3)" fontSize={6} fontFamily="var(--fM)">Tank 3</text>
<text x={100} y={75} textAnchor="middle" fill="var(--t3)" fontSize={7} fontFamily="var(--fK)">{s.ibcShipType} {s.ibcTankType}</text>
</svg>
</div>
</div>
</div>
{/* EmS */}
<div className="rounded-md overflow-hidden" style={{ border: '1px solid rgba(239,68,68,.2)' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(239,68,68,.08),transparent)', borderBottom: '1px solid rgba(239,68,68,.12)' }}>
<div className="text-xs font-bold text-status-red">🆘 (EmS) ERG {s.ergNumber}</div>
</div>
<div className="p-3 flex flex-col gap-2 text-[9px]">
<div className="rounded-sm" style={{ padding: 8, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.1)' }}>
<div className="font-bold text-status-red mb-[3px]">🔥 </div>
<div className="text-text-2 leading-[1.6]">{s.emsFire}</div>
</div>
<div className="rounded-sm" style={{ padding: 8, background: 'rgba(168,85,247,.04)', border: '1px solid rgba(168,85,247,.1)' }}>
<div className="font-bold text-primary-purple mb-[3px]">💧 </div>
<div className="text-text-2 leading-[1.6]">{s.emsSpill}</div>
</div>
<div className="rounded-sm" style={{ padding: 8, background: 'rgba(6,182,212,.04)', border: '1px solid rgba(6,182,212,.1)' }}>
<div className="font-bold text-primary-cyan mb-[3px]">🏥 </div>
<div className="text-text-2 leading-[1.6]">{s.emsFirstAid}</div>
</div>
<div className="text-center">
<button className="text-[10px] font-semibold cursor-pointer rounded-sm text-status-red" style={{ padding: '6px 16px', background: 'rgba(239,68,68,.1)', border: '1px solid rgba(239,68,68,.2)' }}>📋 EmS </button>
</div>
</div>
</div>
</div>
</div>
)}
{/* TAB 3: 화물적부도·항구별 코드 */}
{activeTab === 3 && (
<div className="p-4">
<div className="grid grid-cols-2 gap-[14px]">
{/* 화물적부도 */}
<div className="rounded-lg overflow-hidden" style={{ border: '1px solid rgba(168,85,247,.2)' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(168,85,247,.08),transparent)', borderBottom: '1px solid rgba(168,85,247,.12)' }}>
<div className="text-[12px] font-bold text-primary-purple">📋 </div>
<div className="text-[8px] text-text-3"> </div>
</div>
<div className="p-3">
<table className="w-full border-collapse text-[9px]">
<thead><tr style={{ background: 'rgba(168,85,247,.05)' }}>
<th className="text-primary-purple text-left" style={{ padding: '6px 8px', borderBottom: '1px solid var(--bdL)' }}></th>
<th className="text-text-2 text-left" style={{ padding: '6px 8px', borderBottom: '1px solid var(--bdL)' }}>/</th>
<th className="text-text-2 text-left" style={{ padding: '6px 8px', borderBottom: '1px solid var(--bdL)' }}>/</th>
<th className="text-text-2 text-center" style={{ padding: '6px 8px', borderBottom: '1px solid var(--bdL)' }}></th>
</tr></thead>
<tbody>
{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 (
<tr key={i} style={{ borderBottom: i < s.cargoCodes.length - 1 ? '1px solid rgba(255,255,255,.04)' : undefined }}>
<td className="font-mono text-primary-purple font-semibold cursor-pointer py-[5px] px-2">{c.code}</td>
<td className="py-[5px] px-2">{c.name}</td>
<td className="text-text-3 py-[5px] px-2">{c.company}</td>
<td className="text-center"><span style={{ padding: '1px 6px', borderRadius: 3, background: srcBg, color: srcColor, fontSize: 7 }}>{c.source}</span></td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
{/* 항구별 코드 */}
<div className="rounded-lg overflow-hidden" style={{ border: '1px solid rgba(6,182,212,.2)' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(6,182,212,.08),transparent)', borderBottom: '1px solid rgba(6,182,212,.12)' }}>
<div className="text-[12px] font-bold text-primary-cyan">🏗 </div>
<div className="text-[8px] text-text-3">Port-MIS </div>
</div>
<div className="p-3">
<table className="w-full border-collapse text-[9px]">
<thead><tr style={{ background: 'rgba(6,182,212,.05)' }}>
<th className="text-primary-cyan text-left" style={{ padding: '6px 8px', borderBottom: '1px solid var(--bdL)' }}></th>
<th className="text-text-2 text-left" style={{ padding: '6px 8px', borderBottom: '1px solid var(--bdL)' }}></th>
<th className="text-text-2 text-center" style={{ padding: '6px 8px', borderBottom: '1px solid var(--bdL)' }}> </th>
<th className="text-text-2 text-center" style={{ padding: '6px 8px', borderBottom: '1px solid var(--bdL)' }}></th>
</tr></thead>
<tbody>
{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 (
<tr key={i} style={{ borderBottom: i < s.portFrequency.length - 1 ? '1px solid rgba(255,255,255,.04)' : undefined }}>
<td className="font-semibold py-[5px] px-2">{p.port}</td>
<td className="font-mono text-primary-cyan py-[5px] px-2">{p.portCode}</td>
<td className="text-center font-mono">{p.lastImport}</td>
<td className="text-center"><span style={{ padding: '1px 6px', borderRadius: 3, background: freqBg, color: freqColor, fontWeight: 600, fontSize: 8 }}>{p.frequency}</span></td>
</tr>
)
})}
</tbody>
</table>
<div className="mt-2.5 text-center">
<button className="text-primary-cyan text-[10px] font-semibold cursor-pointer rounded-sm" style={{ padding: '6px 16px', background: 'rgba(6,182,212,.1)', border: '1px solid rgba(6,182,212,.2)' }}>🔗 Port-MIS </button>
</div>
</div>
</div>
</div>
</div>
)}
</div>
)
}
function InfoBoxRow({ label, value, bg, border, labelColor, valueColor }: { label: string; value: string; bg: string; border: string; labelColor: string; valueColor: string }) {
return (
<div className="flex items-center justify-between rounded" style={{ padding: '6px 8px', background: bg, border: `1px solid ${border}` }}>
<span><b style={{ color: labelColor }}>{label}</b></span>
<span className="font-mono font-bold" style={{ color: valueColor }}>{value}</span>
</div>
)
}