Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.bookovia.com/llms.txt

Use this file to discover all available pages before exploring further.

Web Dashboard Guide

Create powerful web-based fleet management dashboards with real-time vehicle tracking, comprehensive analytics, and interactive reporting using the Bookovia Telematics API and modern web frameworks.

Dashboard Architecture

Component Overview

A comprehensive fleet dashboard typically includes:
  • Real-time Map View - Live vehicle positions and routes
  • Fleet Overview - High-level metrics and KPIs
  • Trip Management - Active and completed trip monitoring
  • Safety Analytics - Driver behavior and incident tracking
  • Reporting System - Historical data and custom reports
  • Alert Management - Real-time notifications and alerts

Technology Stack

FrameworkUse CaseStrengths
ReactModern SPA dashboardsComponent ecosystem, real-time updates
Vue.jsRapid prototypingGentle learning curve, excellent tooling
AngularEnterprise applicationsTypeScript, comprehensive framework
SveltePerformance-critical dashboardsSmall bundle size, reactive updates
Next.jsFull-stack applicationsSSR, API routes, deployment

Getting Started

Project Setup

# Create React app
npx create-react-app bookovia-dashboard
cd bookovia-dashboard

# Install dependencies
npm install @bookovia/javascript-sdk
npm install react-router-dom
npm install react-leaflet leaflet
npm install recharts
npm install @headlessui/react @heroicons/react
npm install tailwindcss

# Start development
npm start
Project Structure
src/
├── components/
│   ├── Map/
│   │   ├── FleetMap.jsx
│   │   └── VehicleMarker.jsx
│   ├── Dashboard/
│   │   ├── Overview.jsx
│   │   ├── Metrics.jsx
│   │   └── LiveFeed.jsx
│   └── Reports/
│       ├── SafetyReport.jsx
│       └── TripReport.jsx
├── hooks/
│   ├── useBookovia.js
│   ├── useRealtime.js
│   └── useFleetData.js
├── services/
│   ├── api.js
│   └── websocket.js
└── pages/
    ├── Dashboard.jsx
    ├── Fleet.jsx
    └── Reports.jsx

SDK Integration

// hooks/useBookovia.js
import { useState, useEffect, useCallback } from 'react';
import Bookovia from '@bookovia/javascript-sdk';

const useBookovia = () => {
  const [client, setClient] = useState(null);
  const [isConnected, setIsConnected] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const initializeClient = async () => {
      try {
        const bookoviaClient = new Bookovia({
          apiKey: process.env.REACT_APP_BOOKOVIA_API_KEY,
          environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox'
        });
        
        await bookoviaClient.initialize();
        setClient(bookoviaClient);
        setIsConnected(true);
        
      } catch (err) {
        setError(err.message);
        console.error('Bookovia initialization failed:', err);
      }
    };
    
    initializeClient();
  }, []);
  
  const apiCall = useCallback(async (method, ...args) => {
    if (!client) throw new Error('Bookovia client not initialized');
    
    try {
      return await client[method](...args);
    } catch (err) {
      setError(err.message);
      throw err;
    }
  }, [client]);
  
  return { client, isConnected, error, apiCall };
};

export default useBookovia;
Real-time Data Hook
// hooks/useRealtime.js
import { useState, useEffect, useRef } from 'react';
import useBookovia from './useBookovia';

const useRealtime = (channels = ['locations', 'safety_events']) => {
  const { client } = useBookovia();
  const [data, setData] = useState({});
  const [connectionStatus, setConnectionStatus] = useState('disconnected');
  const wsRef = useRef(null);
  
  useEffect(() => {
    if (!client) return;
    
    const connectWebSocket = async () => {
      try {
        wsRef.current = await client.streaming.connect();
        
        wsRef.current.onOpen(() => {
          setConnectionStatus('connected');
          
          // Subscribe to channels
          channels.forEach(channel => {
            wsRef.current.subscribe(channel);
          });
        });
        
        wsRef.current.onMessage((message) => {
          setData(prevData => ({
            ...prevData,
            [message.type]: message.data
          }));
        });
        
        wsRef.current.onClose(() => {
          setConnectionStatus('disconnected');
          // Auto-reconnect after 5 seconds
          setTimeout(connectWebSocket, 5000);
        });
        
      } catch (error) {
        console.error('WebSocket connection failed:', error);
        setConnectionStatus('error');
      }
    };
    
    connectWebSocket();
    
    return () => {
      if (wsRef.current) {
        wsRef.current.disconnect();
      }
    };
  }, [client, channels]);
  
  return { data, connectionStatus };
};

export default useRealtime;

Real-time Fleet Map

Interactive Map Component

// components/Map/FleetMap.jsx
import React, { useEffect, useState } from 'react';
import { MapContainer, TileLayer, Marker, Popup, Polyline } from 'react-leaflet';
import L from 'leaflet';
import useRealtime from '../../hooks/useRealtime';
import useBookovia from '../../hooks/useBookovia';

// Custom vehicle icon
const vehicleIcon = new L.Icon({
  iconUrl: '/icons/vehicle-marker.png',
  iconSize: [32, 32],
  iconAnchor: [16, 16],
  popupAnchor: [0, -16]
});

