import { useState } from 'react'; import type { VesselPosition } from '@common/types/vessel'; import { getShipKindLabel } from './VesselLayer'; export interface VesselHoverInfo { x: number; y: number; vessel: VesselPosition; } function formatDateTime(iso: string): string { const d = new Date(iso); if (Number.isNaN(d.getTime())) return '-'; const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; } function displayVal(v: unknown): string { if (v === undefined || v === null || v === '') return '-'; return String(v); } export function VesselHoverTooltip({ hover }: { hover: VesselHoverInfo }) { const v = hover.vessel; const speed = v.sog !== undefined ? `${v.sog.toFixed(1)} kn` : '- kn'; const heading = v.heading ?? v.cog; const headingText = heading !== undefined ? `HDG ${Math.round(heading)}°` : 'HDG -'; const typeText = [getShipKindLabel(v.shipKindCode) ?? v.shipTy ?? '-', v.nationalCode] .filter(Boolean) .join(' · '); return (
{v.shipNm ?? '(이름 없음)'}
{typeText}
{speed} {headingText}
); } export function VesselPopupPanel({ vessel: v, onClose, onDetail, }: { vessel: VesselPosition; onClose: () => void; onDetail: () => void; }) { const statusText = v.status ?? '-'; const isAccident = (v.status ?? '').includes('사고'); const statusColor = isAccident ? 'var(--color-danger)' : 'var(--color-success)'; const statusBg = isAccident ? 'color-mix(in srgb, var(--color-danger) 15%, transparent)' : 'color-mix(in srgb, var(--color-success) 10%, transparent)'; const speed = v.sog !== undefined ? `${v.sog.toFixed(1)} kn` : '-'; const heading = v.heading ?? v.cog; const headingText = heading !== undefined ? `${Math.round(heading)}°` : '-'; const receivedAt = v.lastUpdate ? formatDateTime(v.lastUpdate) : '-'; return (
{v.nationalCode ?? '🚢'}
{v.shipNm ?? '(이름 없음)'}
MMSI: {v.mmsi}
🚢
{getShipKindLabel(v.shipKindCode) ?? v.shipTy ?? '-'} {statusText}
출항지 -
입항지 {v.destination ?? '-'}
); } function PopupRow({ label, value, accent, muted, }: { label: string; value: string; accent?: boolean; muted?: boolean; }) { return (
{label} {value}
); } type DetTab = 'info' | 'nav' | 'spec' | 'ins' | 'dg'; const TAB_LABELS: { key: DetTab; label: string }[] = [ { key: 'info', label: '상세정보' }, { key: 'nav', label: '항해정보' }, { key: 'spec', label: '선박제원' }, { key: 'ins', label: '보험정보' }, { key: 'dg', label: '위험물정보' }, ]; export function VesselDetailModal({ vessel: v, onClose, }: { vessel: VesselPosition; onClose: () => void; }) { const [tab, setTab] = useState('info'); return (
{ if (e.target === e.currentTarget) onClose(); }} className="fixed inset-0 z-[10000] flex items-center justify-center" style={{ background: 'rgba(0,0,0,0.65)', backdropFilter: 'blur(6px)', }} >
{v.nationalCode ?? '🚢'}
{v.shipNm ?? '(이름 없음)'}
MMSI: {v.mmsi} · IMO: {displayVal(v.imo)}
{TAB_LABELS.map((t) => ( ))}
{tab === 'info' && } {tab === 'nav' && } {tab === 'spec' && } {tab === 'ins' && } {tab === 'dg' && }
); } function Sec({ title, borderColor, bgColor, badge, children, }: { title: string; borderColor?: string; bgColor?: string; badge?: React.ReactNode; children: React.ReactNode; }) { return (
{title} {badge}
{children}
); } function Grid({ children }: { children: React.ReactNode }) { return
{children}
; } function Cell({ label, value, span, color, }: { label: string; value: string; span?: boolean; color?: string; }) { return (
{label}
{value}
); } function StatusBadge({ label, color }: { label: string; color: string }) { return ( {label} ); } function TabInfo({ v }: { v: VesselPosition }) { const speed = v.sog !== undefined ? `${v.sog.toFixed(1)} kn` : '-'; const heading = v.heading ?? v.cog; const headingText = heading !== undefined ? `${Math.round(heading)}°` : '-'; return ( <>
🚢
); } function TabNav() { const hours = ['08', '09', '10', '11', '12', '13', '14']; const heights = [45, 60, 78, 82, 70, 85, 75]; const colors = [ 'color-mix(in srgb, var(--color-success) 30%, transparent)', 'color-mix(in srgb, var(--color-success) 40%, transparent)', 'color-mix(in srgb, var(--color-info) 40%, transparent)', 'color-mix(in srgb, var(--color-info) 50%, transparent)', 'color-mix(in srgb, var(--color-info) 50%, transparent)', 'color-mix(in srgb, var(--color-info) 60%, transparent)', 'color-mix(in srgb, var(--color-accent) 50%, transparent)', ]; return ( <>
{hours.map((h, i) => (
{h}
))}
평균: 8.4 kn · 최대:{' '} 11.2 kn
); } function TabSpec({ v }: { v: VesselPosition }) { const loa = v.length !== undefined ? `${v.length} m` : '-'; const beam = v.width !== undefined ? `${v.width} m` : '-'; return ( <> ); } function TabInsurance() { return ( <> } > ); } function TabDangerous() { return ( ); }