1811 lines
77 KiB
TypeScript
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">
|
|
CH₃OH
|
|
</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호 95만L 유출 사고.
|
|
</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">
|
|
C₆H₅OH
|
|
</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">
|
|
C₇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)' }}
|
|
>
|
|
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 & 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">CH₃OH 메탄올</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">C₆H₅OH 페놀</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">C₇H₈ 톨루엔</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>{' '}
|
|
| 약자/제품명 검색 시{' '}
|
|
<b className="text-color-accent">부호, 띄어쓰기 제외</b> 후 검색 | 총{' '}
|
|
<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>
|
|
);
|
|
}
|
|
|