From f9da13b69409c3b1f984501e43eba13a9ac073c4 Mon Sep 17 00:00:00 2001 From: htlee Date: Tue, 17 Feb 2026 16:44:55 +0900 Subject: [PATCH] =?UTF-8?q?fix(map):=20=ED=8C=A8=EB=84=90=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EC=8B=9C=20fly-to=20=EB=B3=B5=EC=9B=90,=20?= =?UTF-8?q?=EC=A7=80=EB=8F=84=20=ED=81=B4=EB=A6=AD=EC=9D=80=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mapInitiatedSelectRef 도입: 지도 클릭 선택과 패널 선택을 구분 - 좌측 패널(선박 목록, 알람 목록) 선택 시 해당 위치로 fly-to - 지도에서 직접 클릭/우클릭 선택 시에는 fly-to 비활성화 - onMapSelectMmsi 래퍼로 지도 내 선택 경로 통합 (Globe+Mercator) Co-Authored-By: Claude Opus 4.6 --- apps/web/src/widgets/map3d/Map3D.tsx | 20 +++++++---- apps/web/src/widgets/map3d/hooks/useFlyTo.ts | 37 +++++++++++++++++++- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/apps/web/src/widgets/map3d/Map3D.tsx b/apps/web/src/widgets/map3d/Map3D.tsx index 4a1bf22..8cf47f5 100644 --- a/apps/web/src/widgets/map3d/Map3D.tsx +++ b/apps/web/src/widgets/map3d/Map3D.tsx @@ -94,6 +94,7 @@ export function Map3D({ const projectionBusyRef = useRef(false); const deckHoverRafRef = useRef(null); const deckHoverHasHitRef = useRef(false); + const mapInitiatedSelectRef = useRef(false); useEffect(() => { baseMapRef.current = baseMap; }, [baseMap]); useEffect(() => { projectionRef.current = projection; }, [projection]); @@ -276,6 +277,13 @@ export function Map3D({ return out; }, []); + // 지도 내부 클릭에서의 선택 — fly-to 비활성화 플래그 설정 + // eslint-disable-next-line react-hooks/preserve-manual-memoization + const onMapSelectMmsi = useCallback((mmsi: number | null) => { + mapInitiatedSelectRef.current = true; + onSelectMmsi(mmsi); + }, [onSelectMmsi]); + const onDeckSelectOrHighlight = useCallback( (info: unknown, allowMultiSelect = false) => { const obj = info as { @@ -291,12 +299,12 @@ export function Map3D({ return; } if (!allowMultiSelect && selectedMmsi === mmsi) { - onSelectMmsi(null); + onMapSelectMmsi(null); return; } - onSelectMmsi(mmsi); + onMapSelectMmsi(mmsi); }, - [hasAuxiliarySelectModifier, onSelectMmsi, onToggleHighlightMmsi, selectedMmsi], + [hasAuxiliarySelectModifier, onMapSelectMmsi, onToggleHighlightMmsi, selectedMmsi], ); // eslint-disable-next-line react-hooks/preserve-manual-memoization @@ -565,7 +573,7 @@ export function Map3D({ { projection, settings, shipData: shipLayerData, shipHighlightSet, shipHoverOverlaySet, shipOverlayLayerData, shipLayerData, shipByMmsi, mapSyncEpoch, - onSelectMmsi, onToggleHighlightMmsi, targets: shipLayerData, overlays, + onSelectMmsi: onMapSelectMmsi, onToggleHighlightMmsi, targets: shipLayerData, overlays, legacyHits, selectedMmsi, isBaseHighlightedMmsi, hasAuxiliarySelectModifier, onGlobeShipsReady, alarmMmsiMap, }, @@ -600,7 +608,7 @@ export function Map3D({ clearDeckHoverPairs, clearDeckHoverMmsi, clearMapFleetHoverState, setDeckHoverPairs, setDeckHoverMmsi, setMapFleetHoverState, toFleetMmsiList, touchDeckHoverState, hasAuxiliarySelectModifier, - onDeckSelectOrHighlight, onSelectMmsi, onToggleHighlightMmsi, + onDeckSelectOrHighlight, onSelectMmsi: onMapSelectMmsi, onToggleHighlightMmsi, ensureMercatorOverlay, alarmMmsiMap, }, ); @@ -687,7 +695,7 @@ export function Map3D({ useFlyTo( mapRef, projectionRef, - { selectedMmsi, shipData, fleetFocusId, fleetFocusLon, fleetFocusLat, fleetFocusZoom }, + { selectedMmsi, shipData, mapInitiatedSelectRef, fleetFocusId, fleetFocusLon, fleetFocusLat, fleetFocusZoom }, ); // Map ready 콜백 — mapSyncEpoch 초기 증가 시 1회 호출 diff --git a/apps/web/src/widgets/map3d/hooks/useFlyTo.ts b/apps/web/src/widgets/map3d/hooks/useFlyTo.ts index f143ef6..18bf356 100644 --- a/apps/web/src/widgets/map3d/hooks/useFlyTo.ts +++ b/apps/web/src/widgets/map3d/hooks/useFlyTo.ts @@ -9,14 +9,49 @@ export function useFlyTo( opts: { selectedMmsi: number | null; shipData: { mmsi: number; lon: number; lat: number }[]; + /** true일 때 selectedMmsi fly-to 스킵 (지도 클릭 선택 시) */ + mapInitiatedSelectRef: MutableRefObject; fleetFocusId: string | number | undefined; fleetFocusLon: number | undefined; fleetFocusLat: number | undefined; fleetFocusZoom: number | undefined; }, ) { - const { fleetFocusId, fleetFocusLon, fleetFocusLat, fleetFocusZoom } = opts; + const { selectedMmsi, shipData, mapInitiatedSelectRef, fleetFocusId, fleetFocusLon, fleetFocusLat, fleetFocusZoom } = opts; + // 패널(좌측 목록)에서 선택 시 해당 선박 위치로 이동 + useEffect(() => { + // 지도 내부 클릭에서 발생한 선택이면 스킵 + if (mapInitiatedSelectRef.current) { + mapInitiatedSelectRef.current = false; + return; + } + + const map = mapRef.current; + if (!map || selectedMmsi == null) return; + + const target = shipData.find((t) => t.mmsi === selectedMmsi); + if (!target || !Number.isFinite(target.lon) || !Number.isFinite(target.lat)) return; + + const apply = () => { + const flyOpts = { center: [target.lon, target.lat] as [number, number], duration: 700 }; + if (projectionRef.current === 'globe') { + map.flyTo(flyOpts); + } else { + map.easeTo(flyOpts); + } + }; + + if (map.isStyleLoaded()) { + apply(); + return; + } + + const stop = onMapStyleReady(map, apply); + return () => { stop(); }; + }, [selectedMmsi, shipData]); + + // 선단 포커스 이동 useEffect(() => { const map = mapRef.current; if (!map || fleetFocusLon == null || fleetFocusLat == null || !Number.isFinite(fleetFocusLon) || !Number.isFinite(fleetFocusLat))