import { useRef, useEffect, useState } from 'react'; import type { BacktrackPhase, BacktrackVessel, BacktrackConditions, BacktrackInputConditions, } from '@common/types/backtrack'; interface BacktrackModalProps { isOpen: boolean; onClose: () => void; phase: BacktrackPhase; conditions: BacktrackConditions; vessels: BacktrackVessel[]; onRunAnalysis: (input: BacktrackInputConditions) => void; onStartReplay: () => void; } const toDateTimeLocalValue = (raw: string): string => { if (!raw) return ''; const d = new Date(raw); if (isNaN(d.getTime())) return ''; const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; }; const nowDateTimeLocalValue = (): string => { const d = new Date(); const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; }; export function BacktrackModal({ isOpen, onClose, phase, conditions, vessels, onRunAnalysis, onStartReplay, }: BacktrackModalProps) { const backdropRef = useRef(null); const [inputTimeOverride, setInputTime] = useState(undefined); const inputTime = inputTimeOverride ?? toDateTimeLocalValue(conditions.estimatedSpillTime) ?? nowDateTimeLocalValue(); const [inputRange, setInputRange] = useState('12'); const [inputRadius, setInputRadius] = useState(10); useEffect(() => { const handler = (e: MouseEvent) => { if (e.target === backdropRef.current) onClose(); }; if (isOpen) document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, [isOpen, onClose]); if (!isOpen) return null; const inputDisabled = phase !== 'conditions'; const inputStyle: React.CSSProperties = { width: '100%', padding: '6px 8px', background: 'var(--bg-card)', border: '1px solid var(--bd)', borderRadius: '6px', color: 'var(--t1)', fontSize: 'var(--font-size-caption)', fontFamily: 'var(--fM)', outline: 'none', opacity: inputDisabled ? 0.6 : 1, }; return (
{/* Header */}
๐Ÿ”

์œ ์ถœ์œ  ์—ญ์ถ”์  ๋ถ„์„

AIS ํ•ญ์  ๊ธฐ๋ฐ˜ ์œ ์ถœ ์„ ๋ฐ• ์ถ”์ •
{/* Scrollable Content */}
{/* Analysis Conditions */}

๋ถ„์„ ์กฐ๊ฑด

{/* ์œ ์ถœ ์ถ”์ • ์‹œ๊ฐ */}
์œ ์ถœ ์ถ”์ • ์‹œ๊ฐ
setInputTime(e.target.value)} disabled={inputDisabled} style={inputStyle} />
{/* ๋ถ„์„ ๋ฒ”์œ„ */}
๋ถ„์„ ๋ฒ”์œ„
{/* ํƒ์ƒ‰ ๋ฐ˜๊ฒฝ */}
ํƒ์ƒ‰ ๋ฐ˜๊ฒฝ
setInputRadius(Number(e.target.value))} disabled={inputDisabled} min={1} max={100} step={0.5} style={{ ...inputStyle, flex: 1 }} /> NM
{/* ์œ ์ถœ ์œ„์น˜ */}
์œ ์ถœ ์œ„์น˜
{conditions.spillLocation.lat.toFixed(4)}ยฐN,{' '} {conditions.spillLocation.lon.toFixed(4)}ยฐE
{/* ๋ถ„์„ ๋Œ€์ƒ ์„ ๋ฐ• */}
๋ถ„์„ ๋Œ€์ƒ ์„ ๋ฐ•
{conditions.totalVessels}์ฒ™{' '} (AIS ์ˆ˜์‹ )
{/* Results */} {phase === 'results' && vessels.length > 0 && (

๋ถ„์„ ๊ฒฐ๊ณผ

{conditions.totalVessels}์ฒ™ ์ค‘ {vessels.length}์ฒ™ ์˜์‹ฌ
{vessels.map((v) => ( ))}
)}
{/* Footer */}
{phase === 'conditions' && ( )} {phase === 'analyzing' && ( )} {phase === 'results' && ( )}
); } function VesselCard({ vessel }: { vessel: BacktrackVessel }) { const probColor = vessel.probability >= 80 ? 'var(--color-danger)' : vessel.probability >= 20 ? 'var(--color-warning)' : 'var(--fg-disabled)'; return (
{/* Header row */}
{vessel.rank}
{vessel.name}
IMO: {vessel.imo} ยท {vessel.type} ยท {vessel.flag}
{vessel.probability}%
์œ ์ถœ ํ™•๋ฅ 
{/* Stats grid */}
{[ { label: '์ตœ๊ทผ์ ‘ ์‹œ๊ฐ', value: vessel.closestTime }, { label: '์ตœ๊ทผ์ ‘ ๊ฑฐ๋ฆฌ', value: `${vessel.closestDistance} NM` }, { label: '์†๋„ ๋ณ€ํ™”', value: vessel.speedChange, highlight: vessel.speedChange === '๊ธ‰๊ฐ์†', }, { label: 'AIS ์ƒํƒœ', value: vessel.aisStatus, highlight: vessel.aisStatus === '์ถฉ๋Œ์‹ ํ˜ธ', }, ].map((s, i) => (
{s.label}
{s.value}
))}
{/* Description */} {vessel.description && (
{vessel.description}
)}
); }