const FleetMap = () => {
  const { apiCall } = useBookovia();
  const { data: realtimeData } = useRealtime(['locations', 'trip_updates']);
  const [vehicles, setVehicles] = useState([]);
  const [routes, setRoutes] = useState({});
  const [selectedVehicle, setSelectedVehicle] = useState(null);
  
  // Fetch initial fleet data
  useEffect(() => {
    const fetchFleetData = async () => {
      try {
        const fleetData = await apiCall('fleet.getVehicles', {
          include_location: true,
          status: 'active'
        });
        setVehicles(fleetData.vehicles);
      } catch (error) {
        console.error('Failed to fetch fleet data:', error);
      }
    };
    
    fetchFleetData();
    // Refresh every 30 seconds
    const interval = setInterval(fetchFleetData, 30000);
    
    return () => clearInterval(interval);
  }, [apiCall]);
  
  // Update vehicles with real-time location data
  useEffect(() => {
    if (realtimeData.location_update) {
      const locationUpdate = realtimeData.location_update;
      
      setVehicles(prevVehicles =>
        prevVehicles.map(vehicle =>
          vehicle.id === locationUpdate.vehicle_id
            ? {
                ...vehicle,
                last_location: locationUpdate.location,
                last_update: locationUpdate.timestamp
              }
            : vehicle
        )
      );
    }
  }, [realtimeData.location_update]);
  
  // Fetch route for selected vehicle
  const fetchVehicleRoute = async (vehicleId, tripId) => {
    if (routes[tripId]) return; // Already loaded
    
    try {
      const route = await apiCall('trips.getRoute', { trip_id: tripId });
      setRoutes(prevRoutes => ({
        ...prevRoutes,
        [tripId]: route.coordinates
      }));
    } catch (error) {
      console.error('Failed to fetch route:', error);
    }
  };
  
  const handleVehicleClick = (vehicle) => {
    setSelectedVehicle(vehicle);
    if (vehicle.current_trip_id) {
      fetchVehicleRoute(vehicle.id, vehicle.current_trip_id);
    }
  };
  
  return (
    <div className="relative h-full w-full">
      <MapContainer
        center={[40.7128, -74.0060]}
        zoom={12}
        className="h-full w-full"
      >
        <TileLayer
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        />
        
        {/* Vehicle markers */}
        {vehicles.map(vehicle => (
          <Marker
            key={vehicle.id}
            position={[
              vehicle.last_location?.latitude || 0,
              vehicle.last_location?.longitude || 0
            ]}
            icon={vehicleIcon}
            eventHandlers={{
              click: () => handleVehicleClick(vehicle)
            }}
          >
            <Popup>
              <VehiclePopup vehicle={vehicle} />
            </Popup>
          </Marker>
        ))}
        
        {/* Route polylines */}
        {Object.entries(routes).map(([tripId, coordinates]) => (
          <Polyline
            key={tripId}
            positions={coordinates}
            color="blue"
            weight={3}
            opacity={0.7}
          />
        ))}
      </MapContainer>
      
      {/* Map controls */}
      <MapControls
        vehicles={vehicles}
        selectedVehicle={selectedVehicle}
        onVehicleSelect={setSelectedVehicle}
      />
    </div>
  );
};

const VehiclePopup = ({ vehicle }) => (
  <div className="p-2 min-w-[200px]">
    <h3 className="font-semibold text-lg">{vehicle.name}</h3>
    <div className="space-y-1 text-sm">
      <p><strong>Status:</strong> {vehicle.status}</p>
      <p><strong>Driver:</strong> {vehicle.driver_name || 'Unassigned'}</p>
      <p><strong>Speed:</strong> {vehicle.last_location?.speed || 0} km/h</p>
      <p><strong>Last Update:</strong> {new Date(vehicle.last_update).toLocaleTimeString()}</p>
      {vehicle.current_trip_id && (
        <p><strong>Trip ID:</strong> {vehicle.current_trip_id}</p>
      )}
    </div>
  </div>
);

const MapControls = ({ vehicles, selectedVehicle, onVehicleSelect }) => {
  return (
    <div className="absolute top-4 left-4 z-1000 bg-white rounded-lg shadow-lg p-4 max-w-sm">
      <h3 className="font-semibold mb-2">Fleet Overview</h3>
      <div className="space-y-2 max-h-64 overflow-y-auto">
        {vehicles.map(vehicle => (
          <div
            key={vehicle.id}
            className={`p-2 rounded cursor-pointer ${
              selectedVehicle?.id === vehicle.id
                ? 'bg-blue-100 border border-blue-300'
                : 'bg-gray-50 hover:bg-gray-100'
            }`}
            onClick={() => onVehicleSelect(vehicle)}
          >
            <div className="flex justify-between items-center">
              <span className="font-medium">{vehicle.name}</span>
              <span className={`px-2 py-1 rounded text-xs ${
                vehicle.status === 'active' ? 'bg-green-200 text-green-800' :
                vehicle.status === 'idle' ? 'bg-yellow-200 text-yellow-800' :
                'bg-gray-200 text-gray-800'
              }`}>
                {vehicle.status}
              </span>
            </div>
            <div className="text-sm text-gray-600">
              {vehicle.driver_name || 'No driver assigned'}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

export default FleetMap;

Dashboard Overview

KPI Metrics Component

// components/Dashboard/Overview.jsx
import React, { useState, useEffect } from 'react';
import { 
  TruckIcon, 
  UserGroupIcon, 
  ExclamationTriangleIcon,
  ChartBarIcon 
} from '@heroicons/react/24/outline';
import useBookovia from '../../hooks/useBookovia';
import useRealtime from '../../hooks/useRealtime';

const DashboardOverview = () => {
  const { apiCall } = useBookovia();
  const { data: realtimeData } = useRealtime(['safety_events', 'trip_updates']);
  const [metrics, setMetrics] = useState({
    totalVehicles: 0,
    activeTrips: 0,
    totalDrivers: 0,
    safetyScore: 0,
    todayDistance: 0,
    activeSafetyAlerts: 0
  });
  
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchDashboardData = async () => {
      try {
        setLoading(true);
        
        // Fetch multiple endpoints in parallel
        const [fleetOverview, safetyAnalytics, tripMetrics] = await Promise.all([
          apiCall('fleet.getOverview'),
          apiCall('analytics.getSafetyOverview', { period: 'today' }),
          apiCall('analytics.getTripMetrics', { period: 'today' })
        ]);
        
        setMetrics({
          totalVehicles: fleetOverview.total_vehicles,
          activeTrips: fleetOverview.active_trips,
          totalDrivers: fleetOverview.total_drivers,
          safetyScore: safetyAnalytics.average_score,
          todayDistance: tripMetrics.total_distance,
          activeSafetyAlerts: safetyAnalytics.active_alerts
        });
        
      } catch (error) {
        console.error('Failed to fetch dashboard data:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchDashboardData();
    
    // Refresh every minute
    const interval = setInterval(fetchDashboardData, 60000);
    return () => clearInterval(interval);
  }, [apiCall]);
  
  // Update metrics with real-time data
  useEffect(() => {
    if (realtimeData.safety_event) {
      setMetrics(prev => ({
        ...prev,
        activeSafetyAlerts: prev.activeSafetyAlerts + 1
      }));
    }
  }, [realtimeData.safety_event]);
  
  const metricCards = [
    {
      title: 'Total Vehicles',
      value: metrics.totalVehicles,
      icon: TruckIcon,
      color: 'blue',
      change: '+2 this week'
    },
    {
      title: 'Active Trips',
      value: metrics.activeTrips,
      icon: ChartBarIcon,
      color: 'green',
      change: `${metrics.activeTrips} in progress`
    },
    {
      title: 'Safety Score',
      value: `${Math.round(metrics.safetyScore)}/100`,
      icon: ExclamationTriangleIcon,
      color: metrics.safetyScore > 80 ? 'green' : metrics.safetyScore > 60 ? 'yellow' : 'red',
      change: metrics.safetyScore > 80 ? 'Excellent' : 'Needs attention'
    },
    {
      title: 'Total Drivers',
      value: metrics.totalDrivers,
      icon: UserGroupIcon,
      color: 'purple',
      change: `${Math.round(metrics.totalDrivers * 0.8)} active today`
    }
  ];
  
  if (loading) {
    return (
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
        {[...Array(4)].map((_, i) => (
          <div key={i} className="bg-white rounded-lg shadow p-6 animate-pulse">
            <div className="h-4 bg-gray-200 rounded mb-4"></div>
            <div className="h-8 bg-gray-200 rounded mb-2"></div>
            <div className="h-3 bg-gray-200 rounded w-1/2"></div>
          </div>
        ))}
      </div>
    );
  }
  
  return (
    <div className="space-y-6">
      {/* KPI Cards */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
        {metricCards.map((metric, index) => (
          <MetricCard key={index} {...metric} />
        ))}
      </div>
      
      {/* Today's Summary */}
      <div className="bg-white rounded-lg shadow p-6">
        <h3 className="text-lg font-semibold mb-4">Today's Summary</h3>
        <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
          <div className="text-center">
            <div className="text-2xl font-bold text-blue-600">
              {Math.round(metrics.todayDistance)} km
            </div>
            <div className="text-gray-500">Total Distance</div>
          </div>
          <div className="text-center">
            <div className="text-2xl font-bold text-green-600">
              {Math.round(metrics.todayDistance / (metrics.activeTrips || 1))} km
            </div>
            <div className="text-gray-500">Avg Trip Distance</div>
          </div>
          <div className="text-center">
            <div className={`text-2xl font-bold ${
              metrics.activeSafetyAlerts === 0 ? 'text-green-600' : 'text-red-600'
            }`}>
              {metrics.activeSafetyAlerts}
            </div>
            <div className="text-gray-500">Active Alerts</div>
          </div>
        </div>
      </div>
    </div>
  );
};

const MetricCard = ({ title, value, icon: Icon, color, change }) => {
  const colorClasses = {
    blue: 'text-blue-600 bg-blue-100',
    green: 'text-green-600 bg-green-100',
    yellow: 'text-yellow-600 bg-yellow-100',
    red: 'text-red-600 bg-red-100',
    purple: 'text-purple-600 bg-purple-100'
  };
  
  return (
    <div className="bg-white rounded-lg shadow p-6">
      <div className="flex items-center justify-between">
        <div className="flex-1">
          <p className="text-sm font-medium text-gray-500">{title}</p>
          <p className="text-2xl font-semibold text-gray-900">{value}</p>
          <p className="text-xs text-gray-400 mt-1">{change}</p>
        </div>
        <div className={`p-3 rounded-lg ${colorClasses[color]}`}>
          <Icon className="w-6 h-6" />
        </div>
      </div>
    </div>
  );
};

export default DashboardOverview;

Safety Analytics Dashboard

Safety Metrics and Charts

// components/Dashboard/SafetyAnalytics.jsx
import React, { useState, useEffect } from 'react';
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  PieChart,
  Pie,
  Cell,
  BarChart,
  Bar,
  ResponsiveContainer
} from 'recharts';
import useBookovia from '../../hooks/useBookovia';
import useRealtime from '../../hooks/useRealtime';

const SafetyAnalytics = () => {
  const { apiCall } = useBookovia();
  const { data: realtimeData } = useRealtime(['safety_events']);
  
  const [safetyData, setSafetyData] = useState({
    scoreHistory: [],
    eventDistribution: [],
    driverRankings: [],
    recentEvents: []
  });
  
  const [timeRange, setTimeRange] = useState('7d');
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchSafetyData = async () => {
      try {
        setLoading(true);
        
        const [scoreHistory, eventStats, driverRankings, recentEvents] = await Promise.all([
          apiCall('analytics.getSafetyScoreHistory', { 
            period: timeRange,
            granularity: 'daily'
          }),
          apiCall('analytics.getSafetyEventDistribution', { 
            period: timeRange 
          }),
          apiCall('analytics.getDriverRankings', { 
            metric: 'safety_score',
            period: timeRange,
            limit: 10
          }),
          apiCall('analytics.getRecentSafetyEvents', { 
            limit: 20,
            severity: ['medium', 'high', 'critical']
          })
        ]);
        
        setSafetyData({
          scoreHistory: scoreHistory.data,
          eventDistribution: eventStats.data,
          driverRankings: driverRankings.data,
          recentEvents: recentEvents.data
        });
        
      } catch (error) {
        console.error('Failed to fetch safety data:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchSafetyData();
  }, [apiCall, timeRange]);
  
  // Add new safety events to recent events
  useEffect(() => {
    if (realtimeData.safety_event) {
      setSafetyData(prev => ({
        ...prev,
        recentEvents: [realtimeData.safety_event, ...prev.recentEvents.slice(0, 19)]
      }));
    }
  }, [realtimeData.safety_event]);
  
  const COLORS = ['#10B981', '#F59E0B', '#EF4444', '#8B5CF6'];
  
  if (loading) {
    return <div className="animate-pulse">Loading safety analytics...</div>;
  }
  
  return (
    <div className="space-y-6">
      {/* Time Range Selector */}
      <div className="flex justify-between items-center">
        <h2 className="text-2xl font-bold">Safety Analytics</h2>
        <div className="flex space-x-2">
          {['1d', '7d', '30d', '90d'].map(range => (
            <button
              key={range}
              onClick={() => setTimeRange(range)}
              className={`px-4 py-2 rounded ${
                timeRange === range
                  ? 'bg-blue-600 text-white'
                  : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
              }`}
            >
              {range.replace('d', ' days')}
            </button>
          ))}
        </div>
      </div>
      
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
        {/* Safety Score Trend */}
        <div className="bg-white p-6 rounded-lg shadow">
          <h3 className="text-lg font-semibold mb-4">Safety Score Trend</h3>
          <ResponsiveContainer width="100%" height={300}>
            <LineChart data={safetyData.scoreHistory}>
              <CartesianGrid strokeDasharray="3 3" />
              <XAxis 
                dataKey="date" 
                tickFormatter={(value) => new Date(value).toLocaleDateString()}
              />
              <YAxis domain={[0, 100]} />
              <Tooltip 
                labelFormatter={(value) => new Date(value).toLocaleDateString()}
                formatter={(value) => [`${value}`, 'Safety Score']}
              />
              <Legend />
              <Line 
                type="monotone" 
                dataKey="average_score" 
                stroke="#10B981" 
                strokeWidth={2}
                name="Fleet Average"
              />
              <Line 
                type="monotone" 
                dataKey="top_10_percent" 
                stroke="#3B82F6" 
                strokeWidth={2}
                strokeDasharray="5 5"
                name="Top 10%"
              />
            </LineChart>
          </ResponsiveContainer>
        </div>
        
        {/* Event Distribution */}
        <div className="bg-white p-6 rounded-lg shadow">
          <h3 className="text-lg font-semibold mb-4">Safety Event Distribution</h3>
          <ResponsiveContainer width="100%" height={300}>
            <PieChart>
              <Pie
                data={safetyData.eventDistribution}
                cx="50%"
                cy="50%"
                labelLine={false}
                label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
                outerRadius={80}
                fill="#8884d8"
                dataKey="count"
              >
                {safetyData.eventDistribution.map((entry, index) => (
                  <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
                ))}
              </Pie>
              <Tooltip />
            </PieChart>
          </ResponsiveContainer>
        </div>
      </div>
      
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
        {/* Driver Rankings */}
        <div className="bg-white p-6 rounded-lg shadow">
          <h3 className="text-lg font-semibold mb-4">Top Drivers by Safety Score</h3>
          <ResponsiveContainer width="100%" height={300}>
            <BarChart data={safetyData.driverRankings} layout="horizontal">
              <CartesianGrid strokeDasharray="3 3" />
              <XAxis type="number" domain={[0, 100]} />
              <YAxis type="category" dataKey="driver_name" width={100} />
              <Tooltip 
                formatter={(value) => [`${value}`, 'Safety Score']}
              />
              <Bar dataKey="safety_score" fill="#10B981" />
            </BarChart>
          </ResponsiveContainer>
        </div>
        
        {/* Recent Safety Events */}
        <div className="bg-white p-6 rounded-lg shadow">
          <h3 className="text-lg font-semibold mb-4">Recent Safety Events</h3>
          <div className="space-y-3 max-h-72 overflow-y-auto">
            {safetyData.recentEvents.map((event, index) => (
              <SafetyEventItem key={index} event={event} />
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

const SafetyEventItem = ({ event }) => {
  const severityColors = {
    low: 'bg-green-100 text-green-800',
    medium: 'bg-yellow-100 text-yellow-800',
    high: 'bg-orange-100 text-orange-800',
    critical: 'bg-red-100 text-red-800'
  };
  
  const eventIcons = {
    harsh_braking: '🛑',
    harsh_acceleration: '🚀',
    harsh_cornering: '↪️',
    speeding: '💨',
    phone_usage: '📱'
  };
  
  return (
    <div className="flex items-center justify-between p-3 bg-gray-50 rounded">
      <div className="flex items-center space-x-3">
        <span className="text-xl">{eventIcons[event.type] || '⚠️'}</span>
        <div>
          <div className="font-medium">{event.driver_name}</div>
          <div className="text-sm text-gray-500">
            {event.type.replace('_', ' ')}{new Date(event.timestamp).toLocaleTimeString()}
          </div>
        </div>
      </div>
      <span className={`px-2 py-1 rounded text-xs font-medium ${severityColors[event.severity]}`}>
        {event.severity}
      </span>
    </div>
  );
};

export default SafetyAnalytics;

Real-time Notifications

Alert System

// components/Notifications/AlertSystem.jsx
import React, { useState, useEffect } from 'react';
import { 
  XMarkIcon, 
  ExclamationTriangleIcon,
  InformationCircleIcon,
  CheckCircleIcon 
} from '@heroicons/react/24/outline';
import useRealtime from '../../hooks/useRealtime';

const AlertSystem = () => {
  const { data: realtimeData } = useRealtime(['safety_events', 'emergency_alerts', 'system_notifications']);
  const [alerts, setAlerts] = useState([]);
  const [filter, setFilter] = useState('all'); // all, critical, high, medium
  
  useEffect(() => {
    // Process new safety events
    if (realtimeData.safety_event) {
      const alert = {
        id: `safety_${Date.now()}`,
        type: 'safety',
        severity: realtimeData.safety_event.severity,
        title: `${realtimeData.safety_event.type.replace('_', ' ')} detected`,
        message: `Driver: ${realtimeData.safety_event.driver_name} - Vehicle: ${realtimeData.safety_event.vehicle_id}`,
        timestamp: new Date(),
        data: realtimeData.safety_event,
        actions: [
          { label: 'View Details', action: 'view_details' },
          { label: 'Contact Driver', action: 'contact_driver' }
        ]
      };
      
      addAlert(alert);
    }
    
    // Process emergency alerts
    if (realtimeData.emergency_alert) {
      const alert = {
        id: `emergency_${Date.now()}`,
        type: 'emergency',
        severity: 'critical',
        title: 'Emergency Alert',
        message: realtimeData.emergency_alert.message,
        timestamp: new Date(),
        data: realtimeData.emergency_alert,
        actions: [
          { label: 'Dispatch Emergency Services', action: 'dispatch_emergency' },
          { label: 'Contact Driver', action: 'contact_driver' },
          { label: 'View Location', action: 'view_location' }
        ]
      };
      
      addAlert(alert);
      
      // Play alert sound for critical alerts
      playAlertSound('critical');
    }
    
    // Process system notifications
    if (realtimeData.system_notification) {
      const alert = {
        id: `system_${Date.now()}`,
        type: 'system',
        severity: 'info',
        title: realtimeData.system_notification.title,
        message: realtimeData.system_notification.message,
        timestamp: new Date(),
        data: realtimeData.system_notification
      };
      
      addAlert(alert);
    }
  }, [realtimeData]);
  
  const addAlert = (alert) => {
    setAlerts(prev => [alert, ...prev.slice(0, 49)]); // Keep last 50 alerts
    
    // Auto-dismiss info alerts after 10 seconds
    if (alert.severity === 'info') {
      setTimeout(() => {
        dismissAlert(alert.id);
      }, 10000);
    }
  };
  
  const dismissAlert = (alertId) => {
    setAlerts(prev => prev.filter(alert => alert.id !== alertId));
  };
  
  const handleAlertAction = async (alert, actionType) => {
    switch (actionType) {
      case 'view_details':
        // Navigate to detailed view
        window.open(`/safety-events/${alert.data.id}`, '_blank');
        break;
        
      case 'contact_driver':
        // Initiate driver contact
        if (alert.data.driver_phone) {
          window.open(`tel:${alert.data.driver_phone}`);
        }
        break;
        
      case 'dispatch_emergency':
        // Trigger emergency dispatch
        await dispatchEmergencyServices(alert.data);
        break;
        
      case 'view_location':
        // Show location on map
        showLocationOnMap(alert.data.location);
        break;
    }
    
    // Mark alert as acknowledged
    setAlerts(prev => 
      prev.map(a => 
        a.id === alert.id 
          ? { ...a, acknowledged: true }
          : a
      )
    );
  };
  
  const playAlertSound = (severity) => {
    if ('Notification' in window) {
      new Notification(`${severity.toUpperCase()} Alert`, {
        body: 'Check the dashboard for details',
        icon: '/icons/alert.png'
      });
    }
    
    // Play different sounds based on severity
    const audioMap = {
      critical: '/sounds/critical-alert.mp3',
      high: '/sounds/high-alert.mp3',
      medium: '/sounds/medium-alert.mp3'
    };
    
    if (audioMap[severity]) {
      const audio = new Audio(audioMap[severity]);
      audio.play().catch(e => console.log('Could not play alert sound:', e));
    }
  };
  
  const filteredAlerts = alerts.filter(alert => {
    if (filter === 'all') return true;
    return alert.severity === filter;
  });
  
  const alertCounts = {
    all: alerts.length,
    critical: alerts.filter(a => a.severity === 'critical').length,
    high: alerts.filter(a => a.severity === 'high').length,
    medium: alerts.filter(a => a.severity === 'medium').length
  };
  
  return (
    <div className="bg-white rounded-lg shadow">
      {/* Header */}
      <div className="p-4 border-b">
        <div className="flex justify-between items-center">
          <h3 className="text-lg font-semibold">Live Alerts</h3>
          <div className="flex space-x-2">
            {Object.entries(alertCounts).map(([severity, count]) => (
              <button
                key={severity}
                onClick={() => setFilter(severity)}
                className={`px-3 py-1 rounded text-sm ${
                  filter === severity
                    ? getSeverityButtonClass(severity, true)
                    : getSeverityButtonClass(severity, false)
                }`}
              >
                {severity} ({count})
              </button>
            ))}
          </div>
        </div>
      </div>
      
      {/* Alerts List */}
      <div className="max-h-96 overflow-y-auto">
        {filteredAlerts.length === 0 ? (
          <div className="p-8 text-center text-gray-500">
            <InformationCircleIcon className="w-12 h-12 mx-auto mb-2 text-gray-400" />
            <p>No alerts to display</p>
          </div>
        ) : (
          <div className="divide-y">
            {filteredAlerts.map(alert => (
              <AlertItem
                key={alert.id}
                alert={alert}
                onDismiss={dismissAlert}
                onAction={handleAlertAction}
              />
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

const AlertItem = ({ alert, onDismiss, onAction }) => {
  const getSeverityIcon = (severity) => {
    switch (severity) {
      case 'critical':
        return <ExclamationTriangleIcon className="w-5 h-5 text-red-500" />;
      case 'high':
        return <ExclamationTriangleIcon className="w-5 h-5 text-orange-500" />;
      case 'medium':
        return <ExclamationTriangleIcon className="w-5 h-5 text-yellow-500" />;
      default:
        return <InformationCircleIcon className="w-5 h-5 text-blue-500" />;
    }
  };
  
  const getSeverityBg = (severity) => {
    switch (severity) {
      case 'critical':
        return 'bg-red-50 border-l-red-500';
      case 'high':
        return 'bg-orange-50 border-l-orange-500';
      case 'medium':
        return 'bg-yellow-50 border-l-yellow-500';
      default:
        return 'bg-blue-50 border-l-blue-500';
    }
  };
  
  return (
    <div className={`p-4 border-l-4 ${getSeverityBg(alert.severity)} ${
      alert.acknowledged ? 'opacity-60' : ''
    }`}>
      <div className="flex justify-between items-start">
        <div className="flex items-start space-x-3 flex-1">
          {getSeverityIcon(alert.severity)}
          <div className="flex-1">
            <div className="flex justify-between items-center mb-1">
              <h4 className="font-medium text-gray-900">{alert.title}</h4>
              <span className="text-xs text-gray-500">
                {alert.timestamp.toLocaleTimeString()}
              </span>
            </div>
            <p className="text-sm text-gray-600 mb-2">{alert.message}</p>
            
            {/* Action buttons */}
            {alert.actions && (
              <div className="flex flex-wrap gap-2">
                {alert.actions.map((action, index) => (
                  <button
                    key={index}
                    onClick={() => onAction(alert, action.action)}
                    className="text-xs bg-gray-100 hover:bg-gray-200 px-2 py-1 rounded"
                  >
                    {action.label}
                  </button>
                ))}
              </div>
            )}
          </div>
        </div>
        
        {/* Dismiss button */}
        <button
          onClick={() => onDismiss(alert.id)}
          className="ml-3 text-gray-400 hover:text-gray-600"
        >
          <XMarkIcon className="w-4 h-4" />
        </button>
      </div>
    </div>
  );
};

const getSeverityButtonClass = (severity, active) => {
  const baseClass = active ? 'text-white' : 'text-gray-600 bg-gray-100 hover:bg-gray-200';
  
  if (!active) return baseClass;
  
  switch (severity) {
    case 'all':
      return 'bg-gray-600 text-white';
    case 'critical':
      return 'bg-red-600 text-white';
    case 'high':
      return 'bg-orange-600 text-white';
    case 'medium':
      return 'bg-yellow-600 text-white';
    default:
      return 'bg-blue-600 text-white';
  }
};

const dispatchEmergencyServices = async (alertData) => {
  // Implementation would integrate with emergency services API
  console.log('Dispatching emergency services for:', alertData);
};

const showLocationOnMap = (location) => {
  // Implementation would focus map on location
  console.log('Showing location on map:', location);
};

export default AlertSystem;

Reports and Analytics

Custom Report Builder

// components/Reports/ReportBuilder.jsx
import React, { useState, useEffect } from 'react';
import { 
  CalendarIcon,
  DocumentArrowDownIcon,
  ChartBarIcon,
  Cog6ToothIcon
} from '@heroicons/react/24/outline';
import useBookovia from '../../hooks/useBookovia';

const ReportBuilder = () => {
  const { apiCall } = useBookovia();
  
  const [reportConfig, setReportConfig] = useState({
    type: 'safety_summary',
    dateRange: {
      start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
      end: new Date().toISOString().split('T')[0]
    },
    filters: {
      vehicles: [],
      drivers: [],
      regions: []
    },
    metrics: ['safety_score', 'trip_count', 'distance', 'fuel_consumption'],
    format: 'pdf',
    groupBy: 'driver',
    includeCharts: true
  });
  
  const [availableOptions, setAvailableOptions] = useState({
    vehicles: [],
    drivers: [],
    regions: []
  });
  
  const [generating, setGenerating] = useState(false);
  const [reportPreview, setReportPreview] = useState(null);
  
  const reportTypes = [
    { value: 'safety_summary', label: 'Safety Summary Report' },
    { value: 'trip_analysis', label: 'Trip Analysis Report' },
    { value: 'driver_performance', label: 'Driver Performance Report' },
    { value: 'fleet_utilization', label: 'Fleet Utilization Report' },
    { value: 'fuel_efficiency', label: 'Fuel Efficiency Report' },
    { value: 'compliance', label: 'Compliance Report' }
  ];
  
  const metrics = [
    { value: 'safety_score', label: 'Safety Score' },
    { value: 'trip_count', label: 'Trip Count' },
    { value: 'distance', label: 'Total Distance' },
    { value: 'duration', label: 'Total Duration' },
    { value: 'fuel_consumption', label: 'Fuel Consumption' },
    { value: 'harsh_events', label: 'Harsh Events' },
    { value: 'idle_time', label: 'Idle Time' },
    { value: 'speed_violations', label: 'Speed Violations' }
  ];
  
  useEffect(() => {
    const fetchOptions = async () => {
      try {
        const [vehicles, drivers, regions] = await Promise.all([
          apiCall('fleet.getVehicles'),
          apiCall('fleet.getDrivers'),
          apiCall('fleet.getRegions')
        ]);
        
        setAvailableOptions({
          vehicles: vehicles.data,
          drivers: drivers.data,
          regions: regions.data
        });
      } catch (error) {
        console.error('Failed to fetch report options:', error);
      }
    };
    
    fetchOptions();
  }, [apiCall]);
  
  const handleConfigChange = (field, value) => {
    setReportConfig(prev => ({
      ...prev,
      [field]: value
    }));
  };
  
  const handleFilterChange = (filterType, values) => {
    setReportConfig(prev => ({
      ...prev,
      filters: {
        ...prev.filters,
        [filterType]: values
      }
    }));
  };
  
  const generateReport = async () => {
    setGenerating(true);
    
    try {
      const response = await apiCall('reports.generate', {
        ...reportConfig,
        async: true // Generate asynchronously for large reports
      });
      
      if (response.async) {
        // Poll for completion
        const reportId = response.report_id;
        let completed = false;
        
        while (!completed) {
          await new Promise(resolve => setTimeout(resolve, 2000));
          
          const status = await apiCall('reports.getStatus', { report_id: reportId });
          
          if (status.status === 'completed') {
            completed = true;
            
            if (reportConfig.format === 'pdf') {
              // Download PDF
              const blob = await apiCall('reports.download', { 
                report_id: reportId,
                format: 'pdf'
              });
              downloadFile(blob, `${reportConfig.type}_${Date.now()}.pdf`);
            } else {
              // Show preview for other formats
              const data = await apiCall('reports.getData', { report_id: reportId });
              setReportPreview(data);
            }
          } else if (status.status === 'failed') {
            throw new Error(status.error || 'Report generation failed');
          }
        }
      } else {
        // Immediate response for small reports
        setReportPreview(response.data);
      }
      
    } catch (error) {
      console.error('Report generation failed:', error);
      alert('Failed to generate report: ' + error.message);
    } finally {
      setGenerating(false);
    }
  };
  
  const downloadFile = (blob, filename) => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    document.body.removeChild(a);
  };
  
  const scheduleReport = async () => {
    try {
      await apiCall('reports.schedule', {
        ...reportConfig,
        schedule: {
          frequency: 'weekly',
          day_of_week: 1, // Monday
          time: '09:00',
          recipients: ['fleet@company.com']
        }
      });
      
      alert('Report scheduled successfully!');
    } catch (error) {
      console.error('Failed to schedule report:', error);
    }
  };
  
  return (
    <div className="space-y-6">
      <div className="bg-white rounded-lg shadow p-6">
        <h2 className="text-2xl font-bold mb-6">Custom Report Builder</h2>
        
        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
          {/* Report Configuration */}
          <div className="space-y-4">
            <h3 className="text-lg font-semibold">Report Configuration</h3>
            
            {/* Report Type */}
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Report Type
              </label>
              <select
                value={reportConfig.type}
                onChange={(e) => handleConfigChange('type', e.target.value)}
                className="w-full p-2 border border-gray-300 rounded-md"
              >
                {reportTypes.map(type => (
                  <option key={type.value} value={type.value}>
                    {type.label}
                  </option>
                ))}
              </select>
            </div>
            
            {/* Date Range */}
            <div className="grid grid-cols-2 gap-4">
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Start Date
                </label>
                <input
                  type="date"
                  value={reportConfig.dateRange.start}
                  onChange={(e) => handleConfigChange('dateRange', {
                    ...reportConfig.dateRange,
                    start: e.target.value
                  })}
                  className="w-full p-2 border border-gray-300 rounded-md"
                />
              </div>
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  End Date
                </label>
                <input
                  type="date"
                  value={reportConfig.dateRange.end}
                  onChange={(e) => handleConfigChange('dateRange', {
                    ...reportConfig.dateRange,
                    end: e.target.value
                  })}
                  className="w-full p-2 border border-gray-300 rounded-md"
                />
              </div>
            </div>
            
            {/* Metrics Selection */}
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Metrics to Include
              </label>
              <div className="space-y-2 max-h-32 overflow-y-auto border border-gray-200 rounded p-2">
                {metrics.map(metric => (
                  <label key={metric.value} className="flex items-center">
                    <input
                      type="checkbox"
                      checked={reportConfig.metrics.includes(metric.value)}
                      onChange={(e) => {
                        const newMetrics = e.target.checked
                          ? [...reportConfig.metrics, metric.value]
                          : reportConfig.metrics.filter(m => m !== metric.value);
                        handleConfigChange('metrics', newMetrics);
                      }}
                      className="mr-2"
                    />
                    {metric.label}
                  </label>
                ))}
              </div>
            </div>
            
            {/* Format and Options */}
            <div className="grid grid-cols-2 gap-4">
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Format
                </label>
                <select
                  value={reportConfig.format}
                  onChange={(e) => handleConfigChange('format', e.target.value)}
                  className="w-full p-2 border border-gray-300 rounded-md"
                >
                  <option value="pdf">PDF</option>
                  <option value="excel">Excel</option>
                  <option value="csv">CSV</option>
                  <option value="json">JSON</option>
                </select>
              </div>
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Group By
                </label>
                <select
                  value={reportConfig.groupBy}
                  onChange={(e) => handleConfigChange('groupBy', e.target.value)}
                  className="w-full p-2 border border-gray-300 rounded-md"
                >
                  <option value="driver">Driver</option>
                  <option value="vehicle">Vehicle</option>
                  <option value="region">Region</option>
                  <option value="date">Date</option>
                </select>
              </div>
            </div>
            
            {/* Include Charts */}
            <div>
              <label className="flex items-center">
                <input
                  type="checkbox"
                  checked={reportConfig.includeCharts}
                  onChange={(e) => handleConfigChange('includeCharts', e.target.checked)}
                  className="mr-2"
                />
                Include Charts and Visualizations
              </label>
            </div>
          </div>
          
          {/* Filters */}
          <div className="space-y-4">
            <h3 className="text-lg font-semibold">Filters</h3>
            
            {/* Vehicle Filter */}
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Vehicles (leave empty for all)
              </label>
              <select
                multiple
                value={reportConfig.filters.vehicles}
                onChange={(e) => handleFilterChange('vehicles', 
                  Array.from(e.target.selectedOptions, option => option.value)
                )}
                className="w-full p-2 border border-gray-300 rounded-md h-24"
              >
                {availableOptions.vehicles.map(vehicle => (
                  <option key={vehicle.id} value={vehicle.id}>
                    {vehicle.name}
                  </option>
                ))}
              </select>
            </div>
            
            {/* Driver Filter */}
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                Drivers (leave empty for all)
              </label>
              <select
                multiple
                value={reportConfig.filters.drivers}
                onChange={(e) => handleFilterChange('drivers', 
                  Array.from(e.target.selectedOptions, option => option.value)
                )}
                className="w-full p-2 border border-gray-300 rounded-md h-24"
              >
                {availableOptions.drivers.map(driver => (
                  <option key={driver.id} value={driver.id}>
                    {driver.name}
                  </option>
                ))}
              </select>
            </div>
          </div>
        </div>
        
        {/* Action Buttons */}
        <div className="mt-6 flex flex-wrap gap-4">
          <button
            onClick={generateReport}
            disabled={generating}
            className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
          >
            <DocumentArrowDownIcon className="w-4 h-4 mr-2" />
            {generating ? 'Generating...' : 'Generate Report'}
          </button>
          
          <button
            onClick={scheduleReport}
            className="flex items-center px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700"
          >
            <CalendarIcon className="w-4 h-4 mr-2" />
            Schedule Report
          </button>
          
          <button
            onClick={() => setReportPreview(null)}
            className="flex items-center px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700"
          >
            <ChartBarIcon className="w-4 h-4 mr-2" />
            Preview Data
          </button>
        </div>
      </div>
      
      {/* Report Preview */}
      {reportPreview && (
        <div className="bg-white rounded-lg shadow p-6">
          <h3 className="text-lg font-semibold mb-4">Report Preview</h3>
          <ReportPreview data={reportPreview} config={reportConfig} />
        </div>
      )}
    </div>
  );
};

const ReportPreview = ({ data, config }) => {
  return (
    <div className="space-y-4">
      <div className="text-sm text-gray-600">
        Report Type: {config.type} | 
        Date Range: {config.dateRange.start} to {config.dateRange.end} |
        Records: {data.length}
      </div>
      
      <div className="overflow-x-auto">
        <table className="min-w-full table-auto">
          <thead>
            <tr className="bg-gray-50">
              {config.metrics.map(metric => (
                <th key={metric} className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                  {metric.replace('_', ' ')}
                </th>
              ))}
            </tr>
          </thead>
          <tbody className="bg-white divide-y divide-gray-200">
            {data.slice(0, 10).map((row, index) => (
              <tr key={index}>
                {config.metrics.map(metric => (
                  <td key={metric} className="px-4 py-2 whitespace-nowrap text-sm text-gray-900">
                    {row[metric] || '-'}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      
      {data.length > 10 && (
        <div className="text-sm text-gray-500 text-center">
          Showing first 10 of {data.length} records
        </div>
      )}
    </div>
  );
};

export default ReportBuilder;

Deployment and Performance

Production Optimization

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
  },
  
  // Enable compression
  compress: true,
  
  // Optimize images
  images: {
    domains: ['api.bookovia.com'],
    formats: ['image/avif', 'image/webp'],
  },
  
  // Environment variables
  env: {
    BOOKOVIA_API_URL: process.env.BOOKOVIA_API_URL,
    BOOKOVIA_WS_URL: process.env.BOOKOVIA_WS_URL,
  },
  
  // Webpack optimizations
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    // Analyze bundle in production
    if (!dev && !isServer) {
      config.plugins.push(
        new webpack.optimize.LimitChunkCountPlugin({
          maxChunks: 50
        })
      );
    }
    
    return config;
  },
  
  // Headers for security and caching
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=300, stale-while-revalidate=600'
          }
        ]
      }
    ];
  },
  
  // Redirects
  async redirects() {
    return [
      {
        source: '/dashboard',
        destination: '/dashboard/overview',
        permanent: true
      }
    ];
  }
};

module.exports = nextConfig;
Performance Monitoring
// lib/monitoring.js
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  // Send to your analytics service
  if (process.env.NODE_ENV === 'production') {
    fetch('/api/analytics/web-vitals', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(metric),
    });
  }
}

export function reportWebVitals(metric) {
  console.log(metric);
  sendToAnalytics(metric);
}

// Initialize performance monitoring
export function initializeMonitoring() {
  getCLS(sendToAnalytics);
  getFID(sendToAnalytics);
  getFCP(sendToAnalytics);
  getLCP(sendToAnalytics);
  getTTFB(sendToAnalytics);
}

Best Practices

Security and Performance

  • API Key Management - Never expose API keys in frontend code
  • HTTPS Only - Always use HTTPS for production deployments
  • Input Validation - Validate all user inputs on both client and server
  • Authentication - Implement proper user authentication and authorization
  • CORS Configuration - Configure CORS policies appropriately
  • Content Security Policy - Implement CSP headers to prevent XSS attacks
  • Code Splitting - Split code by routes and components
  • Lazy Loading - Load components and data on demand
  • Caching Strategy - Cache API responses and static assets
  • Image Optimization - Compress and resize images appropriately
  • Bundle Analysis - Regularly analyze and optimize bundle size
  • Memory Management - Clean up event listeners and subscriptions
  • Connection Management - Handle WebSocket reconnections gracefully
  • Data Throttling - Limit update frequency for performance
  • Memory Cleanup - Remove old real-time data to prevent memory leaks
  • Error Boundaries - Implement error boundaries for real-time components
  • Offline Support - Handle network disconnections gracefully
  • Data Validation - Validate incoming real-time data

Testing Strategy

  • Test individual components and hooks
  • Mock API calls and WebSocket connections
  • Test data processing and transformation functions
  • Verify error handling and edge cases
  • Test component interactions and data flow
  • Verify API integration and error handling
  • Test real-time data updates and WebSocket behavior
  • Validate user workflows and navigation
  • Test complete user journeys
  • Verify cross-browser compatibility
  • Test responsive design on different screen sizes
  • Validate performance under load

Next Steps

Mobile Integration

Learn how to build mobile apps that complement your web dashboard

Fleet Management

Advanced fleet management features and optimization strategies

Real-time Streaming

Deep dive into real-time data streaming and WebSocket integration

API Reference

Explore the complete Bookovia API documentation

Ready to build your fleet dashboard? Start with our quickstart guide to set up your development environment and make your first API call.