fix(map): 패널 선택 시 fly-to 복원, 지도 클릭은 제외

- mapInitiatedSelectRef 도입: 지도 클릭 선택과 패널 선택을 구분
- 좌측 패널(선박 목록, 알람 목록) 선택 시 해당 위치로 fly-to
- 지도에서 직접 클릭/우클릭 선택 시에는 fly-to 비활성화
- onMapSelectMmsi 래퍼로 지도 내 선택 경로 통합 (Globe+Mercator)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
htlee 2026-02-17 16:44:55 +09:00
부모 7bca216c53
커밋 f9da13b694
2개의 변경된 파일50개의 추가작업 그리고 7개의 파일을 삭제

파일 보기

@ -94,6 +94,7 @@ export function Map3D({
const projectionBusyRef = useRef(false);
const deckHoverRafRef = useRef<number | null>(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회 호출

파일 보기

@ -9,14 +9,49 @@ export function useFlyTo(
opts: {
selectedMmsi: number | null;
shipData: { mmsi: number; lon: number; lat: number }[];
/** true일 때 selectedMmsi fly-to 스킵 (지도 클릭 선택 시) */
mapInitiatedSelectRef: MutableRefObject<boolean>;
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))