diff --git a/public/src/components/map/DrawingHandler.tsx b/public/src/components/map/DrawingHandler.tsx index 2de1511..5df0255 100644 --- a/public/src/components/map/DrawingHandler.tsx +++ b/public/src/components/map/DrawingHandler.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { useMapEvents, Polyline, Marker } from 'react-leaflet'; +import { useMapEvents, Polyline, Marker, Circle } from 'react-leaflet'; import { useDrawingStore } from '../../stores/drawingStore'; import { useUIStore } from '../../stores/uiStore'; import { mapItemService } from '../../services/mapItemService'; @@ -18,6 +18,8 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) { const [allItems, setAllItems] = useState([]); const [startDeviceId, setStartDeviceId] = useState(null); const [endDeviceId, setEndDeviceId] = useState(null); + const [nearbyDevice, setNearbyDevice] = useState(null); + const [nearbyFullDevice, setNearbyFullDevice] = useState(null); const isCableTool = ['fiber', 'cat6', 'cat6_poe'].includes(activeTool); const isDeviceTool = ['switch', 'indoor_ap', 'outdoor_ap', 'other_device', 'info'].includes(activeTool); @@ -37,7 +39,7 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) { }, [mapId]); // Find nearby device for snapping (exclude info markers) - const findNearbyDevice = (lat: number, lng: number, radiusMeters = 1): any | null => { + const findNearbyDevice = (lat: number, lng: number, radiusMeters = 2.5): any | null => { const devices = allItems.filter(item => ['switch', 'indoor_ap', 'outdoor_ap', 'other_device'].includes(item.type) && item.geometry.type === 'Point' @@ -78,6 +80,10 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) { let { lat, lng } = e.latlng; let clickedDevice = null; + // Clear snap indicator on click + setNearbyDevice(null); + setNearbyFullDevice(null); + // Check for nearby device when drawing cables if (isCableTool && map) { clickedDevice = findNearbyDevice(lat, lng); @@ -151,7 +157,7 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) { // Wireless mesh - connect AP to AP only if (isWirelessTool) { // Must click on an AP - const ap = findNearbyDevice(lat, lng, 1); + const ap = findNearbyDevice(lat, lng, 2.5); if (!ap || !['indoor_ap', 'outdoor_ap'].includes(ap.type)) { showToast('Wireless mesh can only connect between Access Points. Please click on an AP.', 'error'); return; @@ -191,6 +197,40 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) { if (isDrawing && (isCableTool || isWirelessTool)) { setCursorPosition([e.latlng.lat, e.latlng.lng]); } + + // Check for nearby device to show snap indicator + if ((isCableTool || isWirelessTool) && map) { + const nearby = findNearbyDevice(e.latlng.lat, e.latlng.lng); + + // For wireless mesh, only show indicator for APs + if (isWirelessTool) { + if (nearby && ['indoor_ap', 'outdoor_ap'].includes(nearby.type)) { + setNearbyDevice(nearby); + setNearbyFullDevice(null); + } else { + setNearbyDevice(null); + setNearbyFullDevice(null); + } + } else if (isCableTool) { + // For cables, check port availability + if (nearby) { + if (hasAvailablePorts(nearby)) { + setNearbyDevice(nearby); + setNearbyFullDevice(null); + } else { + // Device has full ports - show red indicator + setNearbyDevice(null); + setNearbyFullDevice(nearby); + } + } else { + setNearbyDevice(null); + setNearbyFullDevice(null); + } + } + } else { + setNearbyDevice(null); + setNearbyFullDevice(null); + } }, contextmenu: async (e) => { @@ -214,6 +254,8 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) { if (e.key === 'Escape' && isDrawing) { resetDrawing(); setCursorPosition(null); + setNearbyDevice(null); + setNearbyFullDevice(null); } }; @@ -221,6 +263,12 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) { return () => document.removeEventListener('keydown', handleKeyDown); }, [isDrawing, resetDrawing]); + // Clear nearby device indicator when tool changes + useEffect(() => { + setNearbyDevice(null); + setNearbyFullDevice(null); + }, [activeTool]); + const finishCable = async () => { if (drawingPoints.length < 2) return; @@ -244,6 +292,8 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) { setCursorPosition(null); setStartDeviceId(null); setEndDeviceId(null); + setNearbyDevice(null); + setNearbyFullDevice(null); // Reload items const items = await mapItemService.getMapItems(mapId); setAllItems(items); @@ -283,6 +333,8 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) { setCursorPosition(null); setStartDeviceId(null); setEndDeviceId(null); + setNearbyDevice(null); + setNearbyFullDevice(null); // Reload items const items = await mapItemService.getMapItems(mapId); setAllItems(items); @@ -293,50 +345,81 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) { setCursorPosition(null); setStartDeviceId(null); setEndDeviceId(null); + setNearbyDevice(null); + setNearbyFullDevice(null); } }; - // Render drawing preview - if (isDrawing && drawingPoints.length > 0) { - const color = isCableTool - ? CABLE_COLORS[activeTool as CableType] - : isWirelessTool - ? '#10B981' - : '#6B7280'; + // Render drawing preview and snap indicator + const color = isCableTool + ? CABLE_COLORS[activeTool as CableType] + : isWirelessTool + ? '#10B981' + : '#6B7280'; - const dashArray = isWirelessTool ? '10, 10' : undefined; + const dashArray = isWirelessTool ? '10, 10' : undefined; - return ( - <> - {/* Main line connecting all points */} - {drawingPoints.length > 1 && ( - - )} + return ( + <> + {/* Green snap indicator - show when hovering near a device with available ports */} + {nearbyDevice && nearbyDevice.geometry.type === 'Point' && ( + + )} - {/* Preview line from last point to cursor */} - {cursorPosition && drawingPoints.length > 0 && ( - - )} + {/* Red snap indicator - show when hovering near a device with full ports */} + {nearbyFullDevice && nearbyFullDevice.geometry.type === 'Point' && ( + + )} - {/* Markers at each point */} - {drawingPoints.map((point, idx) => ( - - ))} - - ); - } + {/* Drawing preview - only show when actively drawing */} + {isDrawing && drawingPoints.length > 0 && ( + <> + {/* Main line connecting all points */} + {drawingPoints.length > 1 && ( + + )} - return null; + {/* Preview line from last point to cursor */} + {cursorPosition && drawingPoints.length > 0 && ( + + )} + + {/* Markers at each point */} + {drawingPoints.map((point, idx) => ( + + ))} + + )} + + ); }