83 lines
2.3 KiB
React
83 lines
2.3 KiB
React
|
|
/**
|
||
|
|
* 선박 우클릭 컨텍스트 메뉴
|
||
|
|
* - 단일 선박 우클릭: 해당 선박 메뉴
|
||
|
|
* - Ctrl+Drag 선택 후 우클릭: 선택된 선박 전체 메뉴
|
||
|
|
*/
|
||
|
|
import { useEffect, useRef, useCallback } from 'react';
|
||
|
|
import useShipStore from '../../stores/shipStore';
|
||
|
|
import './ShipContextMenu.scss';
|
||
|
|
|
||
|
|
const MENU_ITEMS = [
|
||
|
|
{ key: 'track', label: '항적조회' },
|
||
|
|
{ key: 'analysis', label: '항적분석' },
|
||
|
|
{ key: 'detail', label: '상세정보' },
|
||
|
|
];
|
||
|
|
|
||
|
|
export default function ShipContextMenu() {
|
||
|
|
const contextMenu = useShipStore((s) => s.contextMenu);
|
||
|
|
const closeContextMenu = useShipStore((s) => s.closeContextMenu);
|
||
|
|
const menuRef = useRef(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 handleAction = useCallback((key) => {
|
||
|
|
if (!contextMenu) return;
|
||
|
|
const { ships } = contextMenu;
|
||
|
|
|
||
|
|
// TODO: 향후 API 연결
|
||
|
|
console.log(`[ContextMenu] action=${key}, ships=`, ships.map((s) => ({
|
||
|
|
featureId: s.featureId,
|
||
|
|
shipName: s.shipName,
|
||
|
|
targetId: s.targetId,
|
||
|
|
})));
|
||
|
|
|
||
|
|
closeContextMenu();
|
||
|
|
}, [contextMenu, closeContextMenu]);
|
||
|
|
|
||
|
|
if (!contextMenu) return null;
|
||
|
|
|
||
|
|
const { x, y, ships } = contextMenu;
|
||
|
|
|
||
|
|
// 화면 밖 넘침 방지
|
||
|
|
const menuWidth = 160;
|
||
|
|
const menuHeight = MENU_ITEMS.length * 36 + 40; // 항목 + 헤더
|
||
|
|
const adjustedX = x + menuWidth > window.innerWidth ? x - menuWidth : x;
|
||
|
|
const adjustedY = y + menuHeight > window.innerHeight ? y - menuHeight : y;
|
||
|
|
|
||
|
|
const title = ships.length === 1
|
||
|
|
? (ships[0].shipName || ships[0].featureId)
|
||
|
|
: `${ships.length}척 선택`;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
ref={menuRef}
|
||
|
|
className="ship-context-menu"
|
||
|
|
style={{ left: adjustedX, top: adjustedY }}
|
||
|
|
>
|
||
|
|
<div className="ship-context-menu__header">{title}</div>
|
||
|
|
{MENU_ITEMS.map((item) => (
|
||
|
|
<div
|
||
|
|
key={item.key}
|
||
|
|
className="ship-context-menu__item"
|
||
|
|
onClick={() => handleAction(item.key)}
|
||
|
|
>
|
||
|
|
{item.label}
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|