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

1811 lines
77 KiB
TypeScript

import { useState, useRef, useEffect, useCallback } from 'react';
import { sanitizeHtml } from '@common/utils/sanitize';
import { api } from '@common/services/api';
import type { HNSSearchSubstance } from '@interfaces/hns/HnsInterface';
import { HmsDetailPanel } from './contents/HmsDetailPanel';
/* ═══ 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('');
/* 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;
});
/* 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-caption 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-caption text-fg-sub leading-normal">
.
</div>
<div
className="mt-1.5 text-caption 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-caption text-fg-sub leading-normal">
.
</div>
<div
className="mt-1.5 text-caption 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-caption text-fg-sub leading-normal">
{'해수면 위에 부유. 비중 < 1.0, 불용성 물질'}
</div>
<div
className="mt-1.5 text-caption 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-caption text-fg-sub leading-normal">
.
</div>
<div
className="mt-1.5 text-caption 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-caption text-fg-sub leading-normal">
{'해저로 침강. 비중 > 1.0, 저층 오염 축적'}
</div>
<div
className="mt-1.5 text-caption 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-caption"
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-body-2 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-caption 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-caption 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-caption 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-caption"
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-caption 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-body-2 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-caption 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-caption 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-caption 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-caption"
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-caption 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-body-2 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-caption 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-caption 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-caption 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-caption 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-body-2 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-caption 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-caption 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-caption 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-caption 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-body-2 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-caption 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-caption 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-caption 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-caption 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-body-2 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-caption 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-caption 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-caption 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-caption 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-caption 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-caption 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-caption 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-caption 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-caption 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>
);
}