대형 파일 집중 변환: - SatelliteRequest: 134→66 (hex 색상 일괄 변환) - IncidentsView: 141→90, MediaModal: 97→38 - HNSScenarioView: 78→38, HNSView: 49→31 - LoginPage, MapView, PredictionInputSection 등 중소 파일 8개 변환 패턴: hex 색상→text-[#hex], CSS 변수→Tailwind 유틸리티, flex/grid/padding/fontSize/fontWeight/overflow 등 정적 속성 className 이동 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
367 lines
21 KiB
TypeScript
Executable File
367 lines
21 KiB
TypeScript
Executable File
import { useState } from 'react'
|
|
import { HNSLeftPanel } from './HNSLeftPanel'
|
|
import { HNSRightPanel } from './HNSRightPanel'
|
|
import { MapView } from '@common/components/map/MapView'
|
|
import { HNSAnalysisListTable } from './HNSAnalysisListTable'
|
|
import { HNSTheoryView } from './HNSTheoryView'
|
|
import { HNSSubstanceView } from './HNSSubstanceView'
|
|
import { HNSScenarioView } from './HNSScenarioView'
|
|
import { HNSRecalcModal } from './HNSRecalcModal'
|
|
import { useSubMenu, navigateToTab, setReportGenCategory } from '@common/hooks/useSubMenu'
|
|
import { createHnsAnalysis } from '../services/hnsApi'
|
|
|
|
/* ─── HNS 매뉴얼 뷰어 컴포넌트 ─── */
|
|
function HNSManualViewer() {
|
|
const card = 'rounded-md p-4 mb-3'
|
|
|
|
return (
|
|
<div className="flex-1 overflow-y-auto bg-bg-0" style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
|
|
<div className="px-5 py-4 max-w-[1200px] mx-auto">
|
|
|
|
{/* 헤더 */}
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div>
|
|
<div className="text-base font-bold">📖 해양 HNS 대응 매뉴얼</div>
|
|
<div className="text-[10px] text-text-3 mt-0.5">Marine HNS Response Manual — Bonn Agreement / HELCOM / REMPEC (WestMOPoCo 2024 한국어판)</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 목차 카드 그리드 */}
|
|
<div className="grid mb-5" style={{ gridTemplateColumns: 'repeat(4,1fr)', gap: '10px' }}>
|
|
{[
|
|
{ icon: '📘', title: '1. 서론', desc: 'HNS 정의 · OPRC-HNS 의정서 · HNS 협약 범위 및 목적', color: 'var(--cyan)' },
|
|
{ icon: '⚖️', title: '2. IMO 협약·의정서·규칙', desc: 'SOLAS · MARPOL · IBC Code · IMDG Code · IGC Code', color: 'var(--cyan)' },
|
|
{ icon: '🔬', title: '3. HNS 거동 및 유해요소', desc: 'SEBC 거동분류 · MSDS · GESAMP · 물리화학적 특성', color: 'var(--purple)' },
|
|
{ icon: '🛡️', title: '4. 대비', desc: '위험 평가 · 비상 계획 · 교육훈련 · 장비 비축', color: 'var(--orange)' },
|
|
{ icon: '🚨', title: '5. 대응', desc: '최초 조치 · 안전구역 · PPE · 모니터링 · 대응 기술', color: 'var(--red)' },
|
|
{ icon: '🔄', title: '6. 유출 후 관리', desc: '비용 문서화 · 환경 회복 · 사고 검토 · 교훈', color: 'var(--cyan)' },
|
|
{ icon: '📋', title: '7. 사례연구', desc: '실제 HNS 해양사고 사례 분석 및 교훈', color: 'var(--cyan)' },
|
|
{ icon: '📊', title: '8. 자료표', desc: '물질별 데이터시트 · AEGL · 노출 한계값', color: 'var(--cyan)' },
|
|
].map(ch => (
|
|
<div key={ch.title} className={card} style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', cursor: 'pointer', transition: '.2s' }}>
|
|
<div className="text-[20px] mb-1.5">{ch.icon}</div>
|
|
<div className="text-[11px] font-bold">{ch.title}</div>
|
|
<div className="text-[9px] text-text-3 mt-1 leading-[1.4]">{ch.desc}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* SEBC 거동 분류 */}
|
|
<div className={card} className="bg-bg-3 border border-border">
|
|
<div className="text-[13px] font-bold mb-2.5">SEBC 거동 분류 (Standard European Behaviour Classification)</div>
|
|
<div className="text-[9px] text-text-3 mb-2.5 leading-normal">물질의 물리적·화학적 특성(용해도, 밀도, 증기압, 점도)에 따라 이론적 거동을 5가지 주요 범주 + 7가지 하위 범주로 분류</div>
|
|
<div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(5,1fr)' }}>
|
|
{[
|
|
{ icon: '💨', label: 'G — 가스', desc: '대기 중 확산\n증기압 > 101.3kPa\n예: 암모니아, 염소', color: 'rgba(139,92,246' },
|
|
{ icon: '🌫️', label: 'E — 증발', desc: '수면→대기 증발\n증기압 > 3kPa\n예: 벤젠, 톨루엔', color: 'rgba(249,115,22' },
|
|
{ icon: '🟡', label: 'F — 부유', desc: '해수면에 부유\n밀도 < 1.025\n예: 스티렌, 크실렌', color: 'rgba(251,191,36' },
|
|
{ icon: '💧', label: 'D — 용해', desc: '해수에 용해\n용해도 > 5%\n예: 메탄올, 염산', color: 'rgba(6,182,212' },
|
|
{ icon: '⬇️', label: 'S — 침강', desc: '해저로 침강\n밀도 > 1.025\n예: EDC, 사염화탄소', color: 'rgba(139,148,158' },
|
|
].map(s => (
|
|
<div key={s.label} className="text-center px-[6px] py-[10px] rounded-sm" style={{ background: `${s.color},.08)`, border: `1px solid ${s.color},.2)` }}>
|
|
<div className="text-[22px] mb-1">{s.icon}</div>
|
|
<div className="text-[11px] font-bold" style={{ color: `${s.color},1)` }}>{s.label}</div>
|
|
<div className="text-text-3 whitespace-pre-line text-[8px] mt-[3px] leading-[1.3]">{s.desc}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* IMDG Code 위험물 등급 */}
|
|
<div className={card} className="bg-bg-3 border border-border">
|
|
<div className="text-[13px] font-bold mb-2.5">IMDG Code 위험물 등급 (Hazard Classification)</div>
|
|
<div className="grid gap-1.5" style={{ gridTemplateColumns: 'repeat(3,1fr)' }}>
|
|
{[
|
|
{ icon: '💥', label: 'Class 1 — 폭발물', sub: 'Explosives', bg: 'rgba(249,115,22,.15)' },
|
|
{ icon: '🫧', label: 'Class 2 — 가스', sub: '인화성/비인화성/독성', bg: 'rgba(34,197,94,.12)' },
|
|
{ icon: '🔥', label: 'Class 3 — 인화성 액체', sub: 'Flammable Liquids', bg: 'rgba(239,68,68,.12)' },
|
|
{ icon: '🧱', label: 'Class 4 — 인화성 고체', sub: '자연발화성/물반응성', bg: 'rgba(251,191,36,.12)' },
|
|
{ icon: '⚡', label: 'Class 5 — 산화제', sub: '산화성 물질/유기과산화물', bg: 'rgba(251,191,36,.12)' },
|
|
{ icon: '☠️', label: 'Class 6 — 독성물질', sub: '독성/감염성 물질', bg: 'rgba(139,92,246,.12)' },
|
|
{ icon: '☢️', label: 'Class 7 — 방사성', sub: 'Radioactive Material', bg: 'rgba(251,191,36,.12)' },
|
|
{ icon: '🧪', label: 'Class 8 — 부식성', sub: 'Corrosive Substances', bg: 'rgba(139,148,158,.12)' },
|
|
{ icon: '⚠️', label: 'Class 9 — 기타', sub: '환경유해물질 포함', bg: 'rgba(139,148,158,.12)' },
|
|
].map(c => (
|
|
<div key={c.label} className="flex items-center gap-2 p-2 bg-bg-0 rounded-[5px]">
|
|
<div className="flex items-center justify-center shrink-0 text-sm w-7 h-7 rounded" style={{ background: c.bg }}>{c.icon}</div>
|
|
<div>
|
|
<div className="text-[10px] font-bold">{c.label}</div>
|
|
<div className="text-text-3 text-[8px]">{c.sub}</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* HNS 사고 대응 프로세스 */}
|
|
<div className={card} className="bg-bg-3 border border-border">
|
|
<div className="text-[13px] font-bold mb-2.5">HNS 사고 대응 프로세스</div>
|
|
<div className="flex items-stretch gap-1 text-[9px]">
|
|
{[
|
|
{ icon: '🚨', step: '1단계: 사고 통보', desc: 'HNS 유출 감지\nGMDSS/DSC 신호\n관계기관 통보', color: 'rgba(239,68,68', textColor: '#f87171' },
|
|
{ icon: '🔍', step: '2단계: 상황 평가', desc: '물질 식별 (UN번호)\nSEBC 거동 판단\n위험 구역 설정', color: 'rgba(249,115,22', textColor: '#fb923c' },
|
|
{ icon: '🛡️', step: '3단계: 최초 조치', desc: '선원/대응인력 보호\nPPE 착용\n안전구역 설정', color: 'rgba(251,191,36', textColor: '#fbbf24' },
|
|
{ icon: '⚙️', step: '4단계: 현장 대응', desc: '선박 중심 조치\n오염물질 중심 조치\n모니터링 수행', color: 'rgba(6,182,212', textColor: 'var(--cyan)' },
|
|
{ icon: '🔄', step: '5단계: 유출후 관리', desc: '환경 회복/복원\n비용 문서화\n사고 검토/교훈', color: 'rgba(34,197,94', textColor: '#22c55e' },
|
|
].map((s, i) => (
|
|
<div key={s.step} className="flex items-stretch flex-1">
|
|
<div className="flex-1 text-center px-2 py-[10px] rounded-sm" style={{ background: `${s.color},.08)`, border: `1px solid ${s.color},.2)` }}>
|
|
<div className="text-base mb-1">{s.icon}</div>
|
|
<div className="font-bold mb-0.5" style={{ color: s.textColor }}>{s.step}</div>
|
|
<div className="text-text-3 whitespace-pre-line text-[8px] leading-[1.3]">{s.desc}</div>
|
|
</div>
|
|
{i < 4 && <div className="flex items-center text-sm px-[2px]" style={{ color: 'var(--bd)' }}>→</div>}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 대응 기술 매트릭스 */}
|
|
<div className="grid gap-[14px] mb-[14px]" style={{ gridTemplateColumns: '1fr 1fr' }}>
|
|
{/* 선박 중심 조치 */}
|
|
<div className={card} className="bg-bg-3 border border-border">
|
|
<div className="text-[12px] font-bold mb-2">🚢 선박 중심 조치</div>
|
|
<div className="flex flex-col gap-1">
|
|
{[
|
|
{ icon: '🚁', title: '긴급 승선 (Emergency Boarding)', desc: '전문 대응팀의 사고 선박 접근 및 상황 파악' },
|
|
{ icon: '🔗', title: '긴급 예인 (Emergency Towing)', desc: '사고 선박을 안전 해역으로 이동' },
|
|
{ icon: '⚓', title: '피난 지역 (Place of Refuge)', desc: '선박이 수리/하역할 수 있는 보호 구역' },
|
|
{ icon: '🔄', title: '화물 이송 (Cargo Transfer)', desc: '위험 화물을 다른 선박/탱크로 이적' },
|
|
{ icon: '🔧', title: '밀봉/마개 (Sealing & Plugging)', desc: '유출 지점 임시 차단 및 봉쇄' },
|
|
].map(item => (
|
|
<div key={item.title} className="flex items-center gap-2 px-2 py-1.5 bg-bg-0 rounded">
|
|
<span className="text-[12px] text-center w-5">{item.icon}</span>
|
|
<div className="text-[9px]">
|
|
<b>{item.title}</b><br />
|
|
<span className="text-text-3">{item.desc}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
{/* 오염물질 중심 조치 */}
|
|
<div className={card} className="bg-bg-3 border border-border">
|
|
<div className="text-[12px] font-bold mb-2">🧪 오염물질 중심 조치</div>
|
|
<div className="flex flex-col gap-1">
|
|
{[
|
|
{ icon: '💨', title: '대기 확산 모니터링', desc: '가스/증발 물질의 대기 농도 감시 (ALOHA/CAMEO)' },
|
|
{ icon: '🌊', title: '해수면 회수 (Surface Recovery)', desc: '부유 물질 흡착재/스키머로 회수' },
|
|
{ icon: '💧', title: '희석/분산 (Dilution/Dispersion)', desc: '용해 물질의 자연 희석 촉진' },
|
|
{ icon: '⬇️', title: '해저 회수 (Subsea Recovery)', desc: '침강 물질 ROV/잠수 회수' },
|
|
{ icon: '🔥', title: '제어 연소 (Controlled Burning)', desc: '인화성 물질 현장 소각 처리' },
|
|
].map(item => (
|
|
<div key={item.title} className="flex items-center gap-2 px-2 py-1.5 bg-bg-0 rounded">
|
|
<span className="text-[12px] text-center w-5">{item.icon}</span>
|
|
<div className="text-[9px]">
|
|
<b>{item.title}</b><br />
|
|
<span className="text-text-3">{item.desc}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* PPE / 안전구역 / 노출한계 */}
|
|
<div className="grid gap-[14px] mb-[14px]" style={{ gridTemplateColumns: '1fr 1fr 1fr' }}>
|
|
{/* PPE */}
|
|
<div className={card} className="bg-bg-3 border border-border">
|
|
<div className="text-[12px] font-bold mb-2">🦺 개인보호장비 (PPE)</div>
|
|
<div className="flex flex-col gap-1 text-[9px]">
|
|
{[
|
|
{ level: 'Level A', desc: '완전 밀폐형 화학보호복 + SCBA\n증기/가스 직접 노출 구역', color: '#ef4444' },
|
|
{ level: 'Level B', desc: '비밀폐형 화학보호복 + SCBA\n액체 스플래시 위험 구역', color: '#f97316' },
|
|
{ level: 'Level C', desc: '화학보호복 + 공기정화식 호흡기\n물질 확인 완료, 농도 기준 이하', color: '#fbbf24' },
|
|
{ level: 'Level D', desc: '작업복 + 안전장화\n오염 위험 최소 구역', color: '#22c55e' },
|
|
].map(p => (
|
|
<div key={p.level} style={{ padding: '6px 8px', background: `color-mix(in srgb,${p.color} 6%,transparent)`, borderLeft: `3px solid ${p.color}`, borderRadius: '0 4px 4px 0' }}>
|
|
<b style={{ color: p.color }}>{p.level}</b><br />
|
|
<span className="text-text-3 whitespace-pre-line">{p.desc}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
{/* 안전구역 */}
|
|
<div className={card} className="bg-bg-3 border border-border">
|
|
<div className="text-[12px] font-bold mb-2">🔴 안전 구역 설정</div>
|
|
<div className="flex flex-col gap-1.5 text-[9px]">
|
|
<div className="text-center flex flex-col items-center justify-center p-[10px]" style={{ background: 'rgba(239,68,68,.08)', border: '2px solid rgba(239,68,68,.3)', borderRadius: '50%', aspectRatio: '1' }}>
|
|
<b style={{ color: '#f87171', fontSize: '11px' }}>HOT ZONE</b>
|
|
<span className="text-text-3 text-[8px]">직접 위험구역<br />Level A/B PPE 필수</span>
|
|
</div>
|
|
<div className="text-center p-2 rounded-sm" style={{ background: 'rgba(251,191,36,.06)', border: '1.5px solid rgba(251,191,36,.25)' }}>
|
|
<b style={{ color: '#fbbf24', fontSize: '10px' }}>WARM ZONE</b>
|
|
<span className="text-text-3 text-[8px]"> — 오염제거/전환 구역</span>
|
|
</div>
|
|
<div className="text-center p-2 rounded-sm" style={{ background: 'rgba(34,197,94,.06)', border: '1.5px solid rgba(34,197,94,.25)' }}>
|
|
<b style={{ color: '#22c55e', fontSize: '10px' }}>COLD ZONE</b>
|
|
<span className="text-text-3 text-[8px]"> — 지휘/지원 구역</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/* 노출한계 (AEGL) */}
|
|
<div className={card} className="bg-bg-3 border border-border">
|
|
<div className="text-[12px] font-bold mb-2">📊 노출한계 (AEGL 기준)</div>
|
|
<div className="text-[9px] text-text-3 mb-1.5 leading-[1.4]">Acute Exposure Guideline Levels (EPA)<br />암모니아(NH₃) 예시 — ppm 기준</div>
|
|
<table className="w-full font-mono border-collapse text-[8px]">
|
|
<thead>
|
|
<tr className="border-b border-border">
|
|
<th className="p-1 text-left text-text-3">구분</th>
|
|
<th className="p-1 text-center text-text-3">10분</th>
|
|
<th className="p-1 text-center text-text-3">30분</th>
|
|
<th className="p-1 text-center text-text-3">60분</th>
|
|
<th className="p-1 text-center text-text-3">4시간</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{[
|
|
{ label: 'AEGL-1', color: '#22c55e', vals: [30, 30, 30, 30] },
|
|
{ label: 'AEGL-2', color: '#fbbf24', vals: [220, 220, 160, 110] },
|
|
{ label: 'AEGL-3', color: '#f87171', vals: [2700, 1600, 1100, 550] },
|
|
].map((row, ri) => (
|
|
<tr key={row.label} className={ri < 2 ? 'border-b border-border' : ''}>
|
|
<td className="p-1 font-bold" style={{ color: row.color }}>{row.label}</td>
|
|
{row.vals.map((v, vi) => (
|
|
<td key={vi} className="p-1 text-center text-text-2">{v}</td>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
<div className="text-text-3 mt-1.5 text-[7px] leading-[1.4]">
|
|
AEGL-1: 불쾌감 (비장애성)<br />
|
|
AEGL-2: 심각한 건강 영향 (비가역적)<br />
|
|
AEGL-3: 생명 위협 또는 사망
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 출처 */}
|
|
<div className="text-text-3 rounded-sm bg-bg-3 p-[10px] text-[8px] leading-[1.5]">
|
|
<b>출처:</b> Marine HNS Response Manual — Bonn Agreement / HELCOM / REMPEC (WestMOPoCo Project, 2024 한국어판)<br />
|
|
번역: 원해민, 이시연, 양보경, 강성길, 이성엽 — KRISO 선박해양플랜트연구소 / NOWPAP MERRAC<br />
|
|
원본: Alcaro L., Brandt J., Giraud W., Mannozzi M., Nicolas-Kopec A. (2021) ISBN: 978-2-87893-147-1
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/* ─── 메인 HNSView ─── */
|
|
export function HNSView() {
|
|
const { activeSubTab, setActiveSubTab } = useSubMenu('hns')
|
|
const [incidentCoord, setIncidentCoord] = useState({ lon: 129.3542, lat: 35.4215 })
|
|
const [isSelectingLocation, setIsSelectingLocation] = useState(false)
|
|
const [isRunningPrediction, setIsRunningPrediction] = useState(false)
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const [dispersionResult, setDispersionResult] = useState<any>(null)
|
|
const [recalcModalOpen, setRecalcModalOpen] = useState(false)
|
|
|
|
const handleMapClick = (lon: number, lat: number) => {
|
|
if (isSelectingLocation) {
|
|
setIncidentCoord({ lon, lat })
|
|
setIsSelectingLocation(false)
|
|
}
|
|
}
|
|
|
|
const handleRunPrediction = async () => {
|
|
setIsRunningPrediction(true)
|
|
|
|
try {
|
|
const { hnsAnlysSn } = await createHnsAnalysis({
|
|
anlysNm: `HNS 분석 ${new Date().toLocaleDateString('ko-KR')}`,
|
|
lon: incidentCoord.lon,
|
|
lat: incidentCoord.lat,
|
|
locNm: `${incidentCoord.lon.toFixed(4)}, ${incidentCoord.lat.toFixed(4)}`,
|
|
})
|
|
|
|
// 시뮬레이션 엔진 미구현 — 프론트 임시 결과 생성
|
|
const windAngle = 225
|
|
const result = {
|
|
hnsAnlysSn,
|
|
zones: [
|
|
{ level: 'AEGL-3', color: 'rgba(239,68,68,0.4)', radius: 500, angle: windAngle },
|
|
{ level: 'AEGL-2', color: 'rgba(249,115,22,0.3)', radius: 1000, angle: windAngle },
|
|
{ level: 'AEGL-1', color: 'rgba(234,179,8,0.25)', radius: 1500, angle: windAngle },
|
|
],
|
|
timestamp: new Date().toISOString(),
|
|
windDirection: windAngle,
|
|
substance: 'Toluene',
|
|
concentration: { 'AEGL-3': '500 ppm', 'AEGL-2': '150 ppm', 'AEGL-1': '37 ppm' }
|
|
}
|
|
|
|
setDispersionResult(result)
|
|
} catch (error) {
|
|
console.error('대기확산 예측 오류:', error)
|
|
alert('대기확산 예측 중 오류가 발생했습니다.')
|
|
} finally {
|
|
setIsRunningPrediction(false)
|
|
}
|
|
}
|
|
|
|
if (activeSubTab === 'scenario') {
|
|
return <HNSScenarioView />
|
|
}
|
|
|
|
if (activeSubTab === 'manual') {
|
|
return <HNSManualViewer />
|
|
}
|
|
|
|
if (activeSubTab === 'theory') {
|
|
return <HNSTheoryView />
|
|
}
|
|
|
|
if (activeSubTab === 'substance') {
|
|
return <HNSSubstanceView />
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-1 overflow-hidden">
|
|
{/* Left Panel - 분석 목록일 때는 숨김 */}
|
|
{activeSubTab === 'analysis' && (
|
|
<HNSLeftPanel
|
|
activeSubTab={activeSubTab}
|
|
onSubTabChange={setActiveSubTab}
|
|
incidentCoord={incidentCoord}
|
|
onCoordChange={setIncidentCoord}
|
|
onMapSelectClick={() => setIsSelectingLocation(true)}
|
|
onRunPrediction={handleRunPrediction}
|
|
isRunningPrediction={isRunningPrediction}
|
|
/>
|
|
)}
|
|
|
|
{/* Center - Map/Content Area */}
|
|
<div className="flex-1 relative overflow-hidden">
|
|
{activeSubTab === 'list' ? (
|
|
<HNSAnalysisListTable onTabChange={(v) => setActiveSubTab(typeof v === 'function' ? v(activeSubTab as 'analysis' | 'list') : v)} />
|
|
) : (
|
|
<MapView
|
|
incidentCoord={incidentCoord}
|
|
isSelectingLocation={isSelectingLocation}
|
|
onMapClick={handleMapClick}
|
|
oilTrajectory={[]}
|
|
enabledLayers={new Set()}
|
|
dispersionResult={dispersionResult}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Right Panel - 분석 목록일 때는 숨김 */}
|
|
{activeSubTab === 'analysis' && (
|
|
<HNSRightPanel
|
|
dispersionResult={dispersionResult}
|
|
onOpenRecalc={() => setRecalcModalOpen(true)}
|
|
onOpenReport={() => { setReportGenCategory(1); navigateToTab('reports', 'generate') }}
|
|
/>
|
|
)}
|
|
|
|
{/* HNS 재계산 모달 */}
|
|
<HNSRecalcModal
|
|
isOpen={recalcModalOpen}
|
|
onClose={() => setRecalcModalOpen(false)}
|
|
onSubmit={() => handleRunPrediction()}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|