import { useEffect, useRef, useState, type CSSProperties } from 'react'; interface Props { x: number; y: number; mmsi: number; vesselName: string; isPermitted: boolean; onRequestTrack?: (mmsi: number, minutes: number) => void; onClose: () => void; } const TRACK_OPTIONS = [ { label: '6시간', minutes: 360 }, { label: '12시간', minutes: 720 }, { label: '1일', minutes: 1440 }, { label: '3일', minutes: 4320 }, { label: '5일', minutes: 7200 }, ] as const; const MENU_WIDTH = 180; const MENU_PAD = 8; const STYLE_ITEM: CSSProperties = { display: 'block', width: '100%', padding: '5px 12px 5px 24px', background: 'none', border: 'none', color: '#e2e2e2', fontSize: 12, textAlign: 'left', cursor: 'pointer', lineHeight: 1.4, }; const STYLE_SEPARATOR: CSSProperties = { height: 1, background: 'rgba(255,255,255,0.08)', margin: '3px 0', }; function handleHover(e: React.MouseEvent) { (e.target as HTMLElement).style.background = 'rgba(59,130,246,0.18)'; } function handleLeave(e: React.MouseEvent) { (e.target as HTMLElement).style.background = 'none'; } export function VesselContextMenu({ x, y, mmsi, vesselName, isPermitted, onRequestTrack, onClose }: Props) { const ref = useRef(null); const [copiedField, setCopiedField] = useState<'name' | 'mmsi' | null>(null); const estimatedHeight = (isPermitted && onRequestTrack ? TRACK_OPTIONS.length * 30 + 56 : 0) + 90; const left = Math.min(x, window.innerWidth - MENU_WIDTH - MENU_PAD); const maxTop = window.innerHeight - estimatedHeight - MENU_PAD; const top = Math.min(y, maxTop); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; const onClick = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) onClose(); }; const onScroll = () => onClose(); window.addEventListener('keydown', onKey); window.addEventListener('mousedown', onClick, true); window.addEventListener('scroll', onScroll, true); return () => { window.removeEventListener('keydown', onKey); window.removeEventListener('mousedown', onClick, true); window.removeEventListener('scroll', onScroll, true); }; }, [onClose]); const handleCopy = async (text: string, field: 'name' | 'mmsi') => { try { await navigator.clipboard.writeText(text); setCopiedField(field); setTimeout(() => setCopiedField(null), 1200); } catch { // clipboard API 불가 시 무시 } }; const handleSelectTrack = (minutes: number) => { onRequestTrack?.(mmsi, minutes); onClose(); }; return (
{/* Header */}
{vesselName}
{/* 선명 복사 */} {/* MMSI 복사 */} {/* 항적조회 (대상선박만) */} {isPermitted && onRequestTrack && ( <>
항적조회
{TRACK_OPTIONS.map((opt) => ( ))} )}
); }