import { useState, useEffect, useRef, useMemo } from 'react';
import { MapContainer, TileLayer, Marker, Popup, Polyline, useMap } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { QRCodeSVG } from 'qrcode.react';
import {
MapPin,
Settings,
Users,
RefreshCw,
Navigation,
Battery,
Clock,
ExternalLink,
X,
Search,
AlertCircle,
CheckCircle,
XCircle,
UserPlus,
UserMinus,
Gauge,
Activity,
Smartphone,
Route,
Car,
Copy,
QrCode,
} from 'lucide-react';
import {
useGpsStatus,
useGpsSettings,
useUpdateGpsSettings,
useDriverLocations,
useGpsDevices,
useEnrollDriver,
useUnenrollDriver,
useDriverStats,
useTraccarSetupStatus,
useTraccarSetup,
useOpenTraccarAdmin,
useDeviceQr,
useDriverLocationHistory,
} from '@/hooks/useGps';
import { Loading } from '@/components/Loading';
import { ErrorMessage } from '@/components/ErrorMessage';
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api';
import { useAuth } from '@/contexts/AuthContext';
import type { Driver } from '@/types';
import type { DriverLocation } from '@/types/gps';
import toast from 'react-hot-toast';
import { formatDistanceToNow } from 'date-fns';
// Fix Leaflet default marker icons
delete (L.Icon.Default.prototype as any)._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png',
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png',
});
// Custom driver marker icon
const createDriverIcon = (isActive: boolean) => {
return L.divIcon({
className: 'custom-driver-marker',
html: `
`,
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -32],
});
};
// Map auto-fit component — only fits bounds on initial load, not on every refresh
function MapFitBounds({ locations }: { locations: DriverLocation[] }) {
const map = useMap();
const hasFitted = useRef(false);
useEffect(() => {
if (hasFitted.current) return;
const validLocations = locations.filter(d => d.location);
if (validLocations.length > 0) {
const bounds = L.latLngBounds(
validLocations.map(loc => [loc.location!.latitude, loc.location!.longitude])
);
map.fitBounds(bounds, { padding: [50, 50], maxZoom: 14 });
hasFitted.current = true;
}
}, [locations, map]);
return null;
}
export function GpsTracking() {
const { backendUser } = useAuth();
const [activeTab, setActiveTab] = useState<'map' | 'devices' | 'settings' | 'stats'>('map');
const [showEnrollModal, setShowEnrollModal] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [selectedDriverId, setSelectedDriverId] = useState('');
const [enrollmentResult, setEnrollmentResult] = useState(null);
const [showQrDriverId, setShowQrDriverId] = useState(null);
const [selectedDriverForTrail, setSelectedDriverForTrail] = useState(null);
const [showTrails, setShowTrails] = useState(true);
// Check admin access
if (backendUser?.role !== 'ADMINISTRATOR') {
return (
Access Denied
Only Administrators can access GPS tracking.
);
}
// Data hooks
const { data: status, isLoading: statusLoading } = useGpsStatus();
const { data: settings, isLoading: settingsLoading } = useGpsSettings();
const { data: locations, isLoading: locationsLoading, refetch: refetchLocations } = useDriverLocations();
const { data: devices, isLoading: devicesLoading } = useGpsDevices();
const { data: traccarStatus } = useTraccarSetupStatus();
const { data: driverStats } = useDriverStats(selectedDriverId);
const { data: qrInfo, isLoading: qrLoading } = useDeviceQr(showQrDriverId);
// Fetch location history for trail display
// If a specific driver is selected, show only their trail. Otherwise, fetch for all active drivers.
const activeDriverIds = useMemo(() => {
return locations?.filter(l => l.location).map(l => l.driverId) || [];
}, [locations]);
// For simplicity, fetch history for the selected driver or the first active driver
const driverIdForTrail = selectedDriverForTrail || (showTrails ? activeDriverIds[0] : null);
const { data: locationHistory } = useDriverLocationHistory(driverIdForTrail, undefined, undefined);
// Mutations
const updateSettings = useUpdateGpsSettings();
const traccarSetup = useTraccarSetup();
const openTraccar = useOpenTraccarAdmin();
const enrollDriver = useEnrollDriver();
const unenrollDriver = useUnenrollDriver();
// Get all drivers for enrollment
const { data: allDrivers } = useQuery({
queryKey: ['drivers'],
queryFn: async () => {
const { data } = await api.get('/drivers');
return data;
},
});
// Filter unenrolled drivers
const enrolledDriverIds = new Set(devices?.map((d: any) => d.driverId) || []);
const unenrolledDrivers = allDrivers?.filter(d => !enrolledDriverIds.has(d.id)) || [];
const filteredUnenrolled = unenrolledDrivers.filter(d =>
d.name.toLowerCase().includes(searchTerm.toLowerCase())
);
// Calculate center for map
const mapCenter: [number, number] = useMemo(() => {
const validLocations = locations?.filter(l => l.location) || [];
if (validLocations.length > 0) {
return [
validLocations.reduce((sum, loc) => sum + loc.location!.latitude, 0) / validLocations.length,
validLocations.reduce((sum, loc) => sum + loc.location!.longitude, 0) / validLocations.length,
];
}
return [36.0, -79.0]; // Default to North Carolina area
}, [locations]);
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
toast.success('Copied to clipboard');
};
const handleEnroll = async (driverId: string) => {
try {
const result = await enrollDriver.mutateAsync({ driverId, sendSignalMessage: true });
setEnrollmentResult(result);
} catch (error) {
// Error handled by hook
}
};
if (statusLoading) {
return ;
}
// Check if Traccar needs setup
if (traccarStatus?.needsSetup && traccarStatus?.isAvailable) {
return (
GPS Tracking Setup
The GPS tracking system needs to be configured before you can start tracking drivers.
This will set up the Traccar server connection.
);
}
return (
{/* Header */}
GPS Tracking
Monitor driver locations in real-time
{status?.traccarAvailable && (
)}
{/* Traccar Status */}
{!status?.traccarAvailable && (
Traccar Server Offline
GPS tracking is unavailable until the server is online.
)}
{/* Status Cards */}
Total Enrolled
{status?.enrolledDrivers || 0}
Active Now
{status?.activeDrivers || 0}
Update Interval
{status?.settings?.updateIntervalSeconds || 60}s
Shift Hours
{status?.settings?.shiftStartTime || '4:00'} - {status?.settings?.shiftEndTime || '1:00'}
{/* Tabs */}
{[
{ id: 'map', label: 'Live Map', icon: MapPin },
{ id: 'devices', label: 'Devices', icon: Smartphone },
{ id: 'stats', label: 'Stats', icon: Gauge },
{ id: 'settings', label: 'Settings', icon: Settings },
].map(tab => (
))}
{/* Tab Content */}
{/* Map Tab */}
{activeTab === 'map' && (
{locationsLoading ? (
) : (
{locations && }
{/* Route trail polyline */}
{showTrails && locationHistory && locationHistory.length > 1 && (
[loc.latitude, loc.longitude])}
pathOptions={{
color: '#3b82f6',
weight: 3,
opacity: 0.6,
}}
/>
)}
{/* Current position markers */}
{locations?.filter(l => l.location).map((driver) => (
{driver.driverName}
{driver.driverPhone && (
{driver.driverPhone}
)}
{driver.location?.speed?.toFixed(1) || 0} mph
{driver.location?.battery && (
{Math.round(driver.location.battery)}%
)}
{formatDistanceToNow(new Date(driver.location!.timestamp), { addSuffix: true })}
))}
)}
{/* Map Legend & Controls */}
{/* Active Drivers List */}
Active Drivers ({locations?.filter(l => l.location).length || 0})
{!locations || locations.filter(l => l.location).length === 0 ? (
No active drivers reporting location
) : (
{locations.filter(l => l.location).map((driver) => (
{driver.driverName}
{driver.location?.speed?.toFixed(0) || 0} mph
Online
))}
)}
)}
{/* Devices Tab */}
{activeTab === 'devices' && (
Enrolled Devices
{devicesLoading ? (
) : devices && devices.length > 0 ? (
| Driver |
Device ID |
Status |
Last Active |
Actions |
{devices.map((device: any) => (
|
{device.driver?.name || 'Unknown'}
{device.driver?.phone && {device.driver.phone} }
|
{device.deviceIdentifier} |
{device.isActive ? 'Active' : 'Inactive'}
|
{device.lastActive ? formatDistanceToNow(new Date(device.lastActive), { addSuffix: true }) : 'Never'}
|
|
))}
) : (
No devices enrolled
Start by enrolling drivers for GPS tracking
)}
)}
{/* Stats Tab */}
{activeTab === 'stats' && (
Driver Statistics
{selectedDriverId && driverStats ? (
{driverStats.stats?.totalMiles || 0}
Miles Driven
{driverStats.stats?.topSpeedMph || 0} mph
Top Speed
{driverStats.stats?.averageSpeedMph || 0} mph
Avg Speed
{driverStats.stats?.totalTrips || 0}
Total Trips
) : selectedDriverId ? (
) : (
Select a driver to view their statistics
)}
)}
{/* Settings Tab */}
{activeTab === 'settings' && (
GPS Settings
{settingsLoading ? (
) : settings ? (
{
const value = parseInt(e.target.value);
if (value >= 10 && value <= 300 && value !== settings.updateIntervalSeconds) {
updateSettings.mutate({ updateIntervalSeconds: value });
}
}}
className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground focus:ring-primary focus:border-primary"
/>
How often drivers report their location (10-300 seconds)
Tip: 15-30s recommended for active events (smooth routes), 60s for routine use (saves battery). Changing this only affects new QR code enrollments.
Drivers are only tracked during these hours.
) : (
)}
)}
{/* Enroll Driver Modal */}
{showEnrollModal && (
{enrollmentResult ? 'Driver Enrolled!' : 'Enroll Driver for GPS'}
{!enrollmentResult ? (
<>
setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-input rounded-md bg-background text-foreground focus:ring-primary focus:border-primary"
/>
{filteredUnenrolled.length === 0 ? (
{searchTerm ? 'No matching drivers found' : 'All drivers are already enrolled'}
) : (
{filteredUnenrolled.map((driver) => (
{driver.name}
{driver.phone || 'No phone'}
))}
)}
Enrolling a driver will send them setup instructions via Signal message.
>
) : (
Driver Enrolled Successfully!
{enrollmentResult.signalMessageSent && (
Setup instructions sent via Signal
)}
{/* QR Code - scan with Traccar Client app to auto-configure */}
Scan with Traccar Client
Open Traccar Client app → tap the QR icon → scan this code
{/* Download links */}
{/* Manual fallback - collapsible */}
Manual Setup (if QR doesn't work)
{enrollmentResult.deviceIdentifier}
{enrollmentResult.serverUrl}
- Open Traccar Client and enter Device ID and Server URL above
- Set frequency to {settings?.updateIntervalSeconds || 60} seconds
- Tap "Service Status" to start tracking
)}
)}
{/* Device QR Code Modal */}
{showQrDriverId && (
{qrInfo ? `${qrInfo.driverName} - Setup QR` : 'Device QR Code'}
{qrLoading ? (
) : qrInfo ? (
<>
{/* QR Code */}
Scan with Traccar Client
Open Traccar Client app {'→'} tap the QR icon {'→'} scan this code
{/* Download links */}
{/* Manual fallback */}
Manual Setup (if QR doesn't work)
{qrInfo.deviceIdentifier}
{qrInfo.serverUrl}
- Open Traccar Client and enter Device ID and Server URL above
- Set frequency to {qrInfo.updateIntervalSeconds} seconds
- Tap "Service Status" to start tracking
>
) : (
)}
)}
);
}