Compare commits
1 커밋
develop
...
ci/actions
| 작성자 | SHA1 | 날짜 | |
|---|---|---|---|
| 9c211f4ab6 |
@ -223,6 +223,26 @@ body {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vessel-search-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 20px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--card);
|
||||||
|
color: var(--fg);
|
||||||
|
font-size: 9px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.vessel-search-input::placeholder {
|
||||||
|
color: var(--muted);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.vessel-search-input:focus {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
.vi {
|
.vi {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -118,6 +118,7 @@ export function DashboardPage() {
|
|||||||
const [baseMap, _setBaseMap] = useState<BaseMapId>("enhanced");
|
const [baseMap, _setBaseMap] = useState<BaseMapId>("enhanced");
|
||||||
const [projection, setProjection] = useState<MapProjectionId>("mercator");
|
const [projection, setProjection] = useState<MapProjectionId>("mercator");
|
||||||
const [mapStyleSettings, setMapStyleSettings] = useState<MapStyleSettings>(DEFAULT_MAP_STYLE_SETTINGS);
|
const [mapStyleSettings, setMapStyleSettings] = useState<MapStyleSettings>(DEFAULT_MAP_STYLE_SETTINGS);
|
||||||
|
const [vesselSearchQuery, setVesselSearchQuery] = useState("");
|
||||||
|
|
||||||
const [overlays, setOverlays] = useState<MapToggleState>({
|
const [overlays, setOverlays] = useState<MapToggleState>({
|
||||||
pairLines: true,
|
pairLines: true,
|
||||||
@ -445,14 +446,24 @@ export function DashboardPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sb" style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }}>
|
<div className="sb" style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }}>
|
||||||
<div className="sb-t">
|
<div className="sb-t" style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
||||||
선박 목록{" "}
|
<span style={{ whiteSpace: "nowrap" }}>
|
||||||
<span style={{ color: "var(--accent)", fontSize: 8 }}>
|
선박 목록{" "}
|
||||||
({legacyVesselsFiltered.length}척)
|
<span style={{ color: "var(--accent)", fontSize: 8 }}>
|
||||||
|
({legacyVesselsFiltered.length}척)
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="vessel-search-input"
|
||||||
|
placeholder="선박 검색..."
|
||||||
|
value={vesselSearchQuery}
|
||||||
|
onChange={(e) => setVesselSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<VesselList
|
<VesselList
|
||||||
vessels={legacyVesselsFiltered}
|
vessels={legacyVesselsFiltered}
|
||||||
|
searchQuery={vesselSearchQuery}
|
||||||
selectedMmsi={selectedMmsi}
|
selectedMmsi={selectedMmsi}
|
||||||
highlightedMmsiSet={activeHighlightedMmsiSet}
|
highlightedMmsiSet={activeHighlightedMmsiSet}
|
||||||
onSelectMmsi={setSelectedMmsi}
|
onSelectMmsi={setSelectedMmsi}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
import { VESSEL_TYPES } from "../../entities/vessel/model/meta";
|
import { VESSEL_TYPES } from "../../entities/vessel/model/meta";
|
||||||
import type { DerivedLegacyVessel } from "../../features/legacyDashboard/model/types";
|
import type { DerivedLegacyVessel } from "../../features/legacyDashboard/model/types";
|
||||||
import type { MouseEvent } from "react";
|
import type { MouseEvent } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
vessels: DerivedLegacyVessel[];
|
vessels: DerivedLegacyVessel[];
|
||||||
|
searchQuery: string;
|
||||||
selectedMmsi: number | null;
|
selectedMmsi: number | null;
|
||||||
highlightedMmsiSet?: number[];
|
highlightedMmsiSet?: number[];
|
||||||
onToggleHighlightMmsi: (mmsi: number) => void;
|
onToggleHighlightMmsi: (mmsi: number) => void;
|
||||||
@ -12,8 +14,29 @@ type Props = {
|
|||||||
onClearHover?: () => 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, normalizedQuery: string): boolean {
|
||||||
|
if (normalize(v.permitNo).includes(normalizedQuery)) return true;
|
||||||
|
if (normalize(v.name).includes(normalizedQuery)) return true;
|
||||||
|
if (v.legacy.shipNameRoman && normalize(v.legacy.shipNameRoman).includes(normalizedQuery)) return true;
|
||||||
|
if (v.legacy.shipNameCn && normalize(v.legacy.shipNameCn).includes(normalizedQuery)) return true;
|
||||||
|
if (v.legacy.callSign && normalize(v.legacy.callSign).includes(normalizedQuery)) return true;
|
||||||
|
if (v.mmsi && normalize(String(v.mmsi)).includes(normalizedQuery)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function VesselList({
|
export function VesselList({
|
||||||
vessels,
|
vessels,
|
||||||
|
searchQuery,
|
||||||
selectedMmsi,
|
selectedMmsi,
|
||||||
highlightedMmsiSet = [],
|
highlightedMmsiSet = [],
|
||||||
onToggleHighlightMmsi,
|
onToggleHighlightMmsi,
|
||||||
@ -29,14 +52,16 @@ export function VesselList({
|
|||||||
onSelectMmsi(mmsi);
|
onSelectMmsi(mmsi);
|
||||||
};
|
};
|
||||||
|
|
||||||
function isFiniteNumber(x: unknown): x is number {
|
const sorted = useMemo(() => {
|
||||||
return typeof x === "number" && Number.isFinite(x);
|
const normalizedQuery = searchQuery.length >= 2 ? normalize(searchQuery) : "";
|
||||||
}
|
const filtered = normalizedQuery
|
||||||
|
? vessels.filter((v) => matchesQuery(v, normalizedQuery))
|
||||||
|
: vessels;
|
||||||
|
|
||||||
const sorted = vessels
|
return filtered
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => (isFiniteNumber(b.sog) ? b.sog : -1) - (isFiniteNumber(a.sog) ? a.sog : -1))
|
.sort((a, b) => (isFiniteNumber(b.sog) ? b.sog : -1) - (isFiniteNumber(a.sog) ? a.sog : -1));
|
||||||
.slice(0, 80);
|
}, [vessels, searchQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vlist">
|
<div className="vlist">
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user