diff --git a/client/src/pages/MapView.jsx b/client/src/pages/MapView.jsx index 286f4c2..6752a95 100644 --- a/client/src/pages/MapView.jsx +++ b/client/src/pages/MapView.jsx @@ -233,37 +233,44 @@ const MapView = () => { // Set initial bounds and center ONLY on first load if (deviceData.length > 0 && isInitialLoad) { - const lats = deviceData.map(device => device.geo_lat); - const lons = deviceData.map(device => device.geo_lon); + // Filter devices that have coordinates + const devicesWithCoords = deviceData.filter(device => + device.geo_lat !== null && device.geo_lon !== null + ); - const minLat = Math.min(...lats); - const maxLat = Math.max(...lats); - const minLon = Math.min(...lons); - const maxLon = Math.max(...lons); - - // Add padding around the bounds (15% on each side for better visibility) - const latPadding = (maxLat - minLat) * 0.15; - const lonPadding = (maxLon - minLon) * 0.15; - - const bounds = [ - [minLat - latPadding, minLon - lonPadding], // Southwest - [maxLat + latPadding, maxLon + lonPadding] // Northeast - ]; - - console.log('MapView: Setting initial bounds and center for devices'); - setMapBounds(bounds); - setShouldFitBounds(true); - - // Calculate center - const centerLat = (minLat + maxLat) / 2; - const centerLon = (minLon + maxLon) / 2; - setMapCenter([centerLat, centerLon]); - - // Disable automatic fitting after initial setup (with longer delay) - setTimeout(() => { - console.log('MapView: Disabling automatic bounds fitting - manual navigation now preserved'); - setShouldFitBounds(false); - }, 2000); + if (devicesWithCoords.length > 0) { + const lats = devicesWithCoords.map(device => device.geo_lat); + const lons = devicesWithCoords.map(device => device.geo_lon); + + const minLat = Math.min(...lats); + const maxLat = Math.max(...lats); + const minLon = Math.min(...lons); + const maxLon = Math.max(...lons); + + // Add padding around the bounds (15% on each side for better visibility) + const latPadding = (maxLat - minLat) * 0.15; + const lonPadding = (maxLon - minLon) * 0.15; + + const bounds = [ + [minLat - latPadding, minLon - lonPadding], // Southwest + [maxLat + latPadding, maxLon + lonPadding] // Northeast + ]; + + console.log('MapView: Setting initial bounds and center for devices with coordinates'); + setMapBounds(bounds); + setShouldFitBounds(true); + + // Calculate center + const centerLat = (minLat + maxLat) / 2; + const centerLon = (minLon + maxLon) / 2; + setMapCenter([centerLat, centerLon]); + + // Disable automatic fitting after initial setup (with longer delay) + setTimeout(() => { + console.log('MapView: Disabling automatic bounds fitting - manual navigation now preserved'); + setShouldFitBounds(false); + }, 2000); + } } // Always mark initial load as complete after first fetch @@ -999,46 +1006,64 @@ const DroneDetectionPopup = ({ detection, age, droneTypes, droneDetectionHistory ); }; -const DeviceListItem = ({ device, status, detections, onClick }) => ( -
-
-
-
0 ? 'bg-red-400 animate-pulse' : 'bg-green-400' - : 'bg-gray-400' - }`} /> -
-
- {device.name || `Device ${device.id}`} -
-
- {device.location_description || `${device.geo_lat}, ${device.geo_lon}`} +const DeviceListItem = ({ device, status, detections, onClick }) => { + const hasCoordinates = device.geo_lat !== null && device.geo_lon !== null; + + return ( +
+
+
+
0 ? 'bg-red-400 animate-pulse' : 'bg-green-400' + : 'bg-gray-400' + }`} /> +
+
+ {device.name || `Device ${device.id}`} +
+
+ {hasCoordinates + ? (device.location_description || `${device.geo_lat}, ${device.geo_lon}`) + : ( + + ⚠️ Coordinates missing - please add location + + ) + } +
-
- -
- {detections.length > 0 && ( -
- - {detections.length} -
- )} - - {status} - +
+ {!hasCoordinates && ( +
+ + Incomplete +
+ )} + + {detections.length > 0 && ( +
+ + {detections.length} +
+ )} + + + {status} + +
-
-); + ); +}; export default MapView; diff --git a/server/routes/device.js b/server/routes/device.js index 8b5e8a7..bff68bc 100644 --- a/server/routes/device.js +++ b/server/routes/device.js @@ -137,11 +137,10 @@ router.get('/', authenticateToken, async (req, res) => { // GET /api/devices/map - Get devices with location data for map display router.get('/map', authenticateToken, async (req, res) => { try { + // Get all active devices, including those without coordinates const devices = await Device.findAll({ where: { - is_active: true, - geo_lat: { [Op.ne]: null }, - geo_lon: { [Op.ne]: null } + is_active: true }, attributes: [ 'id', @@ -153,7 +152,7 @@ router.get('/map', authenticateToken, async (req, res) => { ] }); - // Get recent detections for each device + // Get recent detections for each device and mark coordinate status const devicesWithDetections = await Promise.all(devices.map(async (device) => { const recentDetections = await DroneDetection.count({ where: { @@ -170,12 +169,15 @@ router.get('/map', authenticateToken, async (req, res) => { : null; const isOnline = timeSinceLastHeartbeat && timeSinceLastHeartbeat < 600; // 10 minutes + const hasCoordinates = device.geo_lat !== null && device.geo_lon !== null; return { ...device.toJSON(), has_recent_detections: recentDetections > 0, detection_count_10m: recentDetections, - status: isOnline ? 'online' : 'offline' + status: isOnline ? 'online' : 'offline', + has_coordinates: hasCoordinates, + coordinate_status: hasCoordinates ? 'complete' : 'incomplete' }; }));