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(--bg3)', border: '1px solid var(--bd)', borderRadius: '6px', color: 'var(--t1)', fontSize: '11px', 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(--red)' : vessel.probability >= 20 ? 'var(--orange)' : 'var(--t3)' 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}
)}
) }