import { useQuery } from '@tanstack/react-query'; import { api } from '@/lib/api'; import { Loading } from '@/components/Loading'; import { Car, Clock, MapPin, Users, AlertTriangle, CheckCircle, Plane, Radio, Bell, AlertCircle, ArrowRight, ChevronRight, Activity, Calendar, } from 'lucide-react'; import { useEffect, useState, useRef, useMemo } from 'react'; import { Link } from 'react-router-dom'; import { formatDistanceToNow } from 'date-fns'; import { DriverChatBubble } from '@/components/DriverChatBubble'; import { DriverChatModal } from '@/components/DriverChatModal'; import { GpsIndicatorDot } from '@/components/GpsIndicatorDot'; import { DriverLocationModal } from '@/components/DriverLocationModal'; import { useUnreadCounts, useDriverResponseCheck } from '@/hooks/useSignalMessages'; import { useDriverLocations } from '@/hooks/useGps'; import type { DriverLocation } from '@/types/gps'; import type { Flight } from '@/types'; import { groupFlightsIntoJourneys, formatLayoverDuration } from '@/lib/journeyUtils'; import { useFormattedDate } from '@/hooks/useFormattedDate'; interface Event { id: string; title: string; pickupLocation: string | null; dropoffLocation: string | null; location: string | null; startTime: string; endTime: string; actualStartTime: string | null; status: string; type: string; vip: { id: string; name: string; partySize?: number; } | null; vips?: Array<{ id: string; name: string; partySize: number; }>; driver: { id: string; name: string; } | null; vehicle: { id: string; name: string; type: string; seatCapacity: number; } | null; } interface Vehicle { id: string; name: string; type: string; seatCapacity: number; status: string; currentDriver: { name: string } | null; } interface VIP { id: string; name: string; organization: string | null; arrivalMode: string; expectedArrival: string | null; flights: Array<{ id: string; flightNumber: string; arrivalAirport: string; departureAirport: string; scheduledArrival: string | null; estimatedArrival: string | null; actualArrival: string | null; arrivalDelay: number | null; departureDelay: number | null; arrivalTerminal: string | null; arrivalGate: string | null; arrivalBaggage: string | null; status: string | null; airlineName: string | null; }>; } interface Driver { id: string; name: string; phone: string; } const SCROLL_SPEED = 60; // pixels per second (must be >= 60 for 60fps to register 1px per frame) const SCROLL_PAUSE_AT_END = 2000; // pause 2 seconds at top/bottom const USER_INTERACTION_PAUSE = 20000; // pause 20 seconds after user interaction export function CommandCenter() { const { formatTime, formatDateTime, timezone } = useFormattedDate(); const [currentTime, setCurrentTime] = useState(new Date()); const [lastRefresh, setLastRefresh] = useState(new Date()); const [chatDriver, setChatDriver] = useState(null); const [isChatOpen, setIsChatOpen] = useState(false); const [locationDriver, setLocationDriver] = useState(null); const [isLocationOpen, setIsLocationOpen] = useState(false); // Refs for smooth auto-scrolling const activeScrollRef = useRef(null); const upcomingScrollRef = useRef(null); const arrivalsScrollRef = useRef(null); const availableScrollRef = useRef(null); // Track user interaction to pause auto-scroll const lastInteractionRef = useRef(0); // Track scroll directions (persists across renders) const scrollDirectionsRef = useRef([1, 1, 1, 1]); const handleUserInteraction = () => { lastInteractionRef.current = Date.now(); }; const { data: unreadCounts } = useUnreadCounts(); const openChat = (driver: { id: string; name: string; phone?: string }) => { // Find full driver info including phone const fullDriver = drivers?.find(d => d.id === driver.id); if (fullDriver) { setChatDriver(fullDriver); setIsChatOpen(true); } }; const closeChat = () => { setIsChatOpen(false); setChatDriver(null); }; const openLocationModal = (loc: DriverLocation) => { setLocationDriver(loc); setIsLocationOpen(true); }; const closeLocationModal = () => { setIsLocationOpen(false); setLocationDriver(null); }; // GPS driver locations const { data: driverLocations } = useDriverLocations(); const driverLocationMap = useMemo(() => { const map = new Map(); driverLocations?.forEach(loc => map.set(loc.driverId, loc)); return map; }, [driverLocations]); const { data: events, refetch: refetchEvents } = useQuery({ queryKey: ['events'], queryFn: async () => { const { data } = await api.get('/events'); return data; }, }); const { data: vehicles, refetch: refetchVehicles } = useQuery({ queryKey: ['vehicles'], queryFn: async () => { const { data } = await api.get('/vehicles'); return data; }, }); const { data: vips, refetch: refetchVips } = useQuery({ queryKey: ['vips'], queryFn: async () => { const { data } = await api.get('/vips'); return data; }, }); const { data: drivers } = useQuery({ queryKey: ['drivers'], queryFn: async () => { const { data } = await api.get('/drivers'); return data; }, }); const { data: flights } = useQuery({ queryKey: ['flights'], queryFn: async () => { const { data } = await api.get('/flights'); return data; }, }); // Group flights into journeys for connection risk detection const journeys = useMemo(() => { if (!flights || flights.length === 0) return []; return groupFlightsIntoJourneys(flights); }, [flights]); // Compute awaiting confirmation BEFORE any conditional returns (for hooks) const now = currentTime; const awaitingConfirmation = (events || []).filter((event) => { if (event.status !== 'SCHEDULED' || event.type !== 'TRANSPORT') return false; const start = new Date(event.startTime); return start <= now; }); // Check which awaiting events have driver responses since the event started // MUST be called before any conditional returns to satisfy React's rules of hooks const { data: respondedEventIds } = useDriverResponseCheck(awaitingConfirmation); // Update clock every second useEffect(() => { const clockInterval = setInterval(() => { setCurrentTime(new Date()); }, 1000); return () => clearInterval(clockInterval); }, []); // Auto-refresh data every 30 seconds useEffect(() => { const interval = setInterval(() => { refetchEvents(); refetchVehicles(); refetchVips(); setLastRefresh(new Date()); }, 30000); return () => clearInterval(interval); }, [refetchEvents, refetchVehicles, refetchVips]); // Smooth auto-scrolling for Active, Upcoming, Arrivals, and Available sections useEffect(() => { const pauseTimers: (NodeJS.Timeout | null)[] = [null, null, null, null]; const directions = scrollDirectionsRef.current; const animate = () => { // Get current refs (they may change) const scrollContainers = [ activeScrollRef.current, upcomingScrollRef.current, arrivalsScrollRef.current, availableScrollRef.current, ]; // Check if user has interacted recently const timeSinceInteraction = Date.now() - lastInteractionRef.current; if (timeSinceInteraction < USER_INTERACTION_PAUSE) { return; // Pause scrolling while user is interacting } scrollContainers.forEach((container, index) => { if (!container || pauseTimers[index]) return; const { scrollTop, scrollHeight, clientHeight } = container; const maxScroll = scrollHeight - clientHeight; // Only scroll if there's content to scroll if (maxScroll <= 0) return; // Calculate new scroll position (ensure at least 1px movement) const scrollAmount = Math.max(1, Math.round(SCROLL_SPEED / 60)); // 60fps, min 1px const newScrollTop = scrollTop + (scrollAmount * directions[index]); // Check if we've reached the bottom if (directions[index] === 1 && newScrollTop >= maxScroll) { container.scrollTop = maxScroll; directions[index] = -1; // Pause at bottom pauseTimers[index] = setTimeout(() => { pauseTimers[index] = null; }, SCROLL_PAUSE_AT_END); } // Check if we've reached the top else if (directions[index] === -1 && newScrollTop <= 0) { container.scrollTop = 0; directions[index] = 1; // Pause at top pauseTimers[index] = setTimeout(() => { pauseTimers[index] = null; }, SCROLL_PAUSE_AT_END); } else { container.scrollTop = newScrollTop; } }); }; const intervalId = setInterval(animate, 1000 / 60); // 60fps return () => { clearInterval(intervalId); pauseTimers.forEach(timer => timer && clearTimeout(timer)); }; }, []); // Run once on mount if (!events || !vehicles || !vips) { return ; } const fifteenMinutes = new Date(now.getTime() + 15 * 60 * 1000); const thirtyMinutes = new Date(now.getTime() + 30 * 60 * 1000); const twoHoursLater = new Date(now.getTime() + 2 * 60 * 60 * 1000); const fourHoursLater = new Date(now.getTime() + 4 * 60 * 60 * 1000); // En route trips (IN_PROGRESS) const enRouteTrips = events.filter( (event) => event.status === 'IN_PROGRESS' && event.type === 'TRANSPORT' ); // Upcoming trips in next 2 hours const upcomingTrips = events .filter((event) => { const start = new Date(event.startTime); return start > now && start <= twoHoursLater && event.type === 'TRANSPORT' && event.status === 'SCHEDULED'; }) .sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime()); // ALERTS: Events in next 2 hours without driver or vehicle const unassignedEvents = upcomingTrips.filter((e) => !e.driver || !e.vehicle); // ALERTS: Events starting in <15 min const imminentEvents = upcomingTrips.filter((e) => { const start = new Date(e.startTime); return start <= fifteenMinutes; }); // Upcoming arrivals (next 4 hours) - use best available time (estimated > scheduled) const getFlightArrivalTime = (flight: { actualArrival: string | null; estimatedArrival: string | null; scheduledArrival: string | null }) => flight.actualArrival || flight.estimatedArrival || flight.scheduledArrival; const upcomingArrivals = vips .filter((vip) => { if (vip.expectedArrival) { const arrival = new Date(vip.expectedArrival); return arrival > now && arrival <= fourHoursLater; } return vip.flights.some((flight) => { const arrTime = getFlightArrivalTime(flight); if (arrTime && flight.status?.toLowerCase() !== 'cancelled') { const arrival = new Date(arrTime); return arrival > now && arrival <= fourHoursLater; } return false; }); }) .sort((a, b) => { const aTime = a.expectedArrival || getFlightArrivalTime(a.flights[0]) || ''; const bTime = b.expectedArrival || getFlightArrivalTime(b.flights[0]) || ''; return new Date(aTime).getTime() - new Date(bTime).getTime(); }); // Vehicle status - calculate dynamically based on active events // A vehicle is "in use" if it's assigned to an IN_PROGRESS event const vehiclesInActiveEvents = new Set( events .filter((e) => e.status === 'IN_PROGRESS' && e.vehicle) .map((e) => e.vehicle!.id) ); // Also mark vehicles assigned to events starting within 15 minutes as "reserved" const vehiclesReservedSoon = new Set( events .filter((e) => { if (e.status !== 'SCHEDULED' || !e.vehicle) return false; const start = new Date(e.startTime); return start > now && start <= fifteenMinutes; }) .map((e) => e.vehicle!.id) ); const inUseVehicles = vehicles.filter( (v) => vehiclesInActiveEvents.has(v.id) || v.status === 'IN_USE' ); const reservedVehicles = vehicles.filter( (v) => vehiclesReservedSoon.has(v.id) && !vehiclesInActiveEvents.has(v.id) && v.status !== 'IN_USE' ); const availableVehicles = vehicles.filter( (v) => v.status !== 'MAINTENANCE' && !vehiclesInActiveEvents.has(v.id) && !vehiclesReservedSoon.has(v.id) && v.status !== 'IN_USE' ); // Calculate alerts const alerts: Array<{ type: 'critical' | 'warning' | 'info'; message: string; link?: string }> = []; // Alert for trips awaiting confirmation (overdue) awaitingConfirmation.forEach((event) => { const minutesLate = Math.floor((now.getTime() - new Date(event.startTime).getTime()) / 60000); if (minutesLate >= 5) { alerts.push({ type: 'critical', message: `${formatVipNames(event)}: Trip ${minutesLate}min overdue - awaiting driver confirmation`, link: `/events`, }); } }); unassignedEvents.forEach((event) => { const vipLabel = formatVipNames(event); if (!event.driver && !event.vehicle) { alerts.push({ type: 'critical', message: `${vipLabel}: No driver or vehicle assigned (${getTimeUntil(event.startTime)})`, link: `/events`, }); } else if (!event.driver) { alerts.push({ type: 'critical', message: `${vipLabel}: No driver assigned (${getTimeUntil(event.startTime)})`, link: `/events`, }); } else if (!event.vehicle) { alerts.push({ type: 'warning', message: `${vipLabel}: No vehicle assigned (${getTimeUntil(event.startTime)})`, link: `/events`, }); } }); if (availableVehicles.length === 0 && upcomingTrips.length > 0) { alerts.push({ type: 'warning', message: 'No vehicles available - all vehicles in use', link: `/fleet?tab=vehicles`, }); } // Flight alerts: cancelled, diverted, or significantly delayed if (flights) { const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); const todayEnd = new Date(todayStart); todayEnd.setDate(todayEnd.getDate() + 1); flights.forEach((flight) => { const arrivalTime = flight.estimatedArrival || flight.scheduledArrival; if (!arrivalTime) return; const arrDate = new Date(arrivalTime); if (arrDate < todayStart || arrDate > todayEnd) return; const status = flight.status?.toLowerCase(); const vipName = flight.vip?.name || 'Unknown VIP'; const delay = Math.max(flight.arrivalDelay || 0, flight.departureDelay || 0); if (status === 'cancelled') { alerts.push({ type: 'critical', message: `${flight.flightNumber} (${vipName}): FLIGHT CANCELLED`, link: '/flights', }); } else if (status === 'diverted') { alerts.push({ type: 'critical', message: `${flight.flightNumber} (${vipName}): FLIGHT DIVERTED`, link: '/flights', }); } else if (delay > 30) { alerts.push({ type: 'warning', message: `${flight.flightNumber} (${vipName}): Delayed ${delay} minutes`, link: '/flights', }); } }); } // Connection risk alerts from journey analysis journeys.forEach((journey) => { if (!journey.hasLayoverRisk) return; const vipName = journey.vip?.name || 'Unknown VIP'; journey.layovers.forEach((layover) => { if (layover.risk === 'missed') { alerts.push({ type: 'critical', message: `${vipName}: Connection MISSED at ${layover.airport} - arrived ${formatLayoverDuration(Math.abs(layover.effectiveMinutes))} after departure`, link: '/flights', }); } else if (layover.risk === 'critical') { alerts.push({ type: 'critical', message: `${vipName}: Connection at ${layover.airport} critical - only ${formatLayoverDuration(layover.effectiveMinutes)} layover`, link: '/flights', }); } else if (layover.risk === 'warning') { alerts.push({ type: 'warning', message: `${vipName}: Connection at ${layover.airport} tight - ${formatLayoverDuration(layover.effectiveMinutes)} layover (was ${formatLayoverDuration(layover.scheduledMinutes)})`, link: '/flights', }); } }); }); // Get time until event function getTimeUntil(dateStr: string) { const eventTime = new Date(dateStr); const diff = eventTime.getTime() - now.getTime(); const minutes = Math.floor(diff / 60000); if (minutes < 0) return 'NOW'; if (minutes < 60) return `${minutes}m`; const hours = Math.floor(minutes / 60); const remainingMin = minutes % 60; return `${hours}h ${remainingMin}m`; } function getUrgencyClass(startTime: string) { const start = new Date(startTime); const diff = start.getTime() - now.getTime(); const minutes = diff / 60000; if (minutes <= 15) return 'border-l-red-500 bg-red-50 dark:bg-red-950/30'; if (minutes <= 30) return 'border-l-orange-500 bg-orange-50 dark:bg-orange-950/20'; if (minutes <= 60) return 'border-l-yellow-500 bg-yellow-50 dark:bg-yellow-950/20'; return 'border-l-blue-500'; } function getTimeColor(startTime: string) { const start = new Date(startTime); const diff = start.getTime() - now.getTime(); const minutes = diff / 60000; if (minutes <= 15) return 'text-red-600 dark:text-red-400'; if (minutes <= 30) return 'text-orange-600 dark:text-orange-400'; if (minutes <= 60) return 'text-yellow-600 dark:text-yellow-400'; return 'text-blue-600 dark:text-blue-400'; } // Format VIP names for trip cards (show all, with party size indicators) function formatVipNames(event: Event): string { if (event.vips && event.vips.length > 0) { return event.vips.map(v => v.partySize > 1 ? `${v.name} (+${v.partySize - 1})` : v.name ).join(', '); } return event.vip?.name || 'Unknown VIP'; } const secondsSinceRefresh = Math.floor((now.getTime() - lastRefresh.getTime()) / 1000); // System status const hasAlerts = alerts.length > 0; const hasCriticalAlerts = alerts.some((a) => a.type === 'critical'); const systemStatus = hasCriticalAlerts ? 'critical' : hasAlerts ? 'warning' : 'operational'; return (
{/* Header with Live Clock */}
{/* Title and Status */}

