import { useMemo, useState } from "react"; import type { AisTarget } from "../../entities/aisTarget/model/types"; import type { LegacyVesselIndex } from "../../entities/legacyVessel/lib"; import { matchLegacyVessel } from "../../entities/legacyVessel/lib"; import { fmtIsoTime } from "../../shared/lib/datetime"; type SortMode = "recent" | "speed"; type Props = { targets: AisTarget[]; selectedMmsi: number | null; onSelectMmsi: (mmsi: number) => void; legacyIndex?: LegacyVesselIndex | null; }; function isFiniteNumber(x: unknown): x is number { return typeof x === "number" && Number.isFinite(x); } function getSpeedColor(sog: unknown) { if (!isFiniteNumber(sog)) return "#64748B"; if (sog >= 10) return "#3B82F6"; if (sog >= 1) return "#22C55E"; return "#64748B"; } export function AisTargetList({ targets, selectedMmsi, onSelectMmsi, legacyIndex }: Props) { const [q, setQ] = useState(""); const [mode, setMode] = useState("recent"); const rows = useMemo(() => { const query = q.trim(); let out = targets; if (query) { const qLower = query.toLowerCase(); const isDigits = /^[0-9]+$/.test(query); out = targets.filter((t) => { const name = (t.name || "").trim(); const callsign = (t.callsign || "").trim(); const mmsi = t.mmsi; if (isDigits) return String(mmsi).includes(query); return name.toLowerCase().includes(qLower) || callsign.toLowerCase().includes(qLower) || String(mmsi).includes(query); }); } const sorted = out.slice().sort((a, b) => { if (mode === "speed") { const as = isFiniteNumber(a.sog) ? a.sog : -1; const bs = isFiniteNumber(b.sog) ? b.sog : -1; if (bs !== as) return bs - as; } const at = Date.parse(a.messageTimestamp || ""); const bt = Date.parse(b.messageTimestamp || ""); const ats = Number.isFinite(at) ? at : 0; const bts = Number.isFinite(bt) ? bt : 0; if (bts !== ats) return bts - ats; return (a.mmsi ?? 0) - (b.mmsi ?? 0); }); return sorted.slice(0, 200); }, [targets, q, mode]); return (
setQ(e.target.value)} placeholder="검색: MMSI / 선박명 / CallSign" className="ais-q" />
{rows.map((t) => { const name = (t.name || "").trim() || "(no name)"; const sel = selectedMmsi && t.mmsi === selectedMmsi; const sp = isFiniteNumber(t.sog) ? t.sog.toFixed(1) : "?"; const sc = getSpeedColor(t.sog); const ts = fmtIsoTime(t.messageTimestamp); const legacy = legacyIndex ? matchLegacyVessel(t, legacyIndex) : null; const legacyCode = legacy?.shipCode || ""; return (
onSelectMmsi(t.mmsi)} title={t.vesselType || ""}>
{name}
MMSI {t.mmsi} {t.callsign ? {t.callsign} : null}
{legacy ? (
CN {legacyCode} {legacy.permitNo}
) : null}
{sp}kt
{ts}
); })} {rows.length === 0 ?
(검색 결과 없음)
: null}
); }