wing-ops/frontend/src/tabs/hns/components/HNSSubstanceView.tsx

2859 lines
117 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: '#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<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(--bg-base); color: var(--fg-default); 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-base">
<div
ref={contentRef}
className="flex-1 overflow-y-auto p-5"
style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--stroke-light) 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: 'rgba(6,182,212,.08)',
border: '1px solid rgba(6,182,212,.3)',
}}
>
🧬
</div>
<div>
<div className="text-base font-bold">HNS </div>
<div className="text-label-2 text-fg-disabled 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-stroke text-label-2 outline-none bg-bg-card px-3 py-1.5 w-[200px]"
/>
<button
onClick={handleExportPDF}
className="rounded-sm text-label-2 font-semibold cursor-pointer text-color-accent px-3.5 py-1.5"
style={{
border: '1px solid rgba(6,182,212,.3)',
background: 'rgba(6,182,212,.08)',
}}
>
📥 DB
</button>
</div>
</div>
{/* 서브탭 */}
<div
className="flex gap-0.5 rounded-lg p-1 mb-5 bg-bg-card border border-stroke"
data-html2pdf-ignore
>
{tabLabels.map((tab, idx) => (
<button
key={idx}
onClick={() => setActiveTab(idx)}
className={`flex-1 py-2 px-1 text-label-1 font-medium rounded-md transition-all duration-150 cursor-pointer border ${
activeTab === idx
? 'border-stroke-light bg-bg-elevated text-fg'
: 'border-transparent bg-bg-card text-fg-disabled hover:text-fg-sub'
}`}
>
{tab.icon} {tab.label}
</button>
))}
</div>
{/* ═══ MAT PANEL 0: SEBC 거동분류 ═══ */}
{activeTab === 0 && (
<div>
<div
className="rounded-[12px] p-4 mb-4"
style={{
background: 'rgba(6,182,212,.04)',
border: '1px solid rgba(6,182,212,.2)',
}}
>
<div className="flex items-center gap-2 mb-3">
<span className="text-title-4 font-bold text-color-accent">
SEBC
</span>
<span
className="text-[8px] font-semibold text-color-accent py-[2px] px-2 rounded-md"
style={{ background: 'rgba(6,182,212,.1)' }}
>
Standard European Behaviour Classification
</span>
</div>
<div className="text-label-2 text-fg-sub leading-[1.7] mb-[14px]">
HNS {' '}
<b className="text-color-accent">· </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(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.2)',
}}
>
<div
className="flex items-center justify-center text-title-1 mx-auto mb-2"
style={{
width: 36,
height: 36,
borderRadius: '50%',
background: 'rgba(6,182,212,.15)',
}}
>
💨
</div>
<div className="text-title-4 font-mono font-extrabold text-color-accent">G</div>
<div className="text-label-2 font-bold my-1">Gas</div>
<div className="text-[8px] text-fg-sub leading-normal">
.
</div>
<div
className="mt-1.5 text-[7px] font-semibold text-color-accent p-[3px]"
style={{ background: 'rgba(6,182,212,.08)', borderRadius: 3 }}
>
</div>
</div>
{/* E: Evaporator */}
<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-title-1 mx-auto mb-2"
style={{
width: 36,
height: 36,
borderRadius: '50%',
background: 'rgba(6,182,212,.15)',
}}
>
🔥
</div>
<div className="text-title-4 font-mono font-extrabold text-color-accent">E</div>
<div className="text-label-2 font-bold my-1">Evaporator</div>
<div className="text-[8px] text-fg-sub leading-normal">
.
</div>
<div
className="mt-1.5 text-[7px] font-semibold text-color-accent p-[3px]"
style={{ background: 'rgba(6,182,212,.08)', borderRadius: 3 }}
>
+
</div>
</div>
{/* F: Floater */}
<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-title-1 mx-auto mb-2"
style={{
width: 36,
height: 36,
borderRadius: '50%',
background: 'rgba(6,182,212,.15)',
}}
>
🟡
</div>
<div className="text-title-4 font-mono font-extrabold text-color-accent">F</div>
<div className="text-label-2 font-bold my-1">Floater</div>
<div className="text-[8px] text-fg-sub leading-normal">
{'해수면 위에 부유. 비중 < 1.0, 불용성 물질'}
</div>
<div
className="mt-1.5 text-[7px] font-semibold text-color-accent p-[3px]"
style={{ background: 'rgba(6,182,212,.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-title-1 mx-auto mb-2"
style={{
width: 36,
height: 36,
borderRadius: '50%',
background: 'rgba(6,182,212,.15)',
}}
>
💧
</div>
<div className="text-title-4 font-mono font-extrabold text-color-accent">D</div>
<div className="text-label-2 font-bold my-1">Dissolver</div>
<div className="text-[8px] text-fg-sub leading-normal">
.
</div>
<div
className="mt-1.5 text-[7px] font-semibold text-color-accent 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(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.2)',
}}
>
<div
className="flex items-center justify-center text-title-1 mx-auto mb-2"
style={{
width: 36,
height: 36,
borderRadius: '50%',
background: 'rgba(6,182,212,.15)',
}}
>
</div>
<div className="text-title-4 font-mono font-extrabold text-color-accent">S</div>
<div className="text-label-2 font-bold my-1">Sinker</div>
<div className="text-[8px] text-fg-sub leading-normal">
{'해저로 침강. 비중 > 1.0, 저층 오염 축적'}
</div>
<div
className="mt-1.5 text-[7px] font-semibold text-color-accent p-[3px]"
style={{ background: 'rgba(6,182,212,.08)', borderRadius: 3 }}
>
3D
</div>
</div>
</div>
{/* 복합 거동 */}
<div className="rounded-md p-3 border border-stroke bg-bg-card">
<div className="text-label-2 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-base">
<span className="font-bold text-color-accent">GD</span>
<br />
<span className="text-fg-disabled">+</span>
</div>
<div className="rounded p-1.5 bg-bg-base">
<span className="font-bold text-color-accent">ED</span>
<br />
<span className="text-fg-disabled">+</span>
</div>
<div className="rounded p-1.5 bg-bg-base">
<span className="font-bold text-color-accent">FE</span>
<br />
<span className="text-fg-disabled">+</span>
</div>
<div className="rounded p-1.5 bg-bg-base">
<span className="font-bold text-color-accent">FED</span>
<br />
<span className="text-fg-disabled">++</span>
</div>
<div className="rounded p-1.5 bg-bg-base">
<span className="font-bold text-color-accent">SD</span>
<br />
<span className="text-fg-disabled">+</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-label-2 font-semibold cursor-pointer"
style={{
padding: '6px 12px',
border:
selectedCategory === cat.id
? '1px solid var(--color-accent)'
: '1px solid var(--stroke-default)',
color:
selectedCategory === cat.id ? 'var(--color-accent)' : 'var(--fg-disabled)',
background:
selectedCategory === cat.id ? 'rgba(6,182,212,.08)' : 'var(--bg-card)',
}}
>
{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-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]">
<div>
<span className="text-sm font-mono font-extrabold text-color-accent">
NH
</span>{' '}
<span className="text-label-1 font-bold"></span>
</div>
<div className="flex gap-1">
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
G/GD
</span>
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">CAS:</span>{' '}
<span className="font-mono">7664-41-7</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">17.03</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent">-33.4°C</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">0.73</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">N/A ()</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent"> </span>
</div>
</div>
<div
className="grid text-center text-[7px]"
style={{ gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }}
>
<div
style={{
padding: 4,
background: 'rgba(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.12)',
borderRadius: 3,
}}
>
<span className="text-color-accent">AEGL-2</span>
<br />
<b>160 ppm</b>
</div>
<div
style={{
padding: 4,
background: 'rgba(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.12)',
borderRadius: 3,
}}
>
<span className="text-color-accent">ERPG-2</span>
<br />
<b>150 ppm</b>
</div>
<div
style={{
padding: 4,
background: 'rgba(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.12)',
borderRadius: 3,
}}
>
<span className="text-color-accent">IDLH</span>
<br />
<b>300 ppm</b>
</div>
</div>
<div className="mt-1.5 text-[8px] text-fg-disabled leading-normal">
. .
.
</div>
</div>
)}
{/* 메탄올 */}
{filtered.find((s) => s.casNumber === '67-56-1') && (
<div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]">
<div>
<span className="text-sm font-mono font-extrabold text-color-accent">
CHOH
</span>{' '}
<span className="text-label-1 font-bold"></span>
</div>
<div className="flex gap-1">
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
ED
</span>
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">CAS:</span>{' '}
<span className="font-mono">67-56-1</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">32.04</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent">64.7°C</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">0.79</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent">11°C</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent"> </span>
</div>
</div>
<div
className="grid text-center text-[7px]"
style={{ gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }}
>
<div
style={{
padding: 4,
background: 'rgba(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.12)',
borderRadius: 3,
}}
>
<span className="text-color-accent">AEGL-2</span>
<br />
<b>2,100 ppm</b>
</div>
<div
style={{
padding: 4,
background: 'rgba(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.12)',
borderRadius: 3,
}}
>
<span className="text-color-accent">ERPG-2</span>
<br />
<b>1,000 ppm</b>
</div>
<div
style={{
padding: 4,
background: 'rgba(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.12)',
borderRadius: 3,
}}
>
<span className="text-color-accent">IDLH</span>
<br />
<b>6,000 ppm</b>
</div>
</div>
<div className="mt-1.5 text-[8px] text-fg-disabled leading-normal">
. .
. 2007 FODDANGER호 95L .
</div>
</div>
)}
{/* 수소 */}
{filtered.find((s) => s.casNumber === '1333-74-0') && (
<div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]">
<div>
<span className="text-sm font-mono font-extrabold text-color-accent">H</span>{' '}
<span className="text-label-1 font-bold"></span>
</div>
<div className="flex gap-1">
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
G
</span>
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">CAS:</span>{' '}
<span className="font-mono">1333-74-0</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">2.016</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent">-252.9°C</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">():</span>{' '}
<span className="font-mono">0.07</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">LFL:</span>{' '}
<span className="font-mono text-color-accent">4.0%</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">UFL:</span>{' '}
<span className="font-mono text-color-accent">75.0%</span>
</div>
</div>
<div className="mt-1.5 text-[8px] text-fg-disabled 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-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]">
<div>
<span className="text-sm font-mono font-extrabold text-color-accent">
CH
</span>{' '}
<span className="text-label-1 font-bold">LNG ()</span>
</div>
<div className="flex gap-1">
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
G
</span>
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
/
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">CAS:</span>{' '}
<span className="font-mono">74-82-8</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">16.04</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent">-161.5°C</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">():</span>{' '}
<span className="font-mono">0.42</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">LFL:</span>{' '}
<span className="font-mono text-color-accent">5.0%</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">UFL:</span>{' '}
<span className="font-mono text-color-accent">15.0%</span>
</div>
</div>
<div className="mt-1.5 text-[8px] text-fg-disabled 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-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]">
<div>
<span className="text-sm font-mono font-extrabold text-color-accent">
CHOH
</span>{' '}
<span className="text-label-1 font-bold"></span>
</div>
<div className="flex gap-1">
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
S/SD
</span>
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">CAS:</span>{' '}
<span className="font-mono">108-95-2</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">94.11</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">181.7°C</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent">1.07 ()</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent">79°C</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent">84 g/L</span>
</div>
</div>
<div className="mt-1.5 text-[8px] text-fg-disabled leading-normal">
1.07 <b className="text-color-accent">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-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]">
<div>
<span className="text-sm font-mono font-extrabold text-color-accent">
CH
</span>{' '}
<span className="text-label-1 font-bold"></span>
</div>
<div className="flex gap-1">
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
FE
</span>
<span
className="text-[8px] font-semibold text-color-accent px-1.5 py-0.5"
style={{ borderRadius: 3, background: 'rgba(6,182,212,.1)' }}
>
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-1 text-[8px] mb-2">
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">CAS:</span>{' '}
<span className="font-mono">108-88-3</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">92.14</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">110.6°C</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent">0.87 ()</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono text-color-accent">4°C</span>
</div>
<div className="rounded bg-bg-base px-1.5 py-1">
<span className="text-fg-disabled">:</span>{' '}
<span className="font-mono">0.52 g/L</span>
</div>
</div>
<div className="mt-1.5 text-[8px] text-fg-disabled 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-stroke bg-bg-card">
<div className="text-title-4 font-bold text-color-accent mb-[10px]">
🟢 AEGL (Acute Exposure Guideline Level)
</div>
<div className="text-caption text-fg-disabled 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(6,182,212,.04)',
border: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-2 font-bold text-color-accent">AEGL-1 ()</div>
<div className="text-caption text-fg-sub leading-normal">
, . .
</div>
</div>
<div
className="rounded-sm px-2.5 py-2"
style={{
background: 'rgba(6,182,212,.04)',
border: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-2 font-bold text-color-accent">
AEGL-2 ( )
</div>
<div className="text-caption text-fg-sub leading-normal">
· .{' '}
<b className="text-color-accent"> </b>.
</div>
</div>
<div
className="rounded-sm px-2.5 py-2"
style={{
background: 'rgba(6,182,212,.04)',
border: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-2 font-bold text-color-accent">
AEGL-3 ( )
</div>
<div className="text-caption text-fg-sub leading-normal">
.{' '}
<b className="text-color-accent"> · </b>.
</div>
</div>
</div>
</div>
{/* ERPG / IDLH */}
<div className="rounded-[10px] p-4 border border-stroke bg-bg-card">
<div className="text-title-4 font-bold text-color-accent mb-[10px]">
🔴 ERPG &amp; IDLH
</div>
<div className="flex flex-col gap-2">
<div>
<div className="text-label-2 font-bold text-color-accent mb-1">
ERPG (Emergency Response Planning Guideline)
</div>
<div className="text-caption text-fg-disabled 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(6,182,212,.03)',
border: '1px solid rgba(6,182,212,.1)',
}}
>
<b className="text-color-accent">ERPG-1</b> ,
</div>
<div
className="text-[8px] rounded px-2 py-1.5"
style={{
background: 'rgba(6,182,212,.03)',
border: '1px solid rgba(6,182,212,.1)',
}}
>
<b className="text-color-accent">ERPG-2</b> ,
</div>
<div
className="text-[8px] rounded px-2 py-1.5"
style={{
background: 'rgba(6,182,212,.03)',
border: '1px solid rgba(6,182,212,.1)',
}}
>
<b className="text-color-accent">ERPG-3</b>
</div>
</div>
</div>
<div
className="mt-1 rounded-sm p-2.5"
style={{
background: 'rgba(6,182,212,.04)',
border: '1px solid rgba(6,182,212,.15)',
}}
>
<div className="text-label-2 font-bold text-color-accent mb-1">
IDLH (Immediately Dangerous to Life or Health)
</div>
<div className="text-caption text-fg-disabled mb-1">
NIOSH 30 ·
</div>
<div className="text-caption text-fg-sub leading-normal">
<b className="text-color-accent"> </b>
(SCBA) . WING
.
</div>
</div>
</div>
</div>
</div>
{/* 물질별 위험도 비교표 */}
<div className="rounded-[10px] p-4 border border-stroke bg-bg-card">
<div className="text-label-1 font-bold mb-3">
📊 HNS (ppm)
</div>
<table className="w-full border-collapse text-caption">
<thead>
<tr style={{ background: 'rgba(6,182,212,.06)' }}>
<th
className="text-color-accent text-left"
style={{ padding: '7px 8px', borderBottom: '2px solid var(--stroke-light)' }}
>
</th>
<th
className="text-color-accent text-center"
style={{ padding: '7px 8px', borderBottom: '2px solid var(--stroke-light)' }}
>
AEGL-1
</th>
<th
className="text-color-accent text-center"
style={{ padding: '7px 8px', borderBottom: '2px solid var(--stroke-light)' }}
>
AEGL-2
</th>
<th
className="text-color-accent text-center"
style={{ padding: '7px 8px', borderBottom: '2px solid var(--stroke-light)' }}
>
AEGL-3
</th>
<th
className="text-color-accent text-center"
style={{ padding: '7px 8px', borderBottom: '2px solid var(--stroke-light)' }}
>
ERPG-2
</th>
<th
className="text-color-accent text-center"
style={{ padding: '7px 8px', borderBottom: '2px solid var(--stroke-light)' }}
>
IDLH
</th>
<th
className="text-color-accent text-center"
style={{ padding: '7px 8px', borderBottom: '2px solid var(--stroke-light)' }}
>
LFL(%)
</th>
<th
className="text-fg-sub text-center"
style={{ padding: '7px 8px', borderBottom: '2px solid var(--stroke-light)' }}
>
SEBC
</th>
</tr>
</thead>
<tbody>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td className="font-semibold text-color-accent py-1.5 px-2">NH </td>
<td className="text-center font-mono text-color-accent">30</td>
<td className="text-center font-mono text-color-accent">160</td>
<td className="text-center font-mono text-color-accent">1,100</td>
<td className="text-center font-mono">150</td>
<td className="text-center font-mono text-color-accent">300</td>
<td className="text-center font-mono">15.0</td>
<td className="text-center font-semibold text-color-accent">G/GD</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td className="font-semibold text-color-accent py-1.5 px-2">CHOH </td>
<td className="text-center font-mono text-color-accent">530</td>
<td className="text-center font-mono text-color-accent">2,100</td>
<td className="text-center font-mono text-color-accent">14,000</td>
<td className="text-center font-mono">1,000</td>
<td className="text-center font-mono text-color-accent">6,000</td>
<td className="text-center font-mono">6.0</td>
<td className="text-center font-semibold text-color-accent">ED</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td className="font-semibold text-color-accent py-1.5 px-2">H </td>
<td className="text-center text-fg-disabled">-</td>
<td className="text-center text-fg-disabled">-</td>
<td className="text-center text-fg-disabled">-</td>
<td className="text-center text-fg-disabled">-</td>
<td className="text-center text-fg-disabled">-</td>
<td className="text-center font-mono font-bold text-color-accent">4.0</td>
<td className="text-center font-semibold text-color-accent">G</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td className="font-semibold text-color-accent py-1.5 px-2">CH LNG</td>
<td className="text-center text-fg-disabled">-</td>
<td className="text-center text-fg-disabled">-</td>
<td className="text-center text-fg-disabled">-</td>
<td className="text-center text-fg-disabled">-</td>
<td className="text-center text-fg-disabled">-</td>
<td className="text-center font-mono text-color-accent">5.0</td>
<td className="text-center font-semibold text-color-accent">G</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td className="font-semibold text-color-accent py-1.5 px-2">CHOH </td>
<td className="text-center font-mono text-color-accent">19</td>
<td className="text-center font-mono text-color-accent">29</td>
<td className="text-center font-mono text-color-accent">57</td>
<td className="text-center font-mono">50</td>
<td className="text-center font-mono text-color-accent">250</td>
<td className="text-center font-mono">1.8</td>
<td className="text-center font-semibold text-color-accent">S/SD</td>
</tr>
<tr>
<td className="font-semibold text-color-accent py-1.5 px-2">CH </td>
<td className="text-center font-mono text-color-accent">67</td>
<td className="text-center font-mono text-color-accent">560</td>
<td className="text-center font-mono text-color-accent">3,700</td>
<td className="text-center font-mono">300</td>
<td className="text-center font-mono text-color-accent">500</td>
<td className="text-center font-mono">1.1</td>
<td className="text-center font-semibold text-color-accent">FE</td>
</tr>
</tbody>
</table>
<div className="mt-2 text-[7px] text-fg-disabled">
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-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[14px]">
<div className="text-title-4 font-bold">
🔍 HNS {' '}
<span className="text-caption font-normal text-fg-disabled">
(···CAS·UN번호 )
</span>
</div>
<div className="flex gap-1">
<button
className="text-caption font-semibold cursor-pointer text-color-accent rounded px-2.5 py-[5px]"
style={{
border: '1px solid rgba(6,182,212,.25)',
background: 'rgba(6,182,212,.08)',
}}
>
📥 DB
</button>
<button
className="text-caption font-semibold cursor-pointer text-color-accent 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-caption font-semibold text-fg-disabled">:</span>
<select
value={hmsSearchType}
onChange={(e) => {
setHmsSearchType(e.target.value as typeof hmsSearchType);
setHmsPage(1);
}}
className="rounded-sm border border-stroke text-label-1 outline-none bg-bg-base 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-stroke text-title-4 outline-none bg-bg-base px-3 py-2"
/>
<button
onClick={() => setHmsPage(1)}
className="text-title-4 font-bold cursor-pointer shrink-0 rounded-sm text-color-accent px-5 py-2"
style={{
border: '1px solid rgba(6,182,212,.3)',
background: 'rgba(6,182,212,.08)',
}}
>
🔎
</button>
</div>
<div className="text-[8px] text-fg-disabled leading-[1.6]">
· <b className="text-color-accent"> </b>{' '}
&nbsp;|&nbsp; / {' '}
<b className="text-color-accent">, </b> &nbsp;|&nbsp; {' '}
<b className="text-color-accent">1,222</b>
</div>
</div>
{/* ── 검색 결과 테이블 ── */}
<div className="rounded-[10px] p-4 mb-4 border border-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]">
<div className="text-title-4 font-bold">
📋 {' '}
<span className="text-label-2 font-normal text-fg-disabled">
{hmsTotal}
</span>
</div>
<select
value={hmsFilterSebc}
onChange={(e) => {
setHmsFilterSebc(e.target.value);
setHmsPage(1);
}}
className="rounded border border-stroke text-label-2 text-fg-sub outline-none bg-bg-base 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-label-2">
<thead>
<tr style={{ background: 'rgba(6,182,212,.06)' }}>
<th
className="text-color-accent text-left"
style={{
padding: '8px 8px',
borderBottom: '2px solid var(--stroke-light)',
width: 36,
}}
>
No.
</th>
<th
className="text-color-accent text-left"
style={{
padding: '8px 8px',
borderBottom: '2px solid var(--stroke-light)',
}}
>
/
</th>
<th
className="text-fg-sub text-left"
style={{
padding: '8px 8px',
borderBottom: '2px solid var(--stroke-light)',
}}
>
</th>
<th
className="text-fg-sub text-left"
style={{
padding: '8px 8px',
borderBottom: '2px solid var(--stroke-light)',
}}
>
</th>
<th
className="text-color-accent text-left"
style={{
padding: '8px 8px',
borderBottom: '2px solid var(--stroke-light)',
}}
>
</th>
<th
className="text-fg-sub text-left text-label-2"
style={{
padding: '8px 8px',
borderBottom: '2px solid var(--stroke-light)',
}}
>
/
</th>
<th
className="text-fg-sub text-center"
style={{
padding: '8px 8px',
borderBottom: '2px solid var(--stroke-light)',
width: 68,
}}
>
UN번호
</th>
<th
className="text-fg-sub text-center"
style={{
padding: '8px 8px',
borderBottom: '2px solid var(--stroke-light)',
width: 80,
}}
>
CAS번호
</th>
</tr>
</thead>
<tbody>
{hmsLoading ? (
<tr>
<td colSpan={8} className="text-center text-fg-disabled 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(6,182,212,.03)';
}}
onMouseOut={(e) => {
if (!isSel) e.currentTarget.style.background = '';
}}
>
<td className="font-mono text-fg-disabled px-2 py-2">
{(hmsPage - 1) * HMS_PER_PAGE + idx + 1}
</td>
<td className="font-semibold font-mono text-color-accent px-2 py-2">
{s.abbreviation}
</td>
<td className="px-2 py-2">{s.nameEn}</td>
<td className="text-fg-disabled text-label-2 px-2 py-2">
{s.synonymsEn}
</td>
<td className="font-semibold px-2 py-2">
<span
className="text-color-accent underline cursor-pointer"
onClick={(e) => {
e.stopPropagation();
setHmsSelectedId(s.id);
setHmsDetailTab(0);
}}
>
{s.nameKr}
</span>
</td>
<td className="text-fg-disabled text-label-2 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-fg-disabled py-5 px-2">
.
</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="mt-[10px] text-center text-label-2 text-fg-disabled">
<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-stroke text-label-2 text-fg-sub bg-bg-base 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-stroke text-label-2 text-fg-sub bg-bg-base 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-stroke text-label-2 px-3 py-1"
style={{
background: p === hmsPage ? 'rgba(6,182,212,.15)' : 'var(--bg-base)',
color: p === hmsPage ? 'var(--color-accent)' : 'var(--fg-sub)',
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-stroke text-label-2 text-fg-sub bg-bg-base 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-stroke text-label-2 text-fg-sub bg-bg-base px-2.5 py-1"
style={{ opacity: hmsPage >= hmsTotalPages ? 0.4 : 1 }}
>
</button>
<span className="ml-2 text-fg-disabled">
{hmsPage} / {hmsTotalPages}
</span>
</div>
<span>
<b className="text-color-accent">{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(--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 (
<div
className="rounded-[10px] overflow-hidden bg-bg-card"
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-stroke" style={{ background: 'rgba(6,182,212,.05)' }}>
{tabLabels.map((label, i) => (
<button
key={i}
onClick={() => onTabChange(i)}
className="flex-1 py-2.5 px-1 text-caption cursor-pointer"
style={{
fontWeight: activeTab === i ? 700 : 600,
border: 'none',
borderBottom: `2px solid ${activeTab === i ? 'var(--color-accent)' : 'transparent'}`,
background: activeTab === i ? 'rgba(6,182,212,.06)' : 'transparent',
color: activeTab === i ? 'var(--color-accent)' : 'var(--fg-disabled)',
}}
>
{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-stroke">
<div
className="flex items-center justify-center text-[22px] shrink-0"
style={{
width: 52,
height: 52,
borderRadius: 10,
background: 'rgba(6,182,212,.12)',
border: '1px solid rgba(6,182,212,.25)',
}}
>
🧪
</div>
<div className="flex-1">
<div className="text-title-2 font-extrabold">
{s.nameKr}{' '}
<span className="text-label-1 font-normal text-fg-disabled">({s.nameEn})</span>
</div>
<div className="flex flex-wrap gap-1.5 mt-1.5">
<span
className="text-caption font-semibold font-mono text-color-accent 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-caption font-semibold font-mono text-color-accent rounded py-[2px] px-2"
style={{
background: 'rgba(6,182,212,.1)',
border: '1px solid rgba(6,182,212,.2)',
}}
>
UN: {s.unNumber}
</span>
<span
className="text-caption font-semibold text-color-accent rounded py-[2px] px-2"
style={{
background: 'rgba(6,182,212,.1)',
border: '1px solid rgba(6,182,212,.2)',
}}
>
: {s.transportMethod}
</span>
<span
className="text-caption font-semibold text-color-accent rounded py-[2px] px-2"
style={{
background: 'rgba(6,182,212,.1)',
border: '1px solid rgba(6,182,212,.2)',
}}
>
SEBC: {s.sebc}
</span>
</div>
<div className="text-caption text-fg-disabled 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-label-2 font-bold text-color-accent mb-2">
·
</div>
<div className="grid grid-cols-2 gap-1 text-caption">
{(
[
['용도', 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]) => (
<div
key={label}
style={{
padding: '6px 8px',
background: 'var(--bg-base)',
borderRadius: 4,
}}
>
<span className="text-fg-disabled">{label}</span>
<br />
<b
style={{
color:
label.includes('인화') || label.includes('폭발')
? 'var(--color-accent)'
: label.includes('용해')
? 'var(--color-accent)'
: 'var(--fg-default)',
}}
>
{value}
</b>
</div>
))}
</div>
</div>
{/* Right: NFPA + 위험등급 */}
<div>
<div className="text-label-2 font-bold text-color-accent 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(6,182,212,.2)"
stroke="rgba(6,182,212,.5)"
strokeWidth={1}
/>
<text
x={50}
y={32}
textAnchor="middle"
fill="var(--color-accent)"
fontSize={16}
fontWeight={800}
fontFamily="var(--font-mono)"
>
{nfpa.health}
</text>
<polygon
points="72,28 95,50 72,72 50,50"
fill="rgba(6,182,212,.2)"
stroke="rgba(6,182,212,.5)"
strokeWidth={1}
/>
<text
x={77}
y={55}
textAnchor="middle"
fill="var(--color-accent)"
fontSize={16}
fontWeight={800}
fontFamily="var(--font-mono)"
>
{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="var(--fg-sub)"
fontSize={12}
fontWeight={700}
fontFamily="var(--font-korean)"
>
{nfpa.special}
</text>
<polygon
points="5,50 28,28 50,50 28,72"
fill="rgba(6,182,212,.2)"
stroke="rgba(6,182,212,.5)"
strokeWidth={1}
/>
<text
x={23}
y={55}
textAnchor="middle"
fill="var(--color-accent)"
fontSize={16}
fontWeight={800}
fontFamily="var(--font-mono)"
>
{nfpa.reactivity}
</text>
</svg>
<div className="text-center text-[7px] font-semibold text-fg-disabled 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(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.12)',
borderRadius: 4,
}}
>
<span style={{ color: 'var(--color-accent)', 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(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.12)',
borderRadius: 4,
}}
>
<span style={{ color: 'var(--color-accent)', 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(6,182,212,.06)',
border: '1px solid rgba(6,182,212,.12)',
borderRadius: 4,
}}
>
<span style={{ color: 'var(--color-accent)', 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-caption">
<InfoBoxRow
label="위험 분류"
value={s.hazardClass}
bg="rgba(6,182,212,.06)"
border="rgba(6,182,212,.12)"
labelColor="var(--color-accent)"
valueColor="var(--color-accent)"
/>
<InfoBoxRow
label="ERG 번호"
value={s.ergNumber}
bg="rgba(6,182,212,.06)"
border="rgba(6,182,212,.12)"
labelColor="var(--color-accent)"
valueColor="var(--color-accent)"
/>
<InfoBoxRow
label="IDLH"
value={s.idlh}
bg="rgba(6,182,212,.06)"
border="rgba(6,182,212,.12)"
labelColor="var(--color-accent)"
valueColor="var(--color-accent)"
/>
<InfoBoxRow
label="AEGL-2 (60분)"
value={s.aegl2}
bg="rgba(6,182,212,.06)"
border="rgba(6,182,212,.12)"
labelColor="var(--color-accent)"
valueColor="var(--color-accent)"
/>
<InfoBoxRow
label="ERPG-2"
value={s.erpg2}
bg="rgba(6,182,212,.06)"
border="rgba(6,182,212,.12)"
labelColor="var(--color-accent)"
valueColor="var(--color-accent)"
/>
</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(6,182,212,.2)' }}
>
<div
style={{
padding: '10px 12px',
background: 'rgba(6,182,212,.06)',
borderBottom: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-1 font-bold text-color-accent">
🚧 (ERG {s.ergNumber})
</div>
</div>
<div className="p-3 flex flex-col gap-2">
<div
className="rounded-sm"
style={{
padding: 10,
background: 'rgba(6,182,212,.04)',
border: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-2 font-bold text-color-accent mb-1">🔥 </div>
<div className="text-caption text-fg-sub leading-[1.6]">
: <b className="text-color-accent">{s.responseDistanceFire}</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-label-2 font-bold text-color-accent mb-1">
💨 ()
</div>
<div className="text-caption text-fg-sub leading-[1.6]">
:{' '}
<b className="text-color-accent">{s.responseDistanceSpillDay}</b>
<br />
:{' '}
<b className="text-color-accent">{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-label-2 font-bold text-color-accent mb-1">
🌊
</div>
<div className="text-caption text-fg-sub leading-[1.6]">{s.marineResponse}</div>
</div>
</div>
</div>
<div>
{/* PPE */}
<div
className="rounded-md overflow-hidden mb-3"
style={{ border: '1px solid rgba(6,182,212,.2)' }}
>
<div
style={{
padding: '10px 12px',
background: 'rgba(6,182,212,.06)',
borderBottom: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-1 font-bold text-color-accent">
🛡 (PPE)
</div>
</div>
<div className="grid grid-cols-2 gap-1.5 text-caption p-3">
<div
className="text-center rounded"
style={{
padding: 8,
background: 'rgba(6,182,212,.04)',
border: '1px solid rgba(6,182,212,.1)',
}}
>
<div className="text-base mb-[3px]">🧑🚒</div>
<div className="font-bold text-color-accent"></div>
<div className="text-[8px] text-fg-disabled mt-0.5">{s.ppeClose}</div>
</div>
<div
className="text-center rounded"
style={{
padding: 8,
background: 'rgba(6,182,212,.04)',
border: '1px solid rgba(6,182,212,.1)',
}}
>
<div className="text-base mb-[3px]">🦺</div>
<div className="font-bold text-color-accent"></div>
<div className="text-[8px] text-fg-disabled mt-0.5">{s.ppeFar}</div>
</div>
</div>
</div>
{/* MSDS */}
<div
className="rounded-md overflow-hidden"
style={{ border: '1px solid rgba(6,182,212,.2)' }}
>
<div
className="flex items-center justify-between"
style={{
padding: '10px 12px',
background: 'rgba(6,182,212,.06)',
borderBottom: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-1 font-bold" style={{ color: 'var(--color-accent)' }}>
📄 MSDS
</div>
<button
className="text-[8px] font-semibold cursor-pointer rounded"
style={{
padding: '3px 10px',
background: 'rgba(6,182,212,.1)',
border: '1px solid rgba(6,182,212,.2)',
color: 'var(--color-accent)',
}}
>
📥
</button>
</div>
<div className="text-[8px] text-fg-sub 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(6,182,212,.2)' }}
>
<div
style={{
padding: '10px 12px',
background: 'rgba(6,182,212,.06)',
borderBottom: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-1 font-bold text-color-accent">
IBC CODE
</div>
</div>
<div className="p-3 flex flex-col gap-2 text-caption">
<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-fg-disabled"
style={{ padding: '6px 8px', background: 'var(--bg-base)' }}
>
{label}
</div>
<div
className="rounded font-semibold"
style={{ padding: '6px 8px', background: 'var(--bg-base)' }}
>
{value}
</div>
</React.Fragment>
))}
</div>
{/* Tank diagram SVG */}
<div
className="rounded-sm text-center"
style={{ padding: 10, background: 'var(--bg-base)' }}
>
<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(6,182,212,.4)"
strokeWidth={1.5}
/>
<line
x1={70}
y1={10}
x2={70}
y2={60}
stroke="rgba(6,182,212,.2)"
strokeWidth={1}
strokeDasharray="3"
/>
<line
x1={130}
y1={10}
x2={130}
y2={60}
stroke="rgba(6,182,212,.2)"
strokeWidth={1}
strokeDasharray="3"
/>
<text
x={40}
y={38}
textAnchor="middle"
fill="var(--color-accent)"
fontSize={7}
fontFamily="var(--font-korean)"
>
CARGO
</text>
<text
x={40}
y={48}
textAnchor="middle"
fill="var(--fg-disabled)"
fontSize={6}
fontFamily="var(--font-mono)"
>
Tank 1
</text>
<text
x={100}
y={38}
textAnchor="middle"
fill="var(--color-accent)"
fontSize={7}
fontFamily="var(--font-korean)"
>
CARGO
</text>
<text
x={100}
y={48}
textAnchor="middle"
fill="var(--fg-disabled)"
fontSize={6}
fontFamily="var(--font-mono)"
>
Tank 2
</text>
<text
x={160}
y={38}
textAnchor="middle"
fill="var(--color-accent)"
fontSize={7}
fontFamily="var(--font-korean)"
>
CARGO
</text>
<text
x={160}
y={48}
textAnchor="middle"
fill="var(--fg-disabled)"
fontSize={6}
fontFamily="var(--font-mono)"
>
Tank 3
</text>
<text
x={100}
y={75}
textAnchor="middle"
fill="var(--fg-disabled)"
fontSize={7}
fontFamily="var(--font-korean)"
>
{s.ibcShipType} {s.ibcTankType}
</text>
</svg>
</div>
</div>
</div>
{/* EmS */}
<div
className="rounded-md overflow-hidden"
style={{ border: '1px solid rgba(6,182,212,.2)' }}
>
<div
style={{
padding: '10px 12px',
background: 'rgba(6,182,212,.06)',
borderBottom: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-1 font-bold text-color-accent">
🆘 (EmS) ERG {s.ergNumber}
</div>
</div>
<div className="p-3 flex flex-col gap-2 text-caption">
<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-color-accent mb-[3px]">🔥 </div>
<div className="text-fg-sub leading-[1.6]">{s.emsFire}</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-color-accent mb-[3px]">💧 </div>
<div className="text-fg-sub 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-color-accent mb-[3px]">🏥 </div>
<div className="text-fg-sub leading-[1.6]">{s.emsFirstAid}</div>
</div>
<div className="text-center">
<button
className="text-label-2 font-semibold cursor-pointer rounded-sm text-color-accent"
style={{
padding: '6px 16px',
background: 'rgba(6,182,212,.1)',
border: '1px solid rgba(6,182,212,.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(6,182,212,.2)' }}
>
<div
style={{
padding: '10px 12px',
background: 'rgba(6,182,212,.06)',
borderBottom: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-1 font-bold text-color-accent">
📋
</div>
<div className="text-[8px] text-fg-disabled"> </div>
</div>
<div className="p-3">
<table className="w-full border-collapse text-caption">
<thead>
<tr style={{ background: 'rgba(6,182,212,.05)' }}>
<th
className="text-color-accent text-left"
style={{
padding: '6px 8px',
borderBottom: '1px solid var(--stroke-light)',
}}
>
</th>
<th
className="text-fg-sub text-left"
style={{
padding: '6px 8px',
borderBottom: '1px solid var(--stroke-light)',
}}
>
/
</th>
<th
className="text-fg-sub text-left"
style={{
padding: '6px 8px',
borderBottom: '1px solid var(--stroke-light)',
}}
>
/
</th>
<th
className="text-fg-sub text-center"
style={{
padding: '6px 8px',
borderBottom: '1px solid var(--stroke-light)',
}}
>
</th>
</tr>
</thead>
<tbody>
{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 (
<tr
key={i}
style={{
borderBottom:
i < s.cargoCodes.length - 1
? '1px solid rgba(255,255,255,.04)'
: undefined,
}}
>
<td className="font-mono text-color-accent font-semibold cursor-pointer py-[5px] px-2">
{c.code}
</td>
<td className="py-[5px] px-2">{c.name}</td>
<td className="text-fg-disabled 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: 'rgba(6,182,212,.06)',
borderBottom: '1px solid rgba(6,182,212,.12)',
}}
>
<div className="text-label-1 font-bold text-color-accent">🏗 </div>
<div className="text-[8px] text-fg-disabled">Port-MIS </div>
</div>
<div className="p-3">
<table className="w-full border-collapse text-caption">
<thead>
<tr style={{ background: 'rgba(6,182,212,.05)' }}>
<th
className="text-color-accent text-left"
style={{
padding: '6px 8px',
borderBottom: '1px solid var(--stroke-light)',
}}
>
</th>
<th
className="text-fg-sub text-left"
style={{
padding: '6px 8px',
borderBottom: '1px solid var(--stroke-light)',
}}
>
</th>
<th
className="text-fg-sub text-center"
style={{
padding: '6px 8px',
borderBottom: '1px solid var(--stroke-light)',
}}
>
</th>
<th
className="text-fg-sub text-center"
style={{
padding: '6px 8px',
borderBottom: '1px solid var(--stroke-light)',
}}
>
</th>
</tr>
</thead>
<tbody>
{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 (
<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-color-accent 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-color-accent text-label-2 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>
);
}