Command Center

{systemStatus === 'operational' ? 'All Systems Operational' : systemStatus === 'warning' ? 'Attention Required' : 'Critical Issues'}
{/* Live Clock */}
{currentTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone: timezone })}
{currentTime.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', timeZone: timezone })}
Updated {secondsSinceRefresh}s ago
{/* Alerts Panel */} {alerts.length > 0 && (

{alerts.length} Alert{alerts.length > 1 ? 's' : ''} Requiring Attention

{alerts.slice(0, 5).map((alert, idx) => (
{alert.type === 'critical' ? ( ) : ( )} {alert.message}
{alert.link && ( Fix )}
))} {alerts.length > 5 && (

+{alerts.length - 5} more alerts

)}
)} {/* Quick Stats Row */}

En Route

{enRouteTrips.length}

Upcoming (2h)

{upcomingTrips.length}

Vehicles Free

{availableVehicles.length}/{vehicles.filter(v => v.status !== 'MAINTENANCE').length}

{inUseVehicles.length > 0 && (

{inUseVehicles.length} in use

)}

VIP Arrivals

{upcomingArrivals.length}

{/* Active & Upcoming - Side by Side */}
{/* Active NOW - includes both en route and awaiting confirmation */} {(() => { const allActiveTrips = [...awaitingConfirmation, ...enRouteTrips]; const awaitingIds = new Set(awaitingConfirmation.map(t => t.id)); return (
0 ? 'border-orange-500' : 'border-green-500'} rounded-lg shadow-soft overflow-hidden`}>
0 ? 'bg-orange-500' : 'bg-green-600'} px-4 py-2 flex items-center justify-between`}>

ACTIVE NOW

{awaitingConfirmation.length > 0 && ( {awaitingConfirmation.length} awaiting )}
{allActiveTrips.length} trips
{allActiveTrips.length === 0 ? (

No active transports

) : (
{allActiveTrips.map((trip) => { const isAwaiting = awaitingIds.has(trip.id); // Show glow only if awaiting AND driver hasn't responded since event started const showAwaitingGlow = isAwaiting && !respondedEventIds?.has(trip.id); return (
{formatVipNames(trip)} {trip.title} e.stopPropagation()} className="p-1 rounded hover:bg-primary/10 text-muted-foreground hover:text-primary transition-colors" title="View/Edit Event" >
{trip.pickupLocation} {trip.dropoffLocation}
{trip.driver && ( {trip.driver.name} openChat(trip.driver!)} className="ml-0.5" /> )} {trip.vehicle && ( {trip.vehicle.name} )}

ETA

{formatTime(new Date(trip.endTime))}

); })}
)}
); })()} {/* Upcoming Trips */}

UPCOMING

{upcomingTrips.length} trips
{upcomingTrips.length === 0 ? (

No trips in next 2 hours

) : (
{upcomingTrips.map((trip) => { const timeColor = getTimeColor(trip.startTime); const isImminent = new Date(trip.startTime) <= fifteenMinutes; return (
{isImminent && } {formatVipNames(trip)} {trip.title} e.stopPropagation()} className="p-1 rounded hover:bg-primary/10 text-muted-foreground hover:text-primary transition-colors" title="View/Edit Event" >
{trip.pickupLocation || trip.location} → {trip.dropoffLocation || 'TBD'}
{trip.driver ? ( {trip.driver.name} openChat(trip.driver!)} className="ml-0.5" /> ) : ( No Driver )} {trip.vehicle ? ( {trip.vehicle.name} ) : ( No Vehicle )}

{getTimeUntil(trip.startTime)}

{formatTime(new Date(trip.startTime))}

); })}
)}
{/* Bottom Row: Resources */}
{/* VIP Arrivals */}

Arrivals

{upcomingArrivals.length}
{upcomingArrivals.length === 0 ? (

No arrivals soon

) : (
{upcomingArrivals.map((vip) => { // Find this VIP's journey if it exists const journey = journeys.find(j => j.vipId === vip.id); const flight = vip.flights.find(f => f.status?.toLowerCase() !== 'cancelled') || vip.flights[0]; const lastFlight = journey ? journey.flights[journey.flights.length - 1] : flight; const finalArrival = lastFlight ? (lastFlight.actualArrival || lastFlight.estimatedArrival || lastFlight.scheduledArrival) : null; const arrival = vip.expectedArrival || finalArrival; const currentFlight = journey ? journey.flights[journey.currentSegmentIndex] : flight; const delay = currentFlight ? Math.max(currentFlight.arrivalDelay || 0, currentFlight.departureDelay || 0) : 0; const effectiveStatus = journey?.effectiveStatus || currentFlight?.status?.toLowerCase() || 'scheduled'; const isCancelled = effectiveStatus === 'cancelled'; const isActive = effectiveStatus === 'active'; const isLanded = effectiveStatus === 'landed'; const timeColor = isCancelled ? 'text-red-600 dark:text-red-400' : isLanded ? 'text-emerald-600 dark:text-emerald-400' : delay > 15 ? 'text-amber-600 dark:text-amber-400' : isActive ? 'text-purple-600 dark:text-purple-400' : 'text-blue-600 dark:text-blue-400'; const borderColor = journey?.hasLayoverRisk ? 'border-l-orange-500' : isCancelled ? 'border-l-red-500' : delay > 30 ? 'border-l-amber-500' : isActive ? 'border-l-purple-500' : isLanded ? 'border-l-emerald-500' : 'border-l-blue-500'; // Build route chain const routeChain = journey && journey.isMultiSegment ? journey.flights.map(f => f.departureAirport).concat([journey.flights[journey.flights.length - 1].arrivalAirport]).join(' → ') : flight ? `${flight.departureAirport} → ${flight.arrivalAirport}` : ''; return (

{vip.name}

{journey?.hasLayoverRisk && ( risk )} {delay > 15 && !journey?.hasLayoverRisk && ( +{delay}m )} {isCancelled && ( CANCELLED )}
{routeChain} {journey?.isMultiSegment && ( {journey.flights.length} legs )}
{currentFlight && (currentFlight.arrivalTerminal || currentFlight.arrivalGate || currentFlight.arrivalBaggage) && (
{currentFlight.arrivalTerminal && T{currentFlight.arrivalTerminal}} {currentFlight.arrivalGate && Gate {currentFlight.arrivalGate}} {currentFlight.arrivalBaggage && Bag {currentFlight.arrivalBaggage}}
)}

{isCancelled ? '---' : isLanded ? 'Landed' : arrival ? getTimeUntil(arrival) : '--'}

{arrival && !isCancelled && !isLanded && (

{formatTime(new Date(arrival))}

)}
); })}
)}
{/* Vehicles Available */}

Available

{availableVehicles.length}
{availableVehicles.length === 0 ? (

None available

) : (
{availableVehicles.map((vehicle) => (

{vehicle.name}

{vehicle.seatCapacity} seats

))}
)}
{/* Vehicles In Use */}

In Use

{inUseVehicles.length}
{inUseVehicles.length === 0 ? (

All available

) : (
{inUseVehicles.slice(0, 4).map((vehicle) => { const activeEvent = events.find((e) => e.status === 'IN_PROGRESS' && e.vehicle?.id === vehicle.id); return (

{vehicle.name}

{activeEvent?.driver?.name || 'Driver'} {activeEvent?.driver && ( )}

); })}
)}
{/* Active Drivers (GPS) */} {(() => { const TEN_MINUTES = 10 * 60 * 1000; const activeDrivers = (driverLocations || []).filter(loc => loc.lastActive && (Date.now() - new Date(loc.lastActive).getTime()) < TEN_MINUTES && loc.location ); return (

Active Drivers

{activeDrivers.length}
{activeDrivers.length === 0 ? (

No active GPS drivers

) : (
{activeDrivers.map((loc) => ( ))}
)}
{/* Quick Action Links - compact icon row */}
); })()}
{/* Driver Chat Modal */} {/* Driver Location Modal */}
); }