wing-ops/frontend/src/components/hns/components/HNSTheoryView.tsx
leedano 38d931db65 refactor(mpa): 탭 디렉토리를 MPA 컴포넌트 구조로 재편
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:38:49 +09:00

144 lines
5.7 KiB
TypeScript

import { useState, useRef } from 'react';
import { SystemOverviewPanel } from './contents/SystemOverviewPanel';
import { GaussianModelPanel } from './contents/GaussianModelPanel';
import { SubstanceScenarioPanel } from './contents/SubstanceScenarioPanel';
import { OceanCorrectionPanel } from './contents/OceanCorrectionPanel';
import { VerificationPanel } from './contents/VerificationPanel';
import { RealtimeComparePanel } from './contents/RealtimeComparePanel';
import { WrfChemPanel } from './contents/WrfChemPanel';
/* eslint-disable react-refresh/only-export-components */
const theoryTabs = [
{ icon: '🔬', name: '시스템 개요' },
{ icon: '🌀', name: '가우시안 모델' },
{ icon: '🧪', name: '물질별 시나리오' },
{ icon: '🌊', name: '해양환경 보정' },
{ icon: '✅', name: '모델 검증' },
{ icon: '⚡', name: '실시간 비교' },
{ icon: '🚀', name: 'WRF-Chem·발전' },
];
/* ═══ 공통 스타일 유틸 ═══ */
export const card = 'rounded-[10px] p-[14px] mb-4';
export const cardBg = 'bg-bg-card border border-stroke';
export const labelStyle = (color: string) =>
({ fontSize: 'var(--font-size-title-3)', fontWeight: 700, color, marginBottom: '10px' }) as const;
export const tag = (color: string) =>
({
padding: '3px 8px',
borderRadius: '4px',
fontSize: 'var(--font-size-label-2)',
color,
background: `${color}14`,
border: `1px solid ${color}30`,
}) as const;
export const bodyText = 'text-label-1 text-fg-sub leading-[1.8]';
/* ═══ 패널 0: 시스템 개요 ═══ */
export function HNSTheoryView() {
const [activePanel, setActivePanel] = useState(0);
const contentRef = useRef<HTMLDivElement>(null);
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 = 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: blob:;">
<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: var(--font-korean); 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);
};
return (
<div className="flex flex-col flex-1 h-full overflow-hidden bg-bg-base">
<div
className="flex-1 overflow-y-auto scrollbar-thin p-5"
style={{ scrollbarGutter: 'stable' }}
ref={contentRef}
>
{/* 헤더 */}
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-3">
<div
className="w-[42px] h-[42px] rounded-[10px] flex items-center justify-center text-heading-3"
style={{
background: 'rgba(6,182,212,.04)',
border: '1px solid rgba(6,182,212,.3)',
}}
>
📐
</div>
<div>
<div className="text-title-2 font-bold text-fg">HNS </div>
<div className="text-label-2 text-fg-disabled mt-0.5">
WRF-Chem · Gaussian Plume/Puff · ROMS · Based on Lee Moon-Jin et al.
</div>
</div>
</div>
<button
onClick={handleExportPDF}
data-html2pdf-ignore
className="px-3.5 py-1.5 rounded-sm text-label-2 font-semibold cursor-pointer text-color-accent"
style={{
border: '1px solid rgba(6,182,212,.3)',
background: 'rgba(6,182,212,.08)',
}}
>
📤 PDF
</button>
</div>
{/* 서브탭 */}
<div className="flex gap-0.5 rounded-lg p-1 mb-5 bg-bg-card border border-stroke">
{theoryTabs.map((tab, i) => (
<button
key={i}
onClick={() => setActivePanel(i)}
className={`flex-1 py-2 px-1 text-label-1 font-medium rounded-md transition-all duration-150 cursor-pointer border ${
activePanel === i
? 'border-stroke-light bg-bg-elevated text-fg'
: 'border-transparent bg-bg-card text-fg-disabled hover:text-fg-sub'
}`}
>
{tab.icon} {tab.name}
</button>
))}
</div>
{/* 패널 콘텐츠 */}
{activePanel === 0 && <SystemOverviewPanel />}
{activePanel === 1 && <GaussianModelPanel />}
{activePanel === 2 && <SubstanceScenarioPanel />}
{activePanel === 3 && <OceanCorrectionPanel />}
{activePanel === 4 && <VerificationPanel />}
{activePanel === 5 && <RealtimeComparePanel />}
{activePanel === 6 && <WrfChemPanel />}
</div>
</div>
);
}