diff --git a/client/src/pages/MapView.jsx b/client/src/pages/MapView.jsx
index a6cd8dc..0506826 100644
--- a/client/src/pages/MapView.jsx
+++ b/client/src/pages/MapView.jsx
@@ -421,7 +421,21 @@ const MapView = () => {
opacity: opacity * 0.8,
dashArray: dashPattern
}}
- />
+ eventHandlers={{
+ click: () => {
+ console.log('MapView: Ring clicked for drone:', detection);
+ }
+ }}
+ >
+
+
+
+
{/* Drone ID label for multiple drones */}
{totalDrones > 1 && age < 5 && ( // Show labels for recent detections with multiple drones
@@ -451,7 +465,12 @@ const MapView = () => {
opacity={opacity * 0.9}
>
-
+
)}
@@ -464,7 +483,12 @@ const MapView = () => {
opacity={opacity * 0.7}
>
-
+
)}
@@ -607,121 +631,222 @@ const DevicePopup = ({ device, status, detections }) => (
);
-const DroneDetectionPopup = ({ detection, age, droneTypes }) => (
-
-
-
- 🚨
- Drone Detection
-
-
- {age < 1 ? 'LIVE' : `${Math.round(age)}m ago`}
-
-
+const DroneDetectionPopup = ({ detection, age, droneTypes, droneDetectionHistory }) => {
+ // Get all detections for this specific drone from history
+ const droneHistory = droneDetectionHistory.filter(d =>
+ d.drone_id === detection.drone_id && d.device_id === detection.device_id
+ ).slice(0, 10); // Last 10 detections for this drone
+
+ // Calculate movement trend from history
+ const calculateMovementTrend = () => {
+ if (droneHistory.length < 2) return null;
-
-
-
-
Drone ID:
-
{detection.drone_id}
-
-
-
Type:
-
{droneTypes[detection.drone_type] || 'Unknown'}
-
+ const sortedHistory = [...droneHistory].sort((a, b) =>
+ new Date(a.device_timestamp) - new Date(b.device_timestamp)
+ );
+
+ const first = sortedHistory[0];
+ const latest = sortedHistory[sortedHistory.length - 1];
+ const rssiChange = latest.rssi - first.rssi;
+
+ return {
+ change: rssiChange,
+ trend: rssiChange > 2 ? 'APPROACHING' :
+ rssiChange < -2 ? 'RETREATING' : 'STABLE',
+ duration: (new Date(latest.device_timestamp) - new Date(first.device_timestamp)) / 1000 / 60, // minutes
+ detectionCount: droneHistory.length
+ };
+ };
+
+ const movementTrend = calculateMovementTrend();
+ const firstDetection = droneHistory.length > 0 ?
+ droneHistory.reduce((earliest, current) =>
+ new Date(current.device_timestamp) < new Date(earliest.device_timestamp) ? current : earliest
+ ) : detection;
+
+ return (
+
+
+
+ 🚨
+ Drone Detection Details
+
+
+ {age < 1 ? 'LIVE' : `${Math.round(age)}m ago`}
+
-
-
-
RSSI:
-
-50 ? 'text-red-600' :
- detection.rssi > -70 ? 'text-orange-600' :
- 'text-green-600'
- }`}>
- {detection.rssi}dBm
-
-
-
-
Frequency:
-
{detection.freq}MHz
-
-
-
-
-
-
Confidence:
-
{(detection.confidence_level * 100).toFixed(0)}%
-
-
-
Duration:
-
{(detection.signal_duration / 1000).toFixed(1)}s
-
-
-
- {/* Movement Analysis */}
- {detection.movement_analysis && (
-
-
Movement Analysis:
-
-
= 3 ? 'bg-red-100 text-red-800' :
- detection.movement_analysis.alertLevel >= 2 ? 'bg-orange-100 text-orange-800' :
- detection.movement_analysis.alertLevel >= 1 ? 'bg-blue-100 text-blue-800' :
- 'bg-gray-100 text-gray-800'
- }`}>
- {detection.movement_analysis.description}
+
+ {/* Basic Information */}
+
+
+
+
Drone ID:
+
{detection.drone_id}
-
- {detection.movement_analysis.rssiTrend && (
-
-
Trend:
-
- {detection.movement_analysis.rssiTrend.trend}
- {detection.movement_analysis.rssiTrend.change !== 0 && (
-
- ({detection.movement_analysis.rssiTrend.change > 0 ? '+' : ''}{detection.movement_analysis.rssiTrend.change.toFixed(1)}dB)
-
- )}
-
+
+
Type:
+
{droneTypes[detection.drone_type] || 'Unknown'}
+
+
+
+
+
+
RSSI:
+
-50 ? 'text-red-600' :
+ detection.rssi > -70 ? 'text-orange-600' :
+ 'text-green-600'
+ }`}>
+ {detection.rssi}dBm
- )}
-
- {detection.movement_analysis.proximityLevel && (
-
- Proximity:
-
- {detection.movement_analysis.proximityLevel.replace('_', ' ')}
-
+
+
+
Frequency:
+
{detection.freq}MHz
+
+
+
+
+ {/* Detection Timeline */}
+
+
Detection Timeline:
+
+
+ First detected:
+
+ {format(new Date(firstDetection.device_timestamp), 'MMM dd, HH:mm:ss')}
+
+
+
+ Latest detection:
+
+ {format(new Date(detection.device_timestamp), 'MMM dd, HH:mm:ss')}
+
+
+ {droneHistory.length > 1 && (
+
+ Total detections:
+ {droneHistory.length}
)}
- )}
-
-
-
-
Detected by: Device {detection.device_id}
-
Location: {detection.geo_lat?.toFixed(4)}, {detection.geo_lon?.toFixed(4)}
-
Time: {format(new Date(detection.device_timestamp), 'MMM dd, HH:mm:ss')}
+
+ {/* Movement Analysis */}
+ {movementTrend && (
+
+
Movement Analysis:
+
+
+
+ {movementTrend.trend === 'APPROACHING' ? '⚠️ APPROACHING' :
+ movementTrend.trend === 'RETREATING' ? '✅ RETREATING' :
+ '➡️ STABLE POSITION'}
+
+
+ RSSI change: {movementTrend.change > 0 ? '+' : ''}{movementTrend.change.toFixed(1)}dB
+ over {movementTrend.duration.toFixed(1)} minutes
+
+
+
+ {/* Signal Strength History Graph (simplified) */}
+
+
Signal Strength Trend:
+
+ {droneHistory.slice(0, 8).reverse().map((hist, idx) => {
+ const height = Math.max(10, Math.min(32, (hist.rssi + 100) / 2)); // Scale -100 to 0 dBm to 10-32px
+ return (
+
-50 ? 'bg-red-400' :
+ hist.rssi > -70 ? 'bg-orange-400' :
+ 'bg-green-400'
+ }`}
+ style={{ height: `${height}px` }}
+ title={`${hist.rssi}dBm at ${format(new Date(hist.device_timestamp), 'HH:mm:ss')}`}
+ />
+ );
+ })}
+
+
Last 8 detections (oldest to newest)
+
+
+
+ )}
+
+ {/* Current Detection Details */}
+
+
Current Detection:
+
+
+
Confidence:
+
{(detection.confidence_level * 100).toFixed(0)}%
+
+
+
Duration:
+
{(detection.signal_duration / 1000).toFixed(1)}s
+
+
+
Detector:
+
Device {detection.device_id}
+
+
+
Location:
+
+ {detection.geo_lat?.toFixed(4)}, {detection.geo_lon?.toFixed(4)}
+
+
+
+
+ {/* Legacy movement analysis from detection */}
+ {detection.movement_analysis && (
+
+
Real-time Analysis:
+
+
= 3 ? 'bg-red-100 text-red-800' :
+ detection.movement_analysis.alertLevel >= 2 ? 'bg-orange-100 text-orange-800' :
+ detection.movement_analysis.alertLevel >= 1 ? 'bg-blue-100 text-blue-800' :
+ 'bg-gray-100 text-gray-800'
+ }`}>
+ {detection.movement_analysis.description}
+
+
+ {detection.movement_analysis.rssiTrend && (
+
+ Instant trend:
+
+ {detection.movement_analysis.rssiTrend.trend}
+ {detection.movement_analysis.rssiTrend.change !== 0 && (
+
+ ({detection.movement_analysis.rssiTrend.change > 0 ? '+' : ''}{detection.movement_analysis.rssiTrend.change.toFixed(1)}dB)
+
+ )}
+
+
+ )}
+
+
+ )}
-
-);
+ );
+};
const DeviceListItem = ({ device, status, detections, onClick }) => (