/** * 선박 우클릭 컨텍스트 메뉴 * - 단일 선박 우클릭: 해당 선박 메뉴 * - Ctrl+Drag 선택 후 우클릭: 선택된 선박 전체 메뉴 */ import { useState, useEffect, useRef, useCallback } from 'react'; import useShipStore from '../../stores/shipStore'; import { useTrackQueryStore } from '../../tracking/stores/trackQueryStore'; import useTrackingModeStore, { RADIUS_OPTIONS, isPatrolShip } from '../../stores/trackingModeStore'; import { fetchVesselTracksV2, convertToProcessedTracks, buildVesselListForQuery, deduplicateVessels, } from '../../tracking/services/trackQueryApi'; import './ShipContextMenu.scss'; /** KST 기준 로컬 ISO 문자열 생성 (toISOString()은 UTC이므로 사용하지 않음) */ function toKstISOString(date) { const pad = (n) => String(n).padStart(2, '0'); return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; } const MENU_ITEMS = [ { key: 'track', label: '항적조회' }, // TODO: 임시 배포용 - 미구현 기능 숨김 // { key: 'analysis', label: '항적분석' }, // { key: 'detail', label: '상세정보' }, { key: 'radius', label: '반경설정', hasSubmenu: true }, ]; export default function ShipContextMenu() { const contextMenu = useShipStore((s) => s.contextMenu); const closeContextMenu = useShipStore((s) => s.closeContextMenu); const setRadius = useTrackingModeStore((s) => s.setRadius); const selectTrackedShip = useTrackingModeStore((s) => s.selectTrackedShip); const currentRadius = useTrackingModeStore((s) => s.radiusNM); const menuRef = useRef(null); const [hoveredItem, setHoveredItem] = useState(null); // 외부 클릭 시 닫기 useEffect(() => { if (!contextMenu) return; const handleClick = (e) => { if (menuRef.current && !menuRef.current.contains(e.target)) { closeContextMenu(); } }; document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, [contextMenu, closeContextMenu]); // 반경 선택 핸들러 const handleRadiusSelect = useCallback((radius) => { if (!contextMenu) return; const { ships } = contextMenu; // 단일 경비함정인 경우 해당 함정을 추적 대상으로 설정 if (ships.length === 1) { const ship = ships[0]; setRadius(radius); selectTrackedShip(ship.featureId, ship); } closeContextMenu(); }, [contextMenu, setRadius, selectTrackedShip, closeContextMenu]); // 메뉴 항목 클릭 const handleAction = useCallback(async (key) => { if (!contextMenu) return; const { ships } = contextMenu; // 반경설정은 서브메뉴에서 처리 if (key === 'radius') return; closeContextMenu(); switch (key) { case 'track': { const store = useTrackQueryStore.getState(); store.reset(); const endTime = new Date(); const startTime = new Date(endTime.getTime() - 3 * 24 * 60 * 60 * 1000); // 3일 const { isIntegrate, features } = useShipStore.getState(); const allVessels = []; const errors = []; ships.forEach(ship => { const result = buildVesselListForQuery(ship, 'rightClick', isIntegrate, features); if (result.canQuery) allVessels.push(...result.vessels); else if (result.errorMessage) errors.push(result.errorMessage); }); // (sigSrcCd, targetId) 중복 제거 const uniqueVessels = deduplicateVessels(allVessels); if (uniqueVessels.length === 0) { store.setError(errors[0] || '조회 가능한 선박이 없습니다.'); return; } store.setModalMode(false, null); store.setLoading(true); try { const rawTracks = await fetchVesselTracksV2({ startTime: toKstISOString(startTime), endTime: toKstISOString(endTime), vessels: uniqueVessels, isIntegration: isIntegrate ? '1' : '0', }); const processed = convertToProcessedTracks(rawTracks); if (processed.length === 0) { store.setError('항적 데이터가 없습니다.'); } else { store.setTracks(processed, startTime.getTime()); } } catch (e) { console.error('[ShipContextMenu] 항적 조회 실패:', e); store.setError('항적 조회 실패'); } store.setLoading(false); break; } case 'analysis': { // 항적분석: 동일한 조회 후 showPlayback 활성화 const store = useTrackQueryStore.getState(); store.reset(); const endTime = new Date(); const startTime = new Date(endTime.getTime() - 3 * 24 * 60 * 60 * 1000); // 3일 const { isIntegrate, features } = useShipStore.getState(); const allVessels = []; ships.forEach(ship => { const result = buildVesselListForQuery(ship, 'rightClick', isIntegrate, features); if (result.canQuery) allVessels.push(...result.vessels); }); // (sigSrcCd, targetId) 중복 제거 const uniqueVessels = deduplicateVessels(allVessels); if (uniqueVessels.length === 0) return; store.setModalMode(false, null); store.setLoading(true); try { const rawTracks = await fetchVesselTracksV2({ startTime: toKstISOString(startTime), endTime: toKstISOString(endTime), vessels: uniqueVessels, isIntegration: isIntegrate ? '1' : '0', }); const processed = convertToProcessedTracks(rawTracks); if (processed.length > 0) { store.setTracks(processed, startTime.getTime()); // showPlayback 활성화 (재생 컨트롤 표시) useTrackQueryStore.setState({ showPlayback: true }); } } catch (e) { console.error('[ShipContextMenu] 항적분석 조회 실패:', e); } store.setLoading(false); break; } case 'detail': if (ships.length === 1) { useShipStore.getState().openDetailModal(ships[0]); } break; default: console.log(`[ContextMenu] action=${key}, ships=`, ships.map((s) => ({ featureId: s.featureId, shipName: s.shipName, targetId: s.targetId, }))); } }, [contextMenu, closeContextMenu]); if (!contextMenu) return null; const { x, y, ships } = contextMenu; // 단일 경비함정인지 확인 (반경설정 메뉴 표시 조건) const isSinglePatrolShip = ships.length === 1 && isPatrolShip(ships[0].originalTargetId); // 표시할 메뉴 항목 필터링 const visibleMenuItems = MENU_ITEMS.filter((item) => { if (item.key === 'radius') return isSinglePatrolShip; return true; }); // 화면 밖 넘침 방지 const menuWidth = 160; const menuHeight = visibleMenuItems.length * 36 + 40; // 항목 + 헤더 const adjustedX = x + menuWidth > window.innerWidth ? x - menuWidth : x; const adjustedY = y + menuHeight > window.innerHeight ? y - menuHeight : y; // 서브메뉴 위치 (오른쪽 또는 왼쪽) const submenuOnLeft = adjustedX + menuWidth + 120 > window.innerWidth; const title = ships.length === 1 ? (ships[0].shipName || ships[0].featureId) : `${ships.length}척 선택`; return (