import { useMemo, useState } from "react"; import { TextInput } from "@wing/ui"; import { VESSEL_TYPES } from "../../entities/vessel/model/meta"; import type { DerivedLegacyVessel } from "../../features/legacyDashboard/model/types"; import type { MouseEvent } from "react"; type Props = { vessels: DerivedLegacyVessel[]; selectedMmsi: number | null; highlightedMmsiSet?: number[]; onToggleHighlightMmsi: (mmsi: number) => void; onSelectMmsi: (mmsi: number) => void; onHoverMmsi?: (mmsi: number) => void; onClearHover?: () => void; }; function isFiniteNumber(x: unknown): x is number { return typeof x === "number" && Number.isFinite(x); } const STRIP_RE = /[\s\-.,_]/g; function normalize(s: string): string { return s.replace(STRIP_RE, "").toLowerCase(); } function matchesQuery(v: DerivedLegacyVessel, nq: string): boolean { if (normalize(v.permitNo).includes(nq)) return true; if (normalize(v.name).includes(nq)) return true; if (v.legacy.shipNameRoman && normalize(v.legacy.shipNameRoman).includes(nq)) return true; if (v.legacy.shipNameCn && normalize(v.legacy.shipNameCn).includes(nq)) return true; if (v.legacy.callSign && normalize(v.legacy.callSign).includes(nq)) return true; if (normalize(String(v.mmsi)).includes(nq)) return true; return false; } export function VesselList({ vessels, selectedMmsi, highlightedMmsiSet = [], onToggleHighlightMmsi, onSelectMmsi, onHoverMmsi, onClearHover, }: Props) { const [searchQuery, setSearchQuery] = useState(""); const handlePrimaryAction = (e: MouseEvent, mmsi: number) => { if (e.shiftKey || e.ctrlKey || e.metaKey) { onToggleHighlightMmsi(mmsi); return; } onSelectMmsi(mmsi); }; const sorted = useMemo(() => { const nq = searchQuery.length >= 2 ? normalize(searchQuery) : ""; const filtered = nq ? vessels.filter((v) => matchesQuery(v, nq)) : vessels; return filtered.slice().sort((a, b) => (isFiniteNumber(b.sog) ? b.sog : -1) - (isFiniteNumber(a.sog) ? a.sog : -1)); }, [vessels, searchQuery]); return (
setSearchQuery(e.target.value)} className="h-5 w-full py-0 text-[9px]" />
{sorted.map((v) => { const meta = VESSEL_TYPES[v.shipCode]; const primarySegs = meta.speedProfile.filter((s) => s.primary); const inRange = v.sog !== null && primarySegs.length ? primarySegs.some((s) => v.sog! >= s.range[0] && v.sog! <= s.range[1]) : false; const sc = v.state.isFishing ? "#22C55E" : (v.sog ?? 0) > 3 ? "#3B82F6" : "#64748B"; const speedColor = inRange ? "#22C55E" : (v.sog ?? 0) > 5 ? "#3B82F6" : "var(--muted)"; const hasPair = v.pairPermitNo ? "⛓" : ""; const sel = selectedMmsi === v.mmsi; const hl = highlightedMmsiSet.includes(v.mmsi); return (
handlePrimaryAction(e, v.mmsi)} onMouseEnter={() => onHoverMmsi?.(v.mmsi)} onMouseLeave={() => onClearHover?.()} title={v.name} >
{hasPair} {v.permitNo}
{v.sog !== null ? v.sog.toFixed(1) : "?"}kt
{v.state.label}
); })} {sorted.length === 0 ?
(표시할 대상 선박 없음)
: null}
); }