fix: prevent hover update loop and map style ready guard
This commit is contained in:
부모
ea51aee6b4
커밋
ed5b0da5f9
@ -4,6 +4,7 @@ import { Map3DSettingsToggles } from "../../features/map3dSettings/Map3DSettings
|
|||||||
import type { MapToggleState } from "../../features/mapToggles/MapToggles";
|
import type { MapToggleState } from "../../features/mapToggles/MapToggles";
|
||||||
import { MapToggles } from "../../features/mapToggles/MapToggles";
|
import { MapToggles } from "../../features/mapToggles/MapToggles";
|
||||||
import { TypeFilterGrid } from "../../features/typeFilter/TypeFilterGrid";
|
import { TypeFilterGrid } from "../../features/typeFilter/TypeFilterGrid";
|
||||||
|
import type { DerivedLegacyVessel } from "../../features/legacyDashboard/model/types";
|
||||||
import { useLegacyVessels } from "../../entities/legacyVessel/api/useLegacyVessels";
|
import { useLegacyVessels } from "../../entities/legacyVessel/api/useLegacyVessels";
|
||||||
import { buildLegacyVesselIndex } from "../../entities/legacyVessel/lib";
|
import { buildLegacyVesselIndex } from "../../entities/legacyVessel/lib";
|
||||||
import type { LegacyVesselIndex } 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 [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>>({
|
const [typeEnabled, setTypeEnabled] = useState<Record<VesselTypeCode, boolean>>({
|
||||||
PT: true,
|
PT: true,
|
||||||
"PT-S": true,
|
"PT-S": true,
|
||||||
@ -109,6 +115,8 @@ export function DashboardPage() {
|
|||||||
fleetCircles: true,
|
fleetCircles: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [fleetFocus, setFleetFocus] = useState<{ id: string | number; center: [number, number]; zoom?: number } | undefined>(undefined);
|
||||||
|
|
||||||
const [settings, setSettings] = useState<Map3DSettings>({
|
const [settings, setSettings] = useState<Map3DSettings>({
|
||||||
showShips: true,
|
showShips: true,
|
||||||
showDensity: false,
|
showDensity: false,
|
||||||
@ -192,6 +200,52 @@ export function DashboardPage() {
|
|||||||
return legacyHits.get(selectedMmsi) ?? null;
|
return legacyHits.get(selectedMmsi) ?? null;
|
||||||
}, [selectedMmsi, legacyHits]);
|
}, [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 fishingCount = legacyVesselsAll.filter((v) => v.state.isFishing).length;
|
||||||
const transitCount = legacyVesselsAll.filter((v) => v.state.isTransit).length;
|
const transitCount = legacyVesselsAll.filter((v) => v.state.isTransit).length;
|
||||||
|
|
||||||
@ -299,11 +353,27 @@ export function DashboardPage() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ overflowY: "auto", minHeight: 0 }}>
|
<div style={{ overflowY: "auto", minHeight: 0 }}>
|
||||||
<RelationsPanel
|
<RelationsPanel
|
||||||
selectedVessel={selectedLegacyVessel}
|
selectedVessel={selectedLegacyVessel}
|
||||||
vessels={legacyVesselsAll}
|
vessels={legacyVesselsAll}
|
||||||
fleetVessels={legacyVesselsFiltered}
|
fleetVessels={legacyVesselsFiltered}
|
||||||
onSelectMmsi={setSelectedMmsi}
|
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>
|
||||||
</div>
|
</div>
|
||||||
@ -315,7 +385,15 @@ export function DashboardPage() {
|
|||||||
({legacyVesselsFiltered.length}척)
|
({legacyVesselsFiltered.length}척)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div className="sb" style={{ maxHeight: 130, overflowY: "auto" }}>
|
<div className="sb" style={{ maxHeight: 130, overflowY: "auto" }}>
|
||||||
@ -489,17 +567,32 @@ export function DashboardPage() {
|
|||||||
targets={targetsForMap}
|
targets={targetsForMap}
|
||||||
zones={zones}
|
zones={zones}
|
||||||
selectedMmsi={selectedMmsi}
|
selectedMmsi={selectedMmsi}
|
||||||
|
highlightedMmsiSet={activeHighlightedMmsiSet}
|
||||||
|
hoveredMmsiSet={hoveredMmsiSet}
|
||||||
|
hoveredFleetMmsiSet={hoveredFleetMmsiSet}
|
||||||
|
hoveredPairMmsiSet={hoveredPairMmsiSet}
|
||||||
|
hoveredFleetOwnerKey={hoveredFleetOwnerKey}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
baseMap={baseMap}
|
baseMap={baseMap}
|
||||||
projection={projection}
|
projection={projection}
|
||||||
overlays={overlays}
|
overlays={overlays}
|
||||||
onSelectMmsi={setSelectedMmsi}
|
onSelectMmsi={setSelectedMmsi}
|
||||||
|
onToggleHighlightMmsi={toggleHighlightedMmsi}
|
||||||
onViewBboxChange={setViewBbox}
|
onViewBboxChange={setViewBbox}
|
||||||
legacyHits={legacyHits}
|
legacyHits={legacyHits}
|
||||||
pairLinks={pairLinksForMap}
|
pairLinks={pairLinksForMap}
|
||||||
fcLinks={fcLinksForMap}
|
fcLinks={fcLinksForMap}
|
||||||
fleetCircles={fleetCirclesForMap}
|
fleetCircles={fleetCirclesForMap}
|
||||||
|
fleetFocus={fleetFocus}
|
||||||
onProjectionLoadingChange={setIsProjectionLoading}
|
onProjectionLoadingChange={setIsProjectionLoading}
|
||||||
|
onHoverFleet={(ownerKey, fleetMmsis) => {
|
||||||
|
setHoveredFleetOwnerKey(ownerKey);
|
||||||
|
setHoveredFleetMmsiSet(setSortedIfChanged(fleetMmsis));
|
||||||
|
}}
|
||||||
|
onClearFleetHover={() => {
|
||||||
|
setHoveredFleetOwnerKey(null);
|
||||||
|
setHoveredFleetMmsiSet((prev) => (prev.length === 0 ? prev : []));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<MapLegend />
|
<MapLegend />
|
||||||
{selectedLegacyVessel ? (
|
{selectedLegacyVessel ? (
|
||||||
|
|||||||
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
Load Diff
불러오는 중...
Reference in New Issue
Block a user