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 */}

Map Controls

Active
Inactive
Route Trail

{selectedDriverForTrail && ( )}
{/* 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 ? (
{devices.map((device: any) => ( ))}
Driver Device ID Status Last Active Actions

{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.
{ const value = parseInt(e.target.value); if (value >= 7 && value <= 90 && value !== settings.retentionDays) { updateSettings.mutate({ retentionDays: value }); } }} className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground focus:ring-primary focus:border-primary" />

How long to keep location history (7-90 days)

{ const [hours, minutes] = e.target.value.split(':').map(Number); if (!isNaN(hours) && !isNaN(minutes)) { updateSettings.mutate({ shiftStartHour: hours, shiftStartMinute: minutes, }); } }} className="mt-1 px-3 py-2 border border-input rounded-md bg-background text-foreground focus:ring-primary focus:border-primary" />
to
{ const [hours, minutes] = e.target.value.split(':').map(Number); if (!isNaN(hours) && !isNaN(minutes)) { updateSettings.mutate({ shiftEndHour: hours, shiftEndMinute: minutes, }); } }} className="mt-1 px-3 py-2 border border-input rounded-md bg-background text-foreground focus:ring-primary focus:border-primary" />

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 */}
Download Traccar Client
{/* Manual fallback - collapsible */}
Manual Setup (if QR doesn't work)
{enrollmentResult.deviceIdentifier}
{enrollmentResult.serverUrl}
  1. Open Traccar Client and enter Device ID and Server URL above
  2. Set frequency to {settings?.updateIntervalSeconds || 60} seconds
  3. 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 */}
Download Traccar Client
{/* Manual fallback */}
Manual Setup (if QR doesn't work)
{qrInfo.deviceIdentifier}
{qrInfo.serverUrl}
  1. Open Traccar Client and enter Device ID and Server URL above
  2. Set frequency to {qrInfo.updateIntervalSeconds} seconds
  3. Tap "Service Status" to start tracking
) : ( )}
)}
); }