fix: prevent hover update loop and map style ready guard

This commit is contained in:
htlee 2026-02-15 15:17:48 +09:00
부모 ea51aee6b4
커밋 ed5b0da5f9
2개의 변경된 파일1265개의 추가작업 그리고 156개의 파일을 삭제

파일 보기

@ -4,6 +4,7 @@ import { Map3DSettingsToggles } from "../../features/map3dSettings/Map3DSettings
import type { MapToggleState } from "../../features/mapToggles/MapToggles";
import { MapToggles } from "../../features/mapToggles/MapToggles";
import { TypeFilterGrid } from "../../features/typeFilter/TypeFilterGrid";
import type { DerivedLegacyVessel } from "../../features/legacyDashboard/model/types";
import { useLegacyVessels } from "../../entities/legacyVessel/api/useLegacyVessels";
import { buildLegacyVesselIndex } from "../../entities/legacyVessel/lib";
import type { LegacyVesselIndex } from "../../entities/legacyVessel/lib";
@ -87,6 +88,11 @@ export function DashboardPage() {
});
const [selectedMmsi, setSelectedMmsi] = useState<number | null>(null);
const [highlightedMmsiSet, setHighlightedMmsiSet] = useState<number[]>([]);
const [hoveredMmsiSet, setHoveredMmsiSet] = useState<number[]>([]);
const [hoveredFleetMmsiSet, setHoveredFleetMmsiSet] = useState<number[]>([]);
const [hoveredPairMmsiSet, setHoveredPairMmsiSet] = useState<number[]>([]);
const [hoveredFleetOwnerKey, setHoveredFleetOwnerKey] = useState<string | null>(null);
const [typeEnabled, setTypeEnabled] = useState<Record<VesselTypeCode, boolean>>({
PT: true,
"PT-S": true,
@ -109,6 +115,8 @@ export function DashboardPage() {
fleetCircles: true,
});
const [fleetFocus, setFleetFocus] = useState<{ id: string | number; center: [number, number]; zoom?: number } | undefined>(undefined);
const [settings, setSettings] = useState<Map3DSettings>({
showShips: true,
showDensity: false,
@ -192,6 +200,52 @@ export function DashboardPage() {
return legacyHits.get(selectedMmsi) ?? null;
}, [selectedMmsi, legacyHits]);
const availableTargetMmsiSet = useMemo(
() => new Set(targetsInScope.map((t) => t.mmsi).filter((mmsi) => Number.isFinite(mmsi))),
[targetsInScope],
);
const activeHighlightedMmsiSet = useMemo(
() => highlightedMmsiSet.filter((mmsi) => availableTargetMmsiSet.has(mmsi)),
[highlightedMmsiSet, availableTargetMmsiSet],
);
const setUniqueSorted = (items: number[]) =>
Array.from(new Set(items.filter((item) => Number.isFinite(item)))).sort((a, b) => a - b);
const setSortedIfChanged = (next: number[]) => {
const sorted = setUniqueSorted(next);
return (prev: number[]) => (prev.length === sorted.length && prev.every((v, i) => v === sorted[i]) ? prev : sorted);
};
const handleFleetContextMenu = (ownerKey: string, mmsis: number[]) => {
if (!mmsis.length) return;
const members = mmsis
.map((mmsi) => legacyVesselsFiltered.find((v): v is DerivedLegacyVessel => v.mmsi === mmsi))
.filter(
(v): v is DerivedLegacyVessel & { lat: number; lon: number } =>
v != null && typeof v.lat === "number" && typeof v.lon === "number" && Number.isFinite(v.lat) && Number.isFinite(v.lon),
);
if (members.length === 0) return;
const sumLon = members.reduce((acc, v) => acc + v.lon, 0);
const sumLat = members.reduce((acc, v) => acc + v.lat, 0);
const center: [number, number] = [sumLon / members.length, sumLat / members.length];
setFleetFocus({
id: `${ownerKey}-${Date.now()}`,
center,
zoom: 9,
});
};
const toggleHighlightedMmsi = (mmsi: number) => {
setHighlightedMmsiSet((prev) => {
const next = new Set(prev);
if (next.has(mmsi)) next.delete(mmsi);
else next.add(mmsi);
return Array.from(next).sort((a, b) => a - b);
});
};
const fishingCount = legacyVesselsAll.filter((v) => v.state.isFishing).length;
const transitCount = legacyVesselsAll.filter((v) => v.state.isTransit).length;
@ -299,11 +353,27 @@ export function DashboardPage() {
</span>
</div>
<div style={{ overflowY: "auto", minHeight: 0 }}>
<RelationsPanel
selectedVessel={selectedLegacyVessel}
vessels={legacyVesselsAll}
fleetVessels={legacyVesselsFiltered}
<RelationsPanel
selectedVessel={selectedLegacyVessel}
vessels={legacyVesselsAll}
fleetVessels={legacyVesselsFiltered}
onSelectMmsi={setSelectedMmsi}
onToggleHighlightMmsi={toggleHighlightedMmsi}
onHoverMmsi={(mmsis) => setHoveredMmsiSet(setUniqueSorted(mmsis))}
onClearHover={() => setHoveredMmsiSet([])}
onHoverPair={(pairMmsis) => setHoveredPairMmsiSet(setUniqueSorted(pairMmsis))}
onClearPairHover={() => setHoveredPairMmsiSet([])}
onHoverFleet={(ownerKey, fleetMmsis) => {
setHoveredFleetOwnerKey(ownerKey);
setHoveredFleetMmsiSet(setSortedIfChanged(fleetMmsis));
}}
onClearFleetHover={() => {
setHoveredFleetOwnerKey(null);
setHoveredFleetMmsiSet((prev) => (prev.length === 0 ? prev : []));
}}
hoveredFleetOwnerKey={hoveredFleetOwnerKey}
hoveredFleetMmsiSet={hoveredFleetMmsiSet}
onContextMenuFleet={handleFleetContextMenu}
/>
</div>
</div>
@ -315,7 +385,15 @@ export function DashboardPage() {
({legacyVesselsFiltered.length})
</span>
</div>
<VesselList vessels={legacyVesselsFiltered} selectedMmsi={selectedMmsi} onSelectMmsi={setSelectedMmsi} />
<VesselList
vessels={legacyVesselsFiltered}
selectedMmsi={selectedMmsi}
highlightedMmsiSet={activeHighlightedMmsiSet}
onSelectMmsi={setSelectedMmsi}
onToggleHighlightMmsi={toggleHighlightedMmsi}
onHoverMmsi={(mmsi) => setHoveredMmsiSet([mmsi])}
onClearHover={() => setHoveredMmsiSet([])}
/>
</div>
<div className="sb" style={{ maxHeight: 130, overflowY: "auto" }}>
@ -489,17 +567,32 @@ export function DashboardPage() {
targets={targetsForMap}
zones={zones}
selectedMmsi={selectedMmsi}
highlightedMmsiSet={activeHighlightedMmsiSet}
hoveredMmsiSet={hoveredMmsiSet}
hoveredFleetMmsiSet={hoveredFleetMmsiSet}
hoveredPairMmsiSet={hoveredPairMmsiSet}
hoveredFleetOwnerKey={hoveredFleetOwnerKey}
settings={settings}
baseMap={baseMap}
projection={projection}
overlays={overlays}
onSelectMmsi={setSelectedMmsi}
onToggleHighlightMmsi={toggleHighlightedMmsi}
onViewBboxChange={setViewBbox}
legacyHits={legacyHits}
pairLinks={pairLinksForMap}
fcLinks={fcLinksForMap}
fleetCircles={fleetCirclesForMap}
fleetFocus={fleetFocus}
onProjectionLoadingChange={setIsProjectionLoading}
onHoverFleet={(ownerKey, fleetMmsis) => {
setHoveredFleetOwnerKey(ownerKey);
setHoveredFleetMmsiSet(setSortedIfChanged(fleetMmsis));
}}
onClearFleetHover={() => {
setHoveredFleetOwnerKey(null);
setHoveredFleetMmsiSet((prev) => (prev.length === 0 ? prev : []));
}}
/>
<MapLegend />
{selectedLegacyVessel ? (

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff