wing-ops/frontend/src/tabs/rescue/components/RescueView.tsx
htlee f099ff29b1 refactor(frontend): 탭 단위 패키지 구조 전환 (tabs/)
- 11개 탭 디렉토리 생성: tabs/{prediction,hns,rescue,weather,incidents,aerial,board,reports,assets,scat,admin}/
- 51개 컴포넌트를 역할 기반(views/, analysis/, layout/) → 탭 기반(tabs/) 구조로 이동
- weather 탭에 전용 hooks/, services/ 포함
- incidents 탭에 전용 services/ 포함
- 공통 지도 컴포넌트(MapView, BacktrackReplay)를 common/components/map/으로 이동
- 각 탭에 index.ts 생성하여 View 컴포넌트 re-export
- App.tsx import를 @tabs/ alias 사용으로 변경
- 전체 import 경로 수정 (탭 내부 상대경로, 외부 @common/ alias)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:08:34 +09:00

905 lines
52 KiB
TypeScript
Executable File
Raw Blame 히스토리

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react'
import { useSubMenu } from '@common/hooks/useSubMenu'
import { RescueTheoryView } from './RescueTheoryView'
import { RescueScenarioView } from './RescueScenarioView'
/* ─── Types ─── */
type AccidentType = 'collision' | 'grounding' | 'turning' | 'capsizing' | 'sharpTurn' | 'flooding' | 'sinking'
type AnalysisTab = 'rescue' | 'damageStability' | 'longitudinalStrength'
/* ─── 사고 유형 데이터 ─── */
const accidentTypes: { id: AccidentType; label: string; eng: string; icon: string; desc: string }[] = [
{ id: 'collision', label: '충돌', eng: 'Collision', icon: '💥', desc: '외력에 의한 선체 손상' },
{ id: 'grounding', label: '좌초', eng: 'Grounding', icon: '🪨', desc: '해저 접촉/암초 좌초' },
{ id: 'turning', label: '선회', eng: 'Turning', icon: '🔄', desc: '급격한 방향전환 사고' },
{ id: 'capsizing', label: '전복', eng: 'Capsizing', icon: '🔃', desc: '복원력 상실에 의한 전복' },
{ id: 'sharpTurn', label: '급선회', eng: 'Hard Turn', icon: '↩️', desc: '고속 급선회에 의한 경사' },
{ id: 'flooding', label: '침수', eng: 'Flooding', icon: '🌊', desc: '해수 유입에 의한 침수' },
{ id: 'sinking', label: '침몰', eng: 'Sinking', icon: '⬇️', desc: '부력 상실에 의한 침몰' },
]
const analysisTabs: { id: AnalysisTab; label: string; icon: string }[] = [
{ id: 'rescue', label: '구난분석', icon: '🚨' },
{ id: 'damageStability', label: '손상복원성', icon: '⚖' },
{ id: 'longitudinalStrength', label: '종강도', icon: '📏' },
]
/* ─── 사고 유형별 파라미터 ─── */
const rscTypeData: Record<AccidentType, {
zone: string; gm: string; list: string; trim: string; buoy: number
incident: string; survivors: number; total: number; missing: number; oilRate: string
}> = {
collision: { zone: 'PREDICTED\nDAMAGE ZONE', gm: '0.8', list: '15.0', trim: '2.5', buoy: 30, incident: 'M/V SEA GUARDIAN 충돌 사고', survivors: 15, total: 20, missing: 5, oilRate: '100L/min' },
grounding: { zone: 'GROUNDING\nIMPACT AREA', gm: '1.2', list: '8.0', trim: '3.8', buoy: 45, incident: 'M/V OCEAN STAR 좌초 사고', survivors: 22, total: 25, missing: 3, oilRate: '50L/min' },
turning: { zone: 'PREDICTED\nDRIFT PATH', gm: '1.5', list: '12.0', trim: '0.8', buoy: 65, incident: 'M/V PACIFIC WAVE 선회 사고', survivors: 18, total: 18, missing: 0, oilRate: '0L/min' },
capsizing: { zone: 'CAPSIZING\nRISK ZONE', gm: '0.2', list: '45.0', trim: '1.2', buoy: 10, incident: 'M/V GRAND FORTUNE 전복 사고', survivors: 8, total: 15, missing: 7, oilRate: '200L/min' },
sharpTurn: { zone: 'VESSEL\nTURNING CIRCLE', gm: '0.6', list: '25.0', trim: '0.5', buoy: 50, incident: 'M/V BLUE HORIZON 급선회 사고', survivors: 20, total: 20, missing: 0, oilRate: '0L/min' },
flooding: { zone: 'FLOODING\nSPREAD AREA', gm: '0.5', list: '18.0', trim: '4.2', buoy: 20, incident: 'M/V EASTERN GLORY 침수 사고', survivors: 12, total: 16, missing: 4, oilRate: '80L/min' },
sinking: { zone: 'PREDICTED\nSINKING AREA', gm: '0.1', list: '35.0', trim: '6.0', buoy: 5, incident: 'M/V HARMONY 침몰 사고', survivors: 10, total: 22, missing: 12, oilRate: '350L/min' },
}
/* ─── 색상 헬퍼 ─── */
function gmColor(v: string) { const n = parseFloat(v); return n < 1.0 ? 'var(--red)' : n < 1.5 ? 'var(--yellow)' : 'var(--green)' }
function listColor(v: string) { const n = parseFloat(v); return n > 20 ? 'var(--red)' : n > 10 ? 'var(--yellow)' : 'var(--green)' }
function trimColor(v: string) { const n = parseFloat(v); return n > 3 ? 'var(--red)' : n > 1.5 ? 'var(--yellow)' : 'var(--green)' }
function buoyColor(v: number) { return v < 30 ? 'var(--red)' : v < 50 ? 'var(--yellow)' : 'var(--green)' }
/* ─── 상단 사고 정보바 ─── */
function TopInfoBar({ activeType }: { activeType: AccidentType }) {
const d = rscTypeData[activeType]
const [clock, setClock] = useState('')
useEffect(() => {
const tick = () => {
const now = new Date()
setClock(`${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')} KST`)
}
tick()
const iv = setInterval(tick, 1000)
return () => clearInterval(iv)
}, [])
return (
<div className="h-9 bg-gradient-to-r from-bg-0 to-bg-2 border-b border-border flex items-center px-4 gap-3.5 flex-shrink-0">
<div className="flex items-center gap-1.5">
<span className="text-sm"></span>
<span className="text-[12px] font-bold text-text-1 font-korean"></span>
</div>
<div className="px-3.5 py-0.5 bg-[rgba(239,68,68,0.15)] border border-[rgba(239,68,68,0.35)] rounded-xl text-[10px] font-bold text-status-red font-korean">
: {d.incident}
</div>
<div className="flex gap-3 text-[9px] font-mono text-text-3 ml-auto">
<span>: <b className="text-text-1">{d.survivors}</b>/{d.total}</span>
<span>: <b className="text-status-red">{d.missing}</b></span>
<span>GM: <b style={{ color: gmColor(d.gm) }}>{d.gm}m</b></span>
<span>: <b style={{ color: listColor(d.list) }}>{d.list}°</b></span>
<span>: <b className="text-status-orange">{d.oilRate}</b></span>
</div>
<div className="text-[13px] font-bold text-text-1 font-mono">{clock}</div>
<div className="text-[9px] text-text-3 font-korean">👤 Cmdr. KIM</div>
</div>
)
}
/* ─── 왼쪽 패널: 사고유형 + 긴급경고 + CCTV ─── */
function LeftPanel({
activeType, onTypeChange,
}: {
activeType: AccidentType
onTypeChange: (t: AccidentType) => void
}) {
return (
<div className="w-[208px] min-w-[208px] bg-bg-0 border-r border-border flex flex-col overflow-y-auto scrollbar-thin p-2 gap-0.5">
{/* 사고유형 제목 */}
<div className="text-[9px] font-bold text-[var(--blue)] font-korean mb-0.5 tracking-wider"> (INCIDENT TYPE)</div>
{/* 사고유형 버튼 */}
{accidentTypes.map(t => (
<button
key={t.id}
onClick={() => onTypeChange(t.id)}
className={`flex items-center gap-2 w-full text-left px-2.5 py-[7px] rounded-md border transition-all cursor-pointer ${
activeType === t.id
? 'bg-[rgba(6,182,212,0.1)] border-[rgba(6,182,212,0.35)]'
: 'bg-bg-3 border-border hover:border-[var(--bdL)] hover:bg-bg-hover'
}`}
>
<span className="text-[12px]">{t.icon}</span>
<div>
<div className={`text-[10px] font-bold font-korean ${activeType === t.id ? 'text-[var(--cyan)]' : 'text-text-1'}`}>
{t.label} ({t.eng})
</div>
<div className="text-[7px] text-text-3 font-korean mt-px">{t.desc}</div>
</div>
</button>
))}
{/* 긴급 경고 */}
<div className="text-[9px] font-bold text-text-3 font-korean mt-2.5 mb-1"> (CRITICAL ALERTS)</div>
<div className="py-1.5 px-2.5 bg-[rgba(239,68,68,0.15)] border-l-[3px] border-l-[var(--red)] rounded-r text-[9px] font-bold text-status-red font-korean">
GM
</div>
<div className="py-1.5 px-2.5 bg-[rgba(239,68,68,0.15)] border-l-[3px] border-l-[var(--red)] rounded-r text-[9px] font-bold text-status-red font-korean">
5
</div>
<div className="py-1.5 px-2.5 bg-[rgba(249,115,22,0.12)] border-l-[3px] border-l-[var(--orange)] rounded-r text-[9px] font-bold text-status-orange font-korean">
-
</div>
<div className="py-1.5 px-2.5 bg-[rgba(251,191,36,0.1)] border-l-[3px] border-l-[var(--yellow)] rounded-r text-[9px] font-bold text-status-yellow font-korean">
88%
</div>
{/* CCTV 피드 */}
<div className="w-full aspect-[4/3] bg-[#1a0000] border border-border rounded-md flex items-center justify-center relative overflow-hidden mt-1.5 flex-shrink-0">
<div className="absolute inset-0" style={{ background: 'repeating-linear-gradient(0deg, rgba(255,0,0,.03), transparent 2px)' }} />
<div className="text-[9px] text-[rgba(255,60,60,0.35)] font-mono">CCTV FEED #1</div>
<div className="absolute top-1 left-1.5 text-[7px] text-[rgba(255,60,60,0.5)] font-mono"> REC</div>
</div>
</div>
)
}
/* ─── 중앙 지도 영역 ─── */
function CenterMap({ activeType }: { activeType: AccidentType }) {
const d = rscTypeData[activeType]
const at = accidentTypes.find(t => t.id === activeType)!
return (
<div className="flex-1 relative overflow-hidden" style={{ background: '#0a1628' }}>
{/* 해양 배경 그라데이션 */}
<div className="absolute inset-0" style={{
background: 'radial-gradient(ellipse at 30% 40%, rgba(6,90,130,.25) 0%, transparent 60%), radial-gradient(ellipse at 70% 60%, rgba(8,60,100,.2) 0%, transparent 50%), linear-gradient(180deg, #0a1628, #0d1f35 50%, #091520)'
}} />
{/* 격자 */}
<div className="absolute inset-0" style={{
backgroundImage: 'linear-gradient(rgba(30,60,100,.12) 1px, transparent 1px), linear-gradient(90deg, rgba(30,60,100,.12) 1px, transparent 1px)',
backgroundSize: '80px 80px'
}} />
{/* 해안선 힌트 */}
<div className="absolute right-0 top-0 w-[55%] h-full" style={{
background: 'linear-gradient(225deg, rgba(30,50,70,.55), rgba(20,35,50,.25) 35%, transparent 55%)',
clipPath: 'polygon(60% 0%, 65% 5%, 70% 12%, 72% 20%, 68% 30%, 65% 40%, 60% 50%, 55% 58%, 50% 65%, 45% 72%, 42% 80%, 48% 88%, 100% 100%, 100% 0%)'
}} />
{/* 사고 해역 정보 */}
<div className="absolute top-2.5 left-2.5 z-20 bg-[rgba(13,17,23,0.92)] border border-border rounded-md px-3 py-2 font-mono text-[9px] text-text-3">
<div className="text-[10px] font-bold text-text-1 font-korean mb-1"> </div>
<div className="grid gap-x-1.5 gap-y-px" style={{ gridTemplateColumns: '32px 1fr' }}>
<span></span><b className="text-text-1">37°28'N, 126°15'E</b>
<span></span><b className="text-text-1">45m</b>
<span></span><b className="text-text-1">2.5 knots NE</b>
</div>
</div>
{/* 선박 모형 */}
<div className="absolute z-[15]" style={{ top: '42%', left: '46%', transform: 'rotate(-15deg)' }}>
<div className="relative" style={{
width: '72px', height: '20px',
background: 'linear-gradient(90deg, #4a3728, #6b4c33)',
borderRadius: '3px 10px 10px 3px',
border: '1px solid rgba(255,150,50,.4)',
boxShadow: '0 0 18px rgba(255,100,0,.2)'
}}>
<div className="absolute" style={{ top: '-3px', left: '60%', width: '2px', height: '7px', background: '#888', borderRadius: '1px' }} />
</div>
<div className="text-[7px] text-center mt-0.5 font-mono" style={{ color: 'rgba(255,200,150,.5)' }}>M/V SEA GUARDIAN</div>
</div>
{/* 예측 구역 원 */}
<div className="absolute z-[5] rounded-full" style={{
top: '32%', left: '32%', width: '220px', height: '220px',
background: 'radial-gradient(circle, rgba(6,182,212,.07), rgba(6,182,212,.02) 50%, transparent 70%)',
border: '1.5px dashed rgba(6,182,212,.2)'
}} />
{/* 구역 라벨 */}
<div className="absolute z-[6] text-[8px] font-korean font-semibold tracking-wider uppercase" style={{
top: '50%', left: '36%', color: 'rgba(6,182,212,.45)', whiteSpace: 'pre-line'
}}>
{d.zone.replace('\\n', '\n')}
</div>
{/* SAR 자산 */}
<div className="absolute z-10 text-[7px] font-mono" style={{ top: '10%', left: '42%', color: 'rgba(200,220,255,.35)' }}>ETA 5 MIN </div>
<div className="absolute z-10 text-[7px] font-mono" style={{ top: '14%', left: '56%', color: 'rgba(200,220,255,.35)' }}>ETA 15 MIN </div>
<div className="absolute z-[12] text-sm" style={{ top: '7%', left: '52%', opacity: 0.6, transform: 'rotate(-30deg)' }}>🚁</div>
<div className="absolute z-[12] text-[8px] font-mono" style={{ top: '20%', left: '60%', color: 'rgba(200,220,255,.45)' }}>6M</div>
<div className="absolute z-[12] text-[11px]" style={{ top: '28%', left: '54%', opacity: 0.45 }}>🚢</div>
{/* 환경 민감 구역 */}
<div className="absolute z-10 px-3.5 py-2 rounded" style={{
bottom: '6%', right: '6%',
background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.2)'
}}>
<div className="text-[9px] font-bold font-serif uppercase tracking-wider leading-snug" style={{ color: 'rgba(34,197,94,.55)' }}>
ENVIRONMENTALLY SENSITIVE<br />AREA: AQUACULTURE FARM
</div>
</div>
{/* 지도 컨트롤 */}
<div className="absolute top-2.5 right-2.5 z-20 flex flex-col gap-0.5">
{['🗺', '🔍', '📐'].map((ico, i) => (
<button key={i} className="w-7 h-7 bg-[rgba(13,17,23,0.85)] border border-border rounded text-text-3 text-[12px] flex items-center justify-center cursor-pointer hover:text-text-1">{ico}</button>
))}
</div>
{/* 스케일 바 */}
<div className="absolute bottom-2.5 left-2.5 z-20 bg-[rgba(13,17,23,0.8)] border border-border rounded px-2.5 py-1 text-[8px] text-text-3 font-mono">
<div className="w-[70px] h-0.5 mb-0.5" style={{ background: 'linear-gradient(90deg, #e4e8f1 50%, var(--bd) 50%)' }} />
5 km · Zoom: 100%
</div>
{/* 사고 유형 표시 */}
<div className="absolute bottom-2.5 right-2.5 z-20 bg-[rgba(13,17,23,0.85)] border border-border rounded px-3 py-1.5">
<div className="text-[8px] text-text-3 font-korean"> </div>
<div className="text-[11px] font-bold font-korean" style={{ color: 'var(--cyan)' }}>{at.icon} {at.label} ({at.eng})</div>
</div>
</div>
)
}
/* ─── 오른쪽 분석 패널 ─── */
function RightPanel({
activeAnalysis, onAnalysisChange, activeType,
}: {
activeAnalysis: AnalysisTab
onAnalysisChange: (t: AnalysisTab) => void
activeType: AccidentType
}) {
return (
<div className="w-[310px] min-w-[310px] bg-bg-0 border-l border-border flex flex-col overflow-hidden">
{/* 분석 탭 */}
<div className="flex border-b border-border flex-shrink-0">
{analysisTabs.map(tab => (
<button
key={tab.id}
onClick={() => onAnalysisChange(tab.id)}
className={`flex-1 py-2 text-[9px] font-semibold font-korean cursor-pointer text-center transition-all border-b-2 ${
activeAnalysis === tab.id
? 'text-[var(--cyan)] border-b-[var(--cyan)] bg-[rgba(6,182,212,0.04)]'
: 'text-text-3 border-b-transparent hover:text-text-1'
}`}
>
{tab.icon} {tab.label}
</button>
))}
</div>
{/* 패널 콘텐츠 */}
<div className="flex-1 overflow-y-auto scrollbar-thin">
{activeAnalysis === 'rescue' && <RescuePanel activeType={activeType} />}
{activeAnalysis === 'damageStability' && <DamageStabilityPanel activeType={activeType} />}
{activeAnalysis === 'longitudinalStrength' && <LongStrengthPanel activeType={activeType} />}
</div>
{/* Bottom Action Buttons */}
<div className="flex gap-1.5 p-3 border-t border-border flex-shrink-0">
<button className="flex-1 py-2 px-1 rounded text-[11px] font-semibold bg-gradient-to-r from-boom to-[#d97706] text-black font-korean">
💾
</button>
<button className="flex-1 py-2 px-1 rounded text-[11px] font-semibold bg-[rgba(249,115,22,0.1)] border border-[rgba(249,115,22,0.3)] text-status-orange font-korean">
🔄
</button>
<button className="flex-1 py-2 px-1 rounded text-[11px] font-semibold bg-gradient-to-r from-primary-cyan to-primary-blue text-white font-korean">
📄
</button>
</div>
</div>
)
}
/* ─── 구난 분석 패널 ─── */
function RescuePanel({ activeType }: { activeType: AccidentType }) {
const d = rscTypeData[activeType]
const at = accidentTypes.find(t => t.id === activeType)!
return (
<div className="flex flex-col p-2.5 gap-2">
<div className="text-[12px] font-bold text-text-1 font-korean"> (RESCUE ANALYSIS)</div>
<div className="text-[9px] text-[var(--blue)] font-korean px-2 py-1 bg-[rgba(88,166,255,0.08)] border border-[rgba(88,166,255,0.2)] rounded">
📌 : {at.label} ({at.eng})
</div>
{/* 선박 단면도 SVG */}
<div className="bg-bg-3 border border-border rounded-md p-2 relative">
<div className="absolute top-1.5 right-2 text-[7px] text-text-3 font-mono">VESSEL STATUS</div>
<svg viewBox="0 0 260 80" className="w-full" style={{ height: '65px' }}>
<rect x="30" y="24" width="170" height="28" rx="4" fill="var(--bg-hover)" stroke="var(--bdL)" strokeWidth=".8" />
<rect x="85" y="15" width="55" height="10" rx="2" fill="var(--bg-hover)" stroke="var(--bdL)" strokeWidth=".6" />
<rect x="42" y="27" width="30" height="18" rx="1" fill="rgba(239,68,68,.12)" stroke="rgba(239,68,68,.35)" strokeWidth=".6" />
<text x="57" y="39" fill="rgba(239,68,68,.5)" fontSize="4.5" textAnchor="middle" fontFamily="monospace">FLOODED</text>
<circle cx="42" cy="38" r="6" fill="none" stroke="var(--red)" strokeWidth="1" strokeDasharray="2,1" />
<text x="42" y="50" fill="var(--red)" fontSize="4" textAnchor="middle" fontFamily="monospace">IMPACT</text>
<line x1="30" y1="52" x2="200" y2="52" stroke="var(--bd)" strokeWidth=".4" strokeDasharray="3,2" />
<text x="205" y="55" fill="var(--t3)" fontSize="4.5" fontFamily="monospace">WL</text>
<line x1="130" y1="8" x2="130" y2="60" stroke="rgba(251,191,36,.15)" strokeWidth=".4" strokeDasharray="2,2" />
<text x="230" y="20" fill="var(--t3)" fontSize="5">LIST: {d.list}°</text>
<text x="230" y="28" fill="var(--t3)" fontSize="5">TRIM: {d.trim}m</text>
</svg>
</div>
{/* 핵심 지표 2×2 */}
<div className="grid grid-cols-2 gap-1.5">
<MetricCard label="GM (메타센터 높이)" value={d.gm} unit="m" color={gmColor(d.gm)} sub={`${parseFloat(d.gm) < 1.0 ? '위험' : parseFloat(d.gm) < 1.5 ? '주의' : '정상'} (기준: 1.5m 이상)`} subColor={gmColor(d.gm)} />
<MetricCard label="LIST (횡경사)" value={d.list} unit="°" color={listColor(d.list)} sub={`${parseFloat(d.list) > 20 ? '위험' : parseFloat(d.list) > 10 ? '주의' : '정상'} (기준: 10° 이내)`} subColor={listColor(d.list)} />
</div>
<div className="grid grid-cols-2 gap-1.5">
<MetricCard label="TRIM (종경사)" value={d.trim} unit="m" color={trimColor(d.trim)} sub="선미 침하 (AFT SINKAGE)" subColor={trimColor(d.trim)} />
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[8px] text-text-3 font-korean"> (Reserve Buoyancy)</div>
<div className="text-xl font-bold font-mono" style={{ color: buoyColor(d.buoy) }}>
{d.buoy}<span className="text-[10px]">%</span>
</div>
<div className="h-[5px] bg-bg-hover rounded-sm mt-0.5">
<div className="h-full rounded-sm transition-all" style={{ width: `${d.buoy}%`, background: buoyColor(d.buoy) }} />
</div>
</div>
</div>
{/* 긴급 조치 버튼 */}
<div className="text-[10px] font-bold text-text-3 font-korean"> (EMERGENCY ACTIONS)</div>
<div className="grid grid-cols-2 gap-1.5">
{[
{ en: 'BALLAST INJECT', ko: '밸러스트 주입', color: 'var(--red)' },
{ en: 'BALLAST DISCHARGE', ko: '밸러스트 배출', color: 'var(--red)' },
{ en: 'ENGINE STOP', ko: '기관 정지', color: 'var(--orange)' },
{ en: 'ANCHOR DROP', ko: '묘 투하', color: 'var(--orange)' },
].map((btn, i) => (
<button key={i} className="py-[7px] rounded-[5px] text-center cursor-pointer transition-all hover:brightness-125" style={{
background: `color-mix(in srgb, ${btn.color} 8%, transparent)`,
border: `1px solid color-mix(in srgb, ${btn.color} 25%, transparent)`,
}}>
<div className="text-[10px] font-bold text-text-1 font-mono">{btn.en}</div>
<div className="text-[8px] font-korean" style={{ color: btn.color }}>{btn.ko}</div>
</button>
))}
</div>
{/* 구난 의사결정 프로세스 */}
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5"> (KRISO Decision Support)</div>
<div className="flex flex-col gap-0.5 text-[7px] font-korean">
<div className="flex items-center gap-1">
{[
{ label: '① 상태평가', color: 'var(--cyan)' },
{ label: '② 사례분석', color: 'var(--blue)' },
{ label: '③ 장비선정', color: 'var(--t2)' },
].map((s, i) => (
<>{i > 0 && <span className="text-text-3"></span>}
<div key={i} className="px-1.5 py-0.5 rounded-sm text-center flex-shrink-0" style={{
background: `color-mix(in srgb, ${s.color} 12%, transparent)`,
border: `1px solid color-mix(in srgb, ${s.color} 25%, transparent)`,
color: s.color, minWidth: '68px'
}}>{s.label}</div></>
))}
</div>
<div className="flex items-center gap-1">
{[
{ label: '④ 예인력', color: 'var(--yellow)' },
{ label: '⑤ 이초/인양', color: 'var(--orange)' },
{ label: '⑥ 유출량', color: 'var(--red)' },
].map((s, i) => (
<>{i > 0 && <span className="text-text-3"></span>}
<div key={i} className="px-1.5 py-0.5 rounded-sm text-center flex-shrink-0" style={{
background: `color-mix(in srgb, ${s.color} 10%, transparent)`,
border: `1px solid color-mix(in srgb, ${s.color} 25%, transparent)`,
color: s.color, minWidth: '68px'
}}>{s.label}</div></>
))}
</div>
</div>
</div>
{/* 유체 정역학 */}
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5"> (Hydrostatics)</div>
<div className="grid grid-cols-2 gap-1 text-[8px] font-mono">
{[
{ label: '배수량(Δ)', value: '12,450 ton' },
{ label: '흘수(Draft)', value: '7.2 m' },
{ label: 'KG', value: '8.5 m' },
{ label: 'KM (횡)', value: '9.3 m' },
{ label: 'TPC', value: '22.8 t/cm' },
{ label: 'MTC', value: '185 t·m' },
].map((r, i) => (
<div key={i} className="px-1.5 py-1 bg-bg-0 rounded-sm">
<span className="text-text-3 font-korean text-[7px]">{r.label}</span><br />
<b className="text-text-1">{r.value}</b>
</div>
))}
</div>
</div>
{/* 예인력/이초력 */}
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5"> / (Towing & Refloating)</div>
<div className="grid grid-cols-2 gap-1 text-[8px] font-mono">
{[
{ label: '필요 예인력', value: '285 kN', color: 'var(--yellow)' },
{ label: '비상 예인력', value: '420 kN', color: 'var(--red)' },
{ label: '이초 반력', value: '1,850 kN', color: 'var(--orange)' },
{ label: '인양 안전성', value: 'FAIL', color: 'var(--red)' },
].map((r, i) => (
<div key={i} className="px-1.5 py-1 bg-bg-0 rounded-sm">
<span className="text-text-3 font-korean text-[7px]">{r.label}</span><br />
<b style={{ color: r.color }}>{r.value}</b>
</div>
))}
</div>
<div className="text-[7px] text-text-3 font-korean mt-1"> IMO Salvage Manual / Resistance Increase Ratio </div>
</div>
{/* 유출량 추정 */}
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5"> (Oil Outflow Estimation)</div>
<div className="grid grid-cols-3 gap-1 text-[8px] font-mono">
{[
{ label: '현재 유출률', value: d.oilRate, color: 'var(--orange)' },
{ label: '누적 유출량', value: '6.8 kL', color: 'var(--red)' },
{ label: '24h 예측', value: '145 kL', color: 'var(--red)' },
].map((r, i) => (
<div key={i} className="px-1.5 py-1 bg-bg-0 rounded-sm text-center">
<span className="text-text-3 font-korean text-[7px]">{r.label}</span><br />
<b style={{ color: r.color }}>{r.value}</b>
</div>
))}
</div>
<div className="mt-1 h-1 bg-bg-hover rounded-sm">
<div className="h-full rounded-sm" style={{ width: '68%', background: 'linear-gradient(90deg, var(--orange), var(--red))' }} />
</div>
<div className="text-[7px] text-text-3 font-korean mt-0.5"> 연료유: 210 kL | 잔량: 68% </div>
</div>
{/* CBR 사례기반 추론 */}
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5">CBR (Case-Based Reasoning)</div>
<div className="flex flex-col gap-0.5">
{[
{ pct: '94%', name: 'Hebei Spirit (2007)', desc: '태안 · 충돌 · 원유 12,547kL 유출', color: 'var(--cyan)' },
{ pct: '87%', name: 'Sea Empress (1996)', desc: '밀포드 · 좌초 · 72,000t 유출', color: 'var(--blue)' },
{ pct: '82%', name: 'Rena (2011)', desc: '타우랑가 · 좌초 · 350t HFO 유출', color: 'var(--t2)' },
].map((c, i) => (
<div key={i} className="flex items-center gap-1.5 px-1.5 py-1 rounded-sm cursor-pointer hover:brightness-125" style={{
background: `color-mix(in srgb, ${c.color} 6%, transparent)`,
border: `1px solid color-mix(in srgb, ${c.color} 15%, transparent)`,
}}>
<div className="w-7 h-4 rounded-sm flex items-center justify-center text-[7px] font-bold font-mono" style={{
background: `color-mix(in srgb, ${c.color} 20%, transparent)`, color: c.color
}}>{c.pct}</div>
<div className="flex-1 text-[7px] font-korean">
<b className="text-text-1">{c.name}</b><br />
<span className="text-text-3">{c.desc}</span>
</div>
</div>
))}
</div>
</div>
{/* 위험도 평가 */}
<div className="bg-[rgba(239,68,68,0.04)] border border-[rgba(239,68,68,0.15)] rounded-md p-2">
<div className="text-[9px] font-bold text-status-red font-korean mb-1.5"> 2 </div>
<div className="flex flex-col gap-0.5 text-[7px] font-korean">
{[
{ label: '침수 확대 → 전복', level: 'HIGH', color: 'var(--red)' },
{ label: '유류 대량 유출 → 해양오염', level: 'HIGH', color: 'var(--orange)' },
{ label: '선체 절단 (BM 초과)', level: 'MED', color: 'var(--yellow)' },
{ label: '화재/폭발', level: 'LOW', color: 'var(--t2)' },
].map((r, i) => (
<div key={i} className="flex items-center justify-between px-1.5 py-0.5 rounded-sm" style={{
background: `color-mix(in srgb, ${r.color} 8%, transparent)`,
}}>
<span className="text-text-1">{r.label}</span>
<span className="px-1.5 py-px rounded-lg text-[7px] font-bold" style={{
background: `color-mix(in srgb, ${r.color} 20%, transparent)`, color: r.color
}}>{r.level}</span>
</div>
))}
</div>
</div>
{/* 해상 e-Call */}
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5"> e-Call (GMDSS / VHF-DSC)</div>
<div className="flex flex-col gap-0.5 text-[7px] font-mono text-text-3">
{[
{ label: 'MMSI', value: '440123456', color: 'var(--t1)' },
{ label: 'Nature of Distress', value: 'COLLISION', color: 'var(--red)' },
{ label: 'DSC Alert', value: 'SENT ✓', color: 'var(--green)' },
{ label: 'EPIRB', value: 'ACTIVATED ✓', color: 'var(--green)' },
{ label: 'VTS 인천', value: 'ACK 10:36', color: 'var(--blue)' },
].map((r, i) => (
<div key={i} className="flex justify-between">
<span className="font-korean">{r.label}</span>
<b style={{ color: r.color }}>{r.value}</b>
</div>
))}
</div>
<button className="w-full mt-1 py-1 bg-[rgba(239,68,68,0.12)] border border-[rgba(239,68,68,0.3)] rounded text-[8px] font-bold text-status-red cursor-pointer font-korean hover:bg-[rgba(239,68,68,0.2)]">
📡 DISTRESS RELAY
</button>
</div>
</div>
)
}
/* ─── 손상 복원성 패널 ─── */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function DamageStabilityPanel(_props: { activeType: AccidentType }) {
return (
<div className="flex flex-col p-2.5 gap-2">
<div className="text-[12px] font-bold text-text-1 font-korean"> (DAMAGE STABILITY)</div>
<div className="text-[9px] text-text-3 font-korean leading-snug">
<br />IMO A.749(18) / SOLAS Ch.II-1
</div>
{/* GZ Curve SVG */}
<div className="bg-bg-3 border border-border rounded-md p-2.5">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5">GZ (Righting Lever Curve)</div>
<svg viewBox="0 0 260 120" className="w-full" style={{ height: '100px' }}>
<line x1="30" y1="10" x2="30" y2="100" stroke="var(--bd)" strokeWidth=".5" />
<line x1="30" y1="100" x2="255" y2="100" stroke="var(--bd)" strokeWidth=".5" />
<line x1="30" y1="55" x2="255" y2="55" stroke="var(--bd)" strokeWidth=".3" strokeDasharray="3,3" />
<line x1="30" y1="32" x2="255" y2="32" stroke="var(--bd)" strokeWidth=".3" strokeDasharray="3,3" />
<polyline points="30,100 55,80 80,55 105,38 130,30 155,32 180,42 205,60 230,85 250,100" fill="none" stroke="var(--blue)" strokeWidth="1.5" />
<text x="135" y="25" fill="var(--blue)" fontSize="5" textAnchor="middle"> GZ</text>
<polyline points="30,100 55,88 80,72 105,62 130,58 155,62 180,75 205,92 220,100" fill="none" stroke="var(--red)" strokeWidth="1.5" />
<text x="140" y="54" fill="var(--red)" fontSize="5" textAnchor="middle"> GZ</text>
<line x1="30" y1="78" x2="255" y2="78" stroke="rgba(251,191,36,.4)" strokeWidth=".6" strokeDasharray="4,2" />
<text x="240" y="76" fill="var(--yellow)" fontSize="4.5" fontFamily="monospace">IMO MIN</text>
<text x="5" y="14" fill="var(--t3)" fontSize="5" fontFamily="monospace">GZ(m)</text>
<text x="5" y="35" fill="var(--t3)" fontSize="4.5" fontFamily="monospace">0.6</text>
<text x="5" y="58" fill="var(--t3)" fontSize="4.5" fontFamily="monospace">0.4</text>
<text x="5" y="82" fill="var(--t3)" fontSize="4.5" fontFamily="monospace">0.2</text>
<text x="5" y="104" fill="var(--t3)" fontSize="4.5" fontFamily="monospace">0</text>
<text x="30" y="112" fill="var(--t3)" fontSize="4.5" fontFamily="monospace">0°</text>
<text x="80" y="112" fill="var(--t3)" fontSize="4.5" fontFamily="monospace">15°</text>
<text x="130" y="112" fill="var(--t3)" fontSize="4.5" fontFamily="monospace">30°</text>
<text x="180" y="112" fill="var(--t3)" fontSize="4.5" fontFamily="monospace">45°</text>
<text x="230" y="112" fill="var(--t3)" fontSize="4.5" fontFamily="monospace">60°</text>
</svg>
</div>
{/* 복원성 지표 */}
<div className="grid grid-cols-2 gap-1.5">
<MetricCard label="GZ_max (최대복원력)" value="0.25" unit="m" color="var(--red)" sub="기준: 0.2m 이상 ⚠" subColor="var(--red)" />
<MetricCard label="θ_max (최대복원각)" value="28" unit="°" color="var(--yellow)" sub="기준: 25° 이상 ✓" subColor="var(--yellow)" />
</div>
<div className="grid grid-cols-2 gap-1.5">
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[8px] text-text-3 font-korean"> </div>
<div className="text-lg font-bold text-status-red font-mono">2 <span className="text-[9px]"></span></div>
<div className="text-[7px] text-text-3 font-korean">#1 / #3 </div>
</div>
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[8px] text-text-3 font-korean">Margin Line </div>
<div className="text-lg font-bold text-status-red font-mono">0.12 <span className="text-[9px]">m</span></div>
<div className="text-[7px] text-status-red font-korean"> </div>
</div>
</div>
{/* SOLAS 판정 */}
<div className="bg-[rgba(239,68,68,0.06)] border border-[rgba(239,68,68,0.2)] rounded-md p-2.5">
<div className="text-[10px] font-bold text-status-red font-korean mb-1"> SOLAS 판정: 부적합 (FAIL)</div>
<div className="text-[8px] text-text-3 font-korean leading-snug">
· Area(0~θ_f): 0.028 m·rad ( 0.015 )<br />
· GZ_max 0.1m: 0.25m | θ_max 15°: 28° <br />
· <span className="text-status-red">Margin Line 침수: 0.12m </span>
</div>
</div>
{/* 좌초시 복원성 */}
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1"> (Grounded Stability)</div>
<div className="grid grid-cols-2 gap-1 text-[8px] font-mono">
{[
{ label: '지반반력', value: '1,240 kN', color: 'var(--yellow)' },
{ label: '접촉 면적', value: '12.5 m²', color: 'var(--t1)' },
{ label: '제거력(Removal)', value: '1,850 kN', color: 'var(--red)' },
{ label: '좌초 GM', value: '0.65 m', color: 'var(--yellow)' },
].map((r, i) => (
<div key={i} className="px-1.5 py-1 bg-bg-0 rounded-sm">
<span className="text-text-3 font-korean text-[7px]">{r.label}</span><br />
<b style={{ color: r.color }}>{r.value}</b>
</div>
))}
</div>
</div>
{/* 탱크 상태 */}
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1"> (Tank Volume Status)</div>
<div className="flex flex-col gap-0.5 text-[7px] font-korean">
{[
{ name: '#1 FP Tank', pct: 100, status: '침수', color: 'var(--red)' },
{ name: '#3 Port Tank', pct: 85, status: '85%', color: 'var(--red)' },
{ name: '#2 DB Tank', pct: 45, status: '45%', color: 'var(--green)' },
{ name: 'Ballast #4', pct: 72, status: '72%', color: 'var(--blue)' },
{ name: 'Fuel Oil #5', pct: 68, status: '68%', color: 'var(--orange)' },
].map((t, i) => (
<div key={i} className="flex items-center gap-1">
<div className="w-2 h-2 rounded-sm flex-shrink-0" style={{ background: t.color }} />
<span className="w-[65px] text-text-3">{t.name}</span>
<div className="flex-1 h-1 bg-bg-hover rounded-sm">
<div className="h-full rounded-sm" style={{ width: `${t.pct}%`, background: t.color }} />
</div>
<span className="min-w-[35px] text-right" style={{ color: t.color }}>{t.status}</span>
</div>
))}
</div>
</div>
</div>
)
}
/* ─── 종강도 패널 ─── */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function LongStrengthPanel(_props: { activeType: AccidentType }) {
return (
<div className="flex flex-col p-2.5 gap-2">
<div className="text-[12px] font-bold text-text-1 font-korean"> (LONGITUDINAL STRENGTH)</div>
<div className="text-[9px] text-text-3 font-korean leading-snug">
<br />IACS CSR / Classification Society
</div>
{/* 전단력 분포 SVG */}
<div className="bg-bg-3 border border-border rounded-md p-2.5">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5"> (Shear Force Distribution)</div>
<svg viewBox="0 0 260 90" className="w-full" style={{ height: '75px' }}>
<line x1="25" y1="45" x2="255" y2="45" stroke="var(--bd)" strokeWidth=".5" />
<line x1="25" y1="10" x2="25" y2="80" stroke="var(--bd)" strokeWidth=".5" />
<line x1="25" y1="18" x2="255" y2="18" stroke="rgba(239,68,68,.25)" strokeWidth=".5" strokeDasharray="3,2" />
<line x1="25" y1="72" x2="255" y2="72" stroke="rgba(239,68,68,.25)" strokeWidth=".5" strokeDasharray="3,2" />
<text x="240" y="15" fill="rgba(239,68,68,.4)" fontSize="4">LIMIT</text>
<polyline points="25,45 50,38 75,28 100,22 120,20 140,25 160,35 180,45 200,52 220,58 240,55 255,50" fill="none" stroke="var(--cyan)" strokeWidth="1.5" />
<polyline points="25,45 50,35 75,22 100,16 120,14 140,18 160,30 180,45 200,56 220,64 240,60 255,55" fill="none" stroke="var(--red)" strokeWidth="1.2" strokeDasharray="3,2" />
<text x="80" y="12" fill="var(--red)" fontSize="4.5"> SF </text>
<text x="2" y="14" fill="var(--t3)" fontSize="4" fontFamily="monospace">+SF</text>
<text x="2" y="48" fill="var(--t3)" fontSize="4" fontFamily="monospace">0</text>
<text x="2" y="78" fill="var(--t3)" fontSize="4" fontFamily="monospace">-SF</text>
<text x="25" y="88" fill="var(--t3)" fontSize="4" fontFamily="monospace">AP</text>
<text x="130" y="88" fill="var(--t3)" fontSize="4" fontFamily="monospace">MID</text>
<text x="245" y="88" fill="var(--t3)" fontSize="4" fontFamily="monospace">FP</text>
</svg>
</div>
{/* 굽힘모멘트 분포 SVG */}
<div className="bg-bg-3 border border-border rounded-md p-2.5">
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5"> (Bending Moment Distribution)</div>
<svg viewBox="0 0 260 90" className="w-full" style={{ height: '75px' }}>
<line x1="25" y1="45" x2="255" y2="45" stroke="var(--bd)" strokeWidth=".5" />
<line x1="25" y1="10" x2="25" y2="80" stroke="var(--bd)" strokeWidth=".5" />
<line x1="25" y1="15" x2="255" y2="15" stroke="rgba(239,68,68,.25)" strokeWidth=".5" strokeDasharray="3,2" />
<text x="240" y="12" fill="rgba(239,68,68,.4)" fontSize="4">LIMIT</text>
<polyline points="25,45 50,40 75,32 100,22 130,18 160,22 190,32 220,40 255,45" fill="none" stroke="var(--green)" strokeWidth="1.5" />
<polyline points="25,45 50,38 75,26 100,16 130,12 160,16 190,28 220,38 255,45" fill="none" stroke="var(--orange)" strokeWidth="1.2" strokeDasharray="3,2" />
<text x="115" y="8" fill="var(--orange)" fontSize="4.5"> BM </text>
<text x="2" y="14" fill="var(--t3)" fontSize="4" fontFamily="monospace">+BM</text>
<text x="2" y="48" fill="var(--t3)" fontSize="4" fontFamily="monospace">0</text>
<text x="25" y="88" fill="var(--t3)" fontSize="4" fontFamily="monospace">AP</text>
<text x="130" y="88" fill="var(--t3)" fontSize="4" fontFamily="monospace">MID</text>
<text x="245" y="88" fill="var(--t3)" fontSize="4" fontFamily="monospace">FP</text>
</svg>
</div>
{/* 종강도 지표 */}
<div className="grid grid-cols-2 gap-1.5">
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[8px] text-text-3 font-korean">SF / </div>
<div className="text-lg font-bold text-status-yellow font-mono">88<span className="text-[9px]">%</span></div>
<div className="h-[5px] bg-bg-hover rounded-sm mt-0.5">
<div className="h-full rounded-sm" style={{ width: '88%', background: 'var(--yellow)' }} />
</div>
</div>
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[8px] text-text-3 font-korean">BM / </div>
<div className="text-lg font-bold text-status-orange font-mono">92<span className="text-[9px]">%</span></div>
<div className="h-[5px] bg-bg-hover rounded-sm mt-0.5">
<div className="h-full rounded-sm" style={{ width: '92%', background: 'var(--orange)' }} />
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-1.5">
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[8px] text-text-3 font-korean">Section Modulus </div>
<div className="text-lg font-bold text-status-green font-mono">1.08</div>
<div className="text-[7px] text-status-green font-korean">Req'd: 1.00 이상 ✓</div>
</div>
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[8px] text-text-3 font-korean">Hull Girder ULS</div>
<div className="text-lg font-bold text-status-yellow font-mono">1.12</div>
<div className="text-[7px] text-status-yellow font-korean">Req'd: 1.10 </div>
</div>
</div>
{/* 판정 */}
<div className="bg-[rgba(251,191,36,0.06)] border border-[rgba(251,191,36,0.2)] rounded-md p-2.5">
<div className="text-[10px] font-bold text-status-yellow font-korean mb-1"> 판정: 주의 (CAUTION)</div>
<div className="text-[8px] text-text-3 font-korean leading-snug">
· SF 최대: 허용치의 88% <span className="text-status-yellow"> </span><br />
· BM 최대: 허용치의 92% <span className="text-status-orange"> </span><br />
· Hogging <br />
· <span className="text-status-red"> BM </span>
</div>
</div>
</div>
)
}
/* ─── 하단 바: 이벤트 로그 + 타임라인 ─── */
function BottomBar() {
return (
<div className="h-[145px] min-h-[145px] border-t border-border flex bg-bg-0 flex-shrink-0">
{/* 이벤트 로그 */}
<div className="flex-1 flex flex-col overflow-hidden border-r border-border">
<div className="flex items-center justify-between px-3 py-1 border-b border-border flex-shrink-0">
<span className="text-[9px] font-bold text-text-3 font-korean"> / (EVENT LOG / COMMUNICATION TRANSCRIPT)</span>
<div className="flex gap-0.5">
{[
{ label: '전체', color: 'var(--cyan)' },
{ label: '긴급', color: 'var(--orange)' },
{ label: '통신', color: 'var(--blue)' },
].map((f, i) => (
<button key={i} className="px-2 py-px rounded-sm text-[8px] font-bold cursor-pointer font-korean" style={{
background: `color-mix(in srgb, ${f.color} 15%, transparent)`,
border: `1px solid color-mix(in srgb, ${f.color} 30%, transparent)`,
color: f.color,
}}>{f.label}</button>
))}
</div>
</div>
<div className="flex-1 overflow-y-auto px-3 py-1 font-mono text-[9px] leading-[1.7] scrollbar-thin">
{[
{ time: '10:35', msg: 'SOS FROM M/V SEA GUARDIAN', color: 'var(--red)', bold: true },
{ time: '10:35', msg: 'OIL LEAK DETECTED SENSOR #3', color: 'var(--t1)', bold: false },
{ time: '10:40', msg: 'CG HELO DISPATCHED', color: 'var(--red)', bold: true },
{ time: '10:41', msg: 'GM CRITICAL ALERT — DAMAGE STABILITY FAIL', color: 'var(--red)', bold: true },
{ time: '10:42', msg: 'Coast Guard 123 en route — ETA 15 min', color: 'var(--blue)', bold: false },
{ time: '10:43', msg: 'LONGITUDINAL STRENGTH WARNING — BM 92% of LIMIT', color: 'var(--yellow)', bold: false },
{ time: '10:45', msg: 'BALLAST TRANSFER INITIATED — PORT #2 → STBD #3', color: 'var(--t1)', bold: false },
{ time: '10:48', msg: 'LIST INCREASING — 12° → 15°', color: 'var(--yellow)', bold: false },
{ time: '10:50', msg: 'RESCUE HELO ON SCENE — HOISTING OPS', color: 'var(--blue)', bold: false },
{ time: '10:55', msg: '5 SURVIVORS RECOVERED BY HELO', color: 'var(--green)', bold: false },
].map((e, i) => (
<div key={i}>
<span className="text-text-3">[{e.time}]</span>{' '}
<span style={{ color: e.color, fontWeight: e.bold ? 700 : 400 }}>{e.msg}</span>
</div>
))}
</div>
</div>
{/* 타임라인 시뮬레이션 */}
<div className="w-[330px] min-w-[330px] flex flex-col px-3.5 py-2 gap-1.5">
<div className="text-[9px] font-bold text-text-1 font-korean"> (TIMELINE SIMULATION CONTROL)</div>
<div className="flex items-center gap-1.5 text-[8px] text-text-3 font-mono">
<span>[-6h]</span>
<span className="flex-1 text-center font-bold text-text-1">[CURRENT]</span>
<span>[+6H]</span>
<span>[+12H]</span>
<span>[+24H]</span>
</div>
<div className="relative h-1.5 bg-bg-hover rounded-sm mx-1">
<div className="absolute rounded-full border-2 border-bg-0" style={{
left: '35%', top: '-3px', width: '12px', height: '12px',
background: 'var(--cyan)', boxShadow: '0 0 8px rgba(6,182,212,.4)'
}} />
</div>
<div className="flex items-center justify-center gap-2 mt-0.5">
<button className="w-7 h-7 bg-bg-3 border border-border rounded-full text-text-3 text-[11px] flex items-center justify-center cursor-pointer hover:text-text-1"></button>
<button className="w-[34px] h-[34px] bg-[rgba(6,182,212,0.15)] border border-[rgba(6,182,212,0.3)] rounded-full text-[var(--cyan)] text-[13px] flex items-center justify-center cursor-pointer hover:brightness-125"></button>
<button className="w-7 h-7 bg-bg-3 border border-border rounded-full text-text-3 text-[11px] flex items-center justify-center cursor-pointer hover:text-text-1"></button>
</div>
<div className="text-center text-[8px] text-text-3 font-mono">
: <b style={{ color: 'var(--cyan)' }}>10:45 KST</b>
</div>
</div>
</div>
)
}
/* ─── 공통 메트릭 카드 ─── */
function MetricCard({ label, value, unit, color, sub, subColor }: {
label: string; value: string; unit: string; color: string; sub: string; subColor: string
}) {
return (
<div className="bg-bg-3 border border-border rounded-md p-2">
<div className="text-[8px] text-text-3 font-korean">{label}</div>
<div className="text-xl font-bold font-mono" style={{ color }}>
{value}<span className="text-[10px]"> {unit}</span>
</div>
<div className="text-[8px] font-korean" style={{ color: subColor }}>{sub}</div>
</div>
)
}
/* ─── 긴급구난 목록 탭 ─── */
function RescueListView() {
const listData = [
{ status: '대응중', statusColor: 'var(--red)', no: 'RSC-2026-001', vessel: 'M/V SEA GUARDIAN', type: '충돌/좌초', date: '2026-02-17 10:30', location: '37°28\'N 126°15\'E', crew: '15/20' },
{ status: '대응중', statusColor: 'var(--orange)', no: 'RSC-2026-002', vessel: 'M/V EASTERN GLORY', type: '침수/전복', date: '2026-02-15 14:20', location: '35°05\'N 129°02\'E', crew: '22/28' },
{ status: '종료', statusColor: 'var(--green)', no: 'RSC-2025-048', vessel: 'M/V PACIFIC WAVE', type: '충돌', date: '2025-12-03 08:15', location: '34°45\'N 128°30\'E', crew: '18/18' },
{ status: '종료', statusColor: 'var(--green)', no: 'RSC-2025-047', vessel: 'M/V HARMONY', type: '좌초', date: '2025-11-20 22:40', location: '36°12\'N 126°50\'E', crew: '25/25' },
{ status: '종료', statusColor: 'var(--green)', no: 'RSC-2025-046', vessel: 'M/V GRAND FORTUNE', type: '침몰', date: '2025-10-08 05:30', location: '33°30\'N 127°15\'E', crew: '10/22' },
]
return (
<div className="flex flex-col flex-1 overflow-hidden">
<div className="px-5 py-4 flex items-center justify-between border-b border-border">
<span className="text-sm font-bold font-korean"> </span>
<div className="flex gap-2 items-center">
<input type="text" placeholder="선박명 / 사고번호 검색..." className="px-3 py-1.5 bg-bg-0 border border-border rounded-md text-text-2 font-korean text-[11px] w-[200px] outline-none focus:border-[var(--cyan)]" />
<button className="px-3.5 py-1.5 bg-[rgba(6,182,212,0.12)] border border-[rgba(6,182,212,0.3)] rounded-md text-[var(--cyan)] text-[11px] font-semibold cursor-pointer font-korean hover:bg-[rgba(6,182,212,0.2)]">
+
</button>
</div>
</div>
<div className="flex-1 overflow-y-auto px-5 pb-4">
<table className="w-full border-collapse text-[11px] mt-3">
<thead>
<tr className="bg-bg-3 border-b border-border">
{['상태', '사고번호', '선박명', '사고유형', '발생일시', '위치', '인명'].map(h => (
<th key={h} className="py-2 px-2.5 text-left font-korean font-semibold text-text-3 text-[10px]">{h}</th>
))}
</tr>
</thead>
<tbody>
{listData.map((r, i) => (
<tr key={i} className="border-b border-border hover:bg-bg-hover cursor-pointer">
<td className="py-2 px-2.5">
<span className="px-2 py-0.5 rounded-xl text-[9px] font-bold" style={{
background: `color-mix(in srgb, ${r.statusColor} 15%, transparent)`, color: r.statusColor
}}>{r.status}</span>
</td>
<td className="py-2 px-2.5 font-mono text-[var(--cyan)] font-semibold">{r.no}</td>
<td className="py-2 px-2.5 font-korean text-text-1 font-semibold">{r.vessel}</td>
<td className="py-2 px-2.5 font-korean">{r.type}</td>
<td className="py-2 px-2.5 font-mono text-text-3">{r.date}</td>
<td className="py-2 px-2.5 font-mono text-text-3 text-[10px]">{r.location}</td>
<td className="py-2 px-2.5 font-mono">{r.crew}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
/* ═══ 메인 RescueView ═══ */
export function RescueView() {
const { activeSubTab } = useSubMenu('rescue')
const [activeType, setActiveType] = useState<AccidentType>('collision')
const [activeAnalysis, setActiveAnalysis] = useState<AnalysisTab>('rescue')
if (activeSubTab === 'list') {
return (
<div className="flex flex-1 overflow-hidden">
<RescueListView />
</div>
)
}
if (activeSubTab === 'scenario') {
return <RescueScenarioView />
}
if (activeSubTab === 'theory') {
return <RescueTheoryView />
}
return (
<div className="flex flex-col flex-1 overflow-hidden">
{/* 상단 사고 정보바 */}
<TopInfoBar activeType={activeType} />
{/* 3단 레이아웃: 사고유형 | 지도 | 분석 패널 */}
<div className="flex flex-1 overflow-hidden">
<LeftPanel activeType={activeType} onTypeChange={setActiveType} />
<CenterMap activeType={activeType} />
<RightPanel activeAnalysis={activeAnalysis} onAnalysisChange={setActiveAnalysis} activeType={activeType} />
</div>
{/* 하단: 이벤트 로그 + 타임라인 */}
<BottomBar />
</div>
)
}