import { ZONE_META } from "../../entities/zone/model/meta"; import { SPEED_MAX, VESSEL_TYPES } from "../../entities/vessel/model/meta"; import type { DerivedLegacyVessel } from "../../features/legacyDashboard/model/types"; import { haversineNm } from "../../shared/lib/geo/haversineNm"; import { OVERLAY_RGB, rgbToHex } from "../../shared/lib/map/palette"; type Props = { vessel: DerivedLegacyVessel; allVessels: DerivedLegacyVessel[]; onClose: () => void; onSelectMmsi?: (mmsi: number) => void; }; export function VesselInfoPanel({ vessel: v, allVessels, onClose, onSelectMmsi }: Props) { const t = VESSEL_TYPES[v.shipCode]; const ownerLabel = v.ownerCn || v.ownerRoman || v.ownerKey || "-"; const primary = t.speedProfile.filter((s) => s.primary); const inRange = v.sog !== null && primary.length ? primary.some((s) => v.sog! >= s.range[0] && v.sog! <= s.range[1]) : false; const pair = v.pairPermitNo ? allVessels.find((v2) => v2.permitNo === v.pairPermitNo) ?? null : null; const pairDist = pair ? haversineNm(v.lat, v.lon, pair.lat, pair.lon) : null; const month = new Date().getMonth(); const zone = v.zoneId ? ZONE_META[v.zoneId] : null; const allowed = v.zoneId ? t.allowedZones.includes(v.zoneId) : null; return (
{t.icon}
{v.permitNo}
{v.shipCode} · {t.name} · {v.name}
속도 {v.sog !== null ? v.sog.toFixed(1) : "?"}kt {inRange ? "(조업범위)" : "(범위외)"}
상태 {v.state.label}
수역 {zone ? `${zone.label} ${allowed === null ? "" : allowed ? "✓허가" : "⚠이탈"}` : "-"}
위치 {v.lat.toFixed(3)}°N {v.lon.toFixed(3)}°E
MMSI {v.mmsi}
CallSign {v.callsign || "-"}
Msg TS {v.messageTimestamp || "-"}
소유주 {ownerLabel}
{pair && pairDist !== null ? ( <>
쌍 선박 onSelectMmsi?.(pair.mmsi)}> {pair.permitNo} ({pair.shipCode})
쌍 이격 3 ? rgbToHex(OVERLAY_RGB.pairWarn) : "#22C55E" }}> {pairDist.toFixed(2)}NM {pairDist > 3 ? "⚠" : "✓"}
) : null}
속도 vs 조업범위
{t.speedProfile.map((s) => { const left = (s.range[0] / SPEED_MAX) * 100; const width = ((s.range[1] - s.range[0]) / SPEED_MAX) * 100; return (
); })}
{[0, 5, 10, 15].map((k) => ( {k} ))}
월별 강도
{t.monthlyIntensity.map((val, i) => { const cur = i === month; const bg = val === 0 ? "#EF444433" : `${t.color}${Math.round(val * 200).toString(16).padStart(2, "0")}`; const border = cur ? "1.5px solid #FFF" : "none"; const color = val === 0 ? "#EF4444" : cur ? "#FFF" : "transparent"; const text = val === 0 ? "✗" : cur ? "◉" : ""; return (
{text}
); })}
); }