UIPackage
Menu

Framework

Change language

Boilerplate repo

Dispatch Assignment Board

blocklogistics

Last-mile route dispatching, delivery driver stop sequencing, and vehicle payload load balancing: live dispatch overview KPIs, zone filtering, AI route auto-sequencing, and driver route cards with vehicle payload load balancing, live progress tracking, interactive stops timeline, driver communications, and telemetry actions.

Also available for Vue ->

Installation

$npx shadcn@latest add https://uipkge.dev/r/react/dispatch-assignment-board.json
Named registry:npx shadcn@latest add @uipkge-react/dispatch-assignment-boardInstalls to:components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
initialZonestringoptional
initialFilter'all' | 'in_transit' | 'high_load' | 'near_complete'optional
classNamestringoptional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

RouteStop
interface RouteStop {
  id: string
  sequence: number
  locationName: string
  address: string
  status: StopStatus
  deliveredTime?: string
  estimatedTime?: string
  packagesCount: number
  packageType: string
  notes?: string
  recipient?: string
}
DriverRoute
interface DriverRoute {
  id: string
  routeCode: string
  routeName: string
  zone: string
  driver: {
    name: string
    avatar?: string
    initials: string
    phone: string
    status: 'on_route' | 'break' | 'loading'
    radioChannel: string
  }
  vehicle: {
    name: string
    type: string
    plateNumber: string
    powerLevel: string
    powerType: 'electric' | 'diesel'
  }
  completedStops: number
  totalStops: number
  currentPayloadLbs: number
  maxPayloadLbs: number
  shiftWindow: string
  estimatedCompletion: string
  telemetry: {
    speed: string
    heading: string
    currentLocation: string
    lastPing: string
    signal: string
  }
  stops: RouteStop[]
}
ActiveModalState
interface ActiveModalState {
  type: 'gps' | 'call' | 'reassign'
  route: DriverRoute
  targetRouteId?: string
}

Files installed (4)

  • components/blocks/DispatchAssignmentBoard.tsx33.2 kB
    'use client'
    
    import * as React from 'react'
    import {
      ArrowRightLeft,
      BatteryCharging,
      Clock,
      Download,
      Fuel,
      Gauge,
      MapPin,
      Navigation,
      PackageCheck,
      Phone,
      Search,
      ShieldCheck,
      CheckCircle2,
      Truck,
      X,
      Check,
    } from 'lucide-react'
    import { DispatchActionModal } from './DispatchActionModal'
    import { DRIVER_ROUTES_DATA } from './dispatch-data'
    import type { ActiveModalState, DriverRoute, RouteStop, StopStatus } from './dispatch-assignment-board-types'
    export type { ActiveModalState, DriverRoute, RouteStop, StopStatus } from './dispatch-assignment-board-types'
    import { cn } from '@/lib/utils'
    import { Avatar, AvatarFallback } from '@/components/ui/avatar'
    import { Badge } from '@/components/ui/badge'
    import { Button } from '@/components/ui/button'
    import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
    import { Progress } from '@/components/ui/progress'
    import {
      Select,
      SelectContent,
      SelectGroup,
      SelectItem,
      SelectLabel,
      SelectTrigger,
      SelectValue,
    } from '@/components/ui/select'
    import { Separator } from '@/components/ui/separator'
    
    export interface DispatchAssignmentBoardProps {
      initialZone?: string
      initialFilter?: 'all' | 'in_transit' | 'high_load' | 'near_complete'
      className?: string
    }
    
    export function DispatchAssignmentBoard({
      initialZone = 'Metro Los Angeles - North Zone',
      initialFilter = 'all',
      className,
    }: DispatchAssignmentBoardProps) {
      const [selectedZone, setSelectedZone] = React.useState(initialZone)
      const [filterType, setFilterType] = React.useState<'all' | 'in_transit' | 'high_load' | 'near_complete'>(
        initialFilter,
      )
      const [searchQuery, setSearchQuery] = React.useState('')
      const [isOptimizing, setIsOptimizing] = React.useState(false)
      const [notificationBanner, setNotificationBanner] = React.useState<{
        title: string
        message: string
        type: 'success' | 'info'
      } | null>(null)
      const [activeModal, setActiveModal] = React.useState<{
        type: 'gps' | 'call' | 'reassign'
        route: DriverRoute
        targetRouteId?: string
      } | null>(null)
    
      const filteredRoutes = React.useMemo(() => {
        return DRIVER_ROUTES_DATA.filter((route) => {
          // Zone filter
          if (selectedZone !== 'all' && route.zone !== selectedZone) {
            return false
          }
    
          // Category / Quick filter
          if (filterType === 'in_transit' && route.driver.status !== 'on_route') {
            return false
          }
          if (filterType === 'high_load' && route.currentPayloadLbs / route.maxPayloadLbs < 0.8) {
            return false
          }
          if (filterType === 'near_complete' && route.completedStops / route.totalStops < 0.75) {
            return false
          }
    
          // Search query
          if (searchQuery.trim()) {
            const q = searchQuery.toLowerCase()
            const matchRoute = route.routeCode.toLowerCase().includes(q) || route.routeName.toLowerCase().includes(q)
            const matchDriver = route.driver.name.toLowerCase().includes(q)
            const matchVehicle =
              route.vehicle.name.toLowerCase().includes(q) || route.vehicle.plateNumber.toLowerCase().includes(q)
            const matchStop = route.stops.some(
              (s) => s.locationName.toLowerCase().includes(q) || s.address.toLowerCase().includes(q),
            )
            if (!matchRoute && !matchDriver && !matchVehicle && !matchStop) {
              return false
            }
          }
    
          return true
        })
      }, [selectedZone, filterType, searchQuery])
    
      const handleAutoOptimize = () => {
        setIsOptimizing(true)
        setNotificationBanner(null)
    
        setTimeout(() => {
          setIsOptimizing(false)
          setNotificationBanner({
            title: 'AI Route Optimization Complete',
            message: 'Re-sequenced 4 active routes · Saved 18.4 road miles · Estimated 22 min overall transit reduction.',
            type: 'success',
          })
        }, 750)
      }
    
      const handleExportManifests = () => {
        setNotificationBanner({
          title: 'Dispatch Manifests Exported',
          message: 'Generated PDF loading manifests and CSV stop sequences for 4 routes (180 total stops).',
          type: 'info',
        })
      }
    
      const openModal = (type: 'gps' | 'call' | 'reassign', route: DriverRoute) => {
        setActiveModal({
          type,
          route,
          targetRouteId: DRIVER_ROUTES_DATA.find((r) => r.id !== route.id)?.id,
        })
      }
    
      const closeModal = () => {
        setActiveModal(null)
      }
    
      const handleConfirmReassign = () => {
        if (activeModal) {
          const route = activeModal.route
          setNotificationBanner({
            title: 'Stops Successfully Reassigned',
            message: `Pending stop from ${route.routeCode} transferred. Driver GPS routes and manifest synced.`,
            type: 'success',
          })
          closeModal()
        }
      }
    
      return (
        <div className={cn('text-foreground w-full space-y-6', className)} data-slot="dispatch-assignment-board">
          {/* Header Section */}
          <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
            <div className="space-y-1">
              <div className="flex flex-wrap items-center gap-2">
                <div className="bg-success/10 text-success flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-semibold">
                  <span className="relative flex h-2 w-2">
                    <span className="bg-success absolute inline-flex h-full w-full rounded-full opacity-75" />
                    <span className="bg-success relative inline-flex h-2 w-2 rounded-full" />
                  </span>
                  <span>Live Dispatch Operations</span>
                </div>
                <div className="text-muted-foreground flex items-center gap-1 text-xs">
                  <Clock className="h-3.5 w-3.5" />
                  <span className="font-mono tabular-nums">Today · Aug 21, 2026</span>
                </div>
              </div>
              <h1 className="text-foreground text-xl font-bold tracking-tight sm:text-2xl">
                Delivery Dispatch & Route Optimization
              </h1>
              <p className="text-muted-foreground text-xs sm:text-sm">
                Last-mile route sequencing, real-time stop execution, and vehicle payload load balancing.
              </p>
            </div>
    
            {/* Header Controls */}
            <div className="flex flex-wrap items-center gap-2.5">
              <div className="w-full sm:w-64">
                <Select value={selectedZone} onValueChange={setSelectedZone}>
                  <SelectTrigger className="h-9 w-full text-xs font-medium" aria-label="Select Dispatch Zone">
                    <SelectValue placeholder="Select Zone" />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectGroup>
                      <SelectLabel className="text-xs">Dispatch Zones</SelectLabel>
                      <SelectItem value="all" className="text-xs">
                        All Dispatch Zones
                      </SelectItem>
                      <SelectItem value="Metro Los Angeles - North Zone" className="text-xs">
                        Metro Los Angeles - North Zone
                      </SelectItem>
                      <SelectItem value="Metro Los Angeles - South Zone" className="text-xs">
                        Metro Los Angeles - South Zone
                      </SelectItem>
                      <SelectItem value="San Fernando Valley Hub" className="text-xs">
                        San Fernando Valley Hub
                      </SelectItem>
                      <SelectItem value="Orange County Central" className="text-xs">
                        Orange County Central
                      </SelectItem>
                    </SelectGroup>
                  </SelectContent>
                </Select>
              </div>
    
              <Button
                aria-label="Download attachment"
                variant="outline"
                size="sm"
                className="h-9 gap-1.5 text-xs font-medium"
                onClick={handleExportManifests}
              >
                <Download className="h-3.5 w-3.5" />
                <span>Export Manifests</span>
              </Button>
    
              <Button
                size="sm"
                className="h-9 gap-1.5 text-xs font-medium shadow-xs"
                disabled={isOptimizing}
                onClick={handleAutoOptimize}
              >
                <Navigation className={cn('h-3.5 w-3.5', isOptimizing && 'text-primary-foreground animate-spin')} />
                <span>{isOptimizing ? 'Optimizing Routes...' : 'Auto-Optimize Routes'}</span>
              </Button>
            </div>
          </div>
    
          {/* Notification Banner */}
          {notificationBanner && (
            <div
              className={cn(
                'animate-in fade-in-50 flex items-start justify-between rounded-lg border p-3.5 text-xs transition-colors duration-200',
                notificationBanner.type === 'success'
                  ? 'border-success/30 bg-success/10 text-success'
                  : 'border-primary/30 bg-primary/10 text-foreground',
              )}
            >
              <div className="flex items-start gap-2.5">
                {notificationBanner.type === 'success' ? (
                  <CheckCircle2 className="text-success mt-0.5 h-4 w-4 shrink-0" />
                ) : (
                  <PackageCheck className="text-primary mt-0.5 h-4 w-4 shrink-0" />
                )}
                <div>
                  <span className="font-semibold">{notificationBanner.title}:</span>
                  <span className="text-muted-foreground dark:text-foreground/80 ml-1">{notificationBanner.message}</span>
                </div>
              </div>
              <button
                aria-label="Dismiss notification"
                type="button"
                className="text-muted-foreground hover:text-foreground transition-colors"
                onClick={() => setNotificationBanner(null)}
              >
                <X className="h-4 w-4" />
              </button>
            </div>
          )}
    
          {/* Dispatch Overview KPI Cards (4 Cards) */}
          <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            {/* KPI 1: Active Routes */}
            <Card className="border-border shadow-xs">
              <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                <CardTitle className="text-muted-foreground text-xs font-medium">Total Routes Active</CardTitle>
                <div className="bg-primary/10 text-primary flex h-8 w-8 items-center justify-center rounded-md">
                  <Truck className="h-4 w-4" />
                </div>
              </CardHeader>
              <CardContent className="space-y-1.5">
                <div className="flex items-baseline gap-2">
                  <span className="text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums">8</span>
                  <span className="text-muted-foreground text-xs font-medium">routes dispatched</span>
                </div>
                <div className="text-muted-foreground flex items-center gap-2 text-xs">
                  <Badge
                    variant="outline"
                    className="border-success/20 bg-success/10 text-success h-5 px-1.5 text-xs font-medium"
                  >
                    6 In Transit
                  </Badge>
                  <span>2 loading at hub</span>
                </div>
              </CardContent>
            </Card>
    
            {/* KPI 2: Stops Remaining & Progress */}
            <Card className="border-border shadow-xs">
              <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                <CardTitle className="text-muted-foreground text-xs font-medium">Total Stops Remaining</CardTitle>
                <div className="bg-info/10 text-info flex h-8 w-8 items-center justify-center rounded-md">
                  <PackageCheck className="h-4 w-4" />
                </div>
              </CardHeader>
              <CardContent className="space-y-2">
                <div className="flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
                  <div className="flex items-baseline gap-1">
                    <span className="text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums">142</span>
                    <span className="text-muted-foreground font-mono text-xs tabular-nums">/ 180 stops</span>
                  </div>
                  <span className="text-info text-info font-mono text-xs font-semibold tabular-nums">78.8%</span>
                </div>
                <Progress value={78.8} className="h-1.5" />
                <p className="text-muted-foreground text-xs">
                  <span className="text-foreground font-mono font-semibold tabular-nums">38</span> stops pending completion
                </p>
              </CardContent>
            </Card>
    
            {/* KPI 3: On-Time Rate */}
            <Card className="border-border shadow-xs">
              <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                <CardTitle className="text-muted-foreground text-xs font-medium">On-Time Delivery Rate</CardTitle>
                <div className="bg-success/10 text-success flex h-8 w-8 items-center justify-center rounded-md">
                  <ShieldCheck className="h-4 w-4" />
                </div>
              </CardHeader>
              <CardContent className="space-y-1.5">
                <div className="flex items-baseline gap-2">
                  <span className="text-success text-success font-mono text-2xl font-bold tracking-tight tabular-nums">
                    97.4%
                  </span>
                  <Badge
                    variant="outline"
                    className="border-success/20 bg-success/10 text-success h-5 px-1.5 text-xs font-medium"
                  >
                    +1.8% vs SLA
                  </Badge>
                </div>
                <p className="text-muted-foreground text-xs">
                  Target: <span className="text-foreground font-mono font-medium tabular-nums">≥95.0%</span> · 0 SLA
                  breaches
                </p>
              </CardContent>
            </Card>
    
            {/* KPI 4: Cargo Weight Dispatched */}
            <Card className="border-border shadow-xs">
              <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                <CardTitle className="text-muted-foreground text-xs font-medium">Total Cargo Weight Dispatched</CardTitle>
                <div className="bg-warning/10 text-warning flex h-8 w-8 items-center justify-center rounded-md">
                  <Gauge className="h-4 w-4" />
                </div>
              </CardHeader>
              <CardContent className="space-y-2">
                <div className="flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
                  <div className="flex items-baseline gap-1">
                    <span className="text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums">14,280</span>
                    <span className="text-muted-foreground text-xs">lbs</span>
                  </div>
                  <Badge
                    variant="outline"
                    className="border-warning/20 bg-warning/10 text-warning h-5 px-1.5 text-xs font-medium"
                  >
                    82% capacity
                  </Badge>
                </div>
                <div className="bg-muted relative h-1.5 w-full overflow-hidden rounded-full">
                  <div
                    className="bg-warning h-full rounded-full transition-[width] duration-500"
                    style={{ width: '82%' }}
                  />
                </div>
                <p className="text-muted-foreground text-xs">
                  Payload limit: <span className="text-foreground font-mono font-medium tabular-nums">17,400 lbs</span> ·
                  Well balanced
                </p>
              </CardContent>
            </Card>
          </div>
    
          {/* Filter & Search Controls Bar */}
          <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
            <div className="flex flex-wrap items-center gap-1.5">
              <Button
                size="sm"
                variant={filterType === 'all' ? 'default' : 'outline'}
                className="h-8 text-xs font-medium"
                onClick={() => setFilterType('all')}
              >
                All Routes ({DRIVER_ROUTES_DATA.length})
              </Button>
              <Button
                size="sm"
                variant={filterType === 'in_transit' ? 'default' : 'outline'}
                className="h-8 text-xs font-medium"
                onClick={() => setFilterType('in_transit')}
              >
                In Transit (4)
              </Button>
              <Button
                size="sm"
                variant={filterType === 'high_load' ? 'default' : 'outline'}
                className="h-8 text-xs font-medium"
                onClick={() => setFilterType('high_load')}
              >
                High Load (&gt;80%)
              </Button>
              <Button
                size="sm"
                variant={filterType === 'near_complete' ? 'default' : 'outline'}
                className="h-8 text-xs font-medium"
                onClick={() => setFilterType('near_complete')}
              >
                Near Completion (&gt;70%)
              </Button>
            </div>
    
            <div className="relative w-full sm:w-72">
              <Search className="text-muted-foreground absolute top-2.5 left-2.5 h-3.5 w-3.5" />
              <input
                value={searchQuery}
                onChange={(e) => setSearchQuery(e.target.value)}
                type="text"
                placeholder="Filter driver, route, or stop..."
                className="border-border bg-background text-foreground placeholder:text-muted-foreground focus-visible:ring-ring h-8 w-full rounded-md border pr-3 pl-8 text-xs focus-visible:ring-2 focus-visible:outline-hidden"
              />
              {searchQuery && (
                <button
                  type="button"
                  className="text-muted-foreground hover:text-foreground absolute top-2 right-2"
                  aria-label="Clear search"
                  onClick={() => setSearchQuery('')}
                >
                  <X className="h-3.5 w-3.5" />
                </button>
              )}
            </div>
          </div>
    
          {/* Empty State when filtered out */}
          {filteredRoutes.length === 0 ? (
            <div className="border-border flex flex-col items-center justify-center rounded-xl border border-dashed py-12 text-center">
              <Truck className="text-muted-foreground/50 mb-3 h-10 w-10" />
              <h3 className="text-foreground text-sm font-semibold">No matching dispatch routes found</h3>
              <p className="text-muted-foreground mt-1 max-w-sm text-xs">
                Try resetting your zone selector or search query to see active drivers and vehicle assignments.
              </p>
              <Button
                variant="outline"
                size="sm"
                className="mt-4 h-8 text-xs"
                onClick={() => {
                  setSelectedZone('all')
                  setSearchQuery('')
                  setFilterType('all')
                }}
              >
                Reset Filters
              </Button>
            </div>
          ) : (
            /* Driver Routes Grid / Multi-Card Board (4 Driver Route Cards) */
            <div className="grid grid-cols-1 gap-6 xl:grid-cols-2">
              {filteredRoutes.map((route) => (
                <Card
                  key={route.id}
                  className="border-border flex flex-col shadow-xs transition-[color,background-color,border-color,box-shadow] hover:shadow-sm"
                >
                  {/* Card Header: Route ID, Status, Driver and Vehicle snapshot */}
                  <CardHeader className="space-y-3 pb-3">
                    <div className="flex flex-wrap items-start justify-between gap-2">
                      <div className="space-y-0.5">
                        <div className="flex items-center gap-2">
                          <span className="text-primary font-mono text-xs font-semibold">{route.routeCode}</span>
                          <CardTitle className="text-foreground text-sm font-bold sm:text-base">
                            {route.routeName}
                          </CardTitle>
                        </div>
                        <p className="text-muted-foreground text-xs">{route.zone}</p>
                      </div>
    
                      <div className="flex items-center gap-1.5">
                        <Badge
                          variant="outline"
                          className="border-success/20 bg-success/10 text-success h-5 px-2 text-xs font-medium"
                        >
                          In Transit · On Schedule
                        </Badge>
                      </div>
                    </div>
    
                    <Separator />
    
                    {/* Driver Profile & Vehicle Metadata */}
                    <div className="bg-muted/40 flex flex-col gap-3 rounded-lg p-3 sm:flex-row sm:items-center sm:justify-between">
                      <div className="flex items-center gap-3">
                        <Avatar className="border-border h-9 w-9 border">
                          <AvatarFallback className="bg-primary/10 text-primary text-xs font-bold">
                            {route.driver.initials}
                          </AvatarFallback>
                        </Avatar>
                        <div className="space-y-0.5">
                          <div className="flex items-center gap-1.5">
                            <span className="text-foreground text-xs font-semibold">{route.driver.name}</span>
                            <span className="bg-success inline-flex h-1.5 w-1.5 rounded-full" title="Active" />
                          </div>
                          <div className="text-muted-foreground flex items-center gap-2 text-xs">
                            <span className="font-mono tabular-nums">{route.driver.phone}</span>
                            <span>·</span>
                            <span>{route.driver.radioChannel}</span>
                          </div>
                        </div>
                      </div>
    
                      <div className="border-border flex items-center justify-between gap-x-2 border-t pt-2 sm:border-t-0 sm:pt-0 sm:text-right">
                        <div className="space-y-0.5">
                          <div className="flex items-center gap-1.5 sm:justify-end">
                            <Truck className="text-muted-foreground h-3.5 w-3.5" />
                            <span className="text-foreground text-xs font-medium">{route.vehicle.name}</span>
                          </div>
                          <div className="text-muted-foreground flex items-center gap-1.5 text-xs sm:justify-end">
                            <span className="font-mono text-xs">{route.vehicle.plateNumber}</span>
                            <span>·</span>
                            <span className="flex items-center gap-1 font-mono text-xs tabular-nums">
                              {route.vehicle.powerType === 'electric' ? (
                                <BatteryCharging className="text-success h-3 w-3" />
                              ) : (
                                <Fuel className="text-info h-3 w-3" />
                              )}
                              {route.vehicle.powerLevel}
                            </span>
                          </div>
                        </div>
                      </div>
                    </div>
                  </CardHeader>
    
                  <CardContent className="flex-1 space-y-4">
                    {/* Route Progress & Payload Load Balance Overview */}
                    <div className="border-border bg-card grid grid-cols-1 gap-3 rounded-lg border p-3 sm:grid-cols-2">
                      {/* Stops Progress */}
                      <div className="space-y-1.5">
                        <div className="flex items-center justify-between gap-x-2 text-xs">
                          <span className="text-muted-foreground">Stops Completion</span>
                          <span className="text-foreground font-mono font-semibold tabular-nums">
                            {route.completedStops} / {route.totalStops}{' '}
                            <span className="text-muted-foreground font-normal">
                              ({Math.round((route.completedStops / route.totalStops) * 100)}%)
                            </span>
                          </span>
                        </div>
                        <Progress value={(route.completedStops / route.totalStops) * 100} className="h-1.5" />
                        <div className="text-muted-foreground flex items-center justify-between gap-x-2 font-mono text-xs tabular-nums">
                          <span>Shift: {route.shiftWindow}</span>
                          <span>ETA: {route.estimatedCompletion}</span>
                        </div>
                      </div>
    
                      {/* Payload Weight Utilization */}
                      <div className="space-y-1.5">
                        <div className="flex items-center justify-between gap-x-2 text-xs">
                          <span className="text-muted-foreground">Payload Load Balance</span>
                          <span className="text-foreground font-mono font-semibold tabular-nums">
                            {route.currentPayloadLbs.toLocaleString()} / {route.maxPayloadLbs.toLocaleString()} lbs
                          </span>
                        </div>
                        <div className="bg-muted relative h-1.5 w-full overflow-hidden rounded-full">
                          <div
                            className={cn(
                              'h-full rounded-full transition-[width,background-color] duration-500',
                              route.currentPayloadLbs / route.maxPayloadLbs > 0.85 ? 'bg-warning' : 'bg-primary',
                            )}
                            style={{
                              width: `${(route.currentPayloadLbs / route.maxPayloadLbs) * 100}%`,
                            }}
                          />
                        </div>
                        <div className="text-muted-foreground flex items-center justify-between gap-x-2 font-mono text-xs tabular-nums">
                          <span>{Math.round((route.currentPayloadLbs / route.maxPayloadLbs) * 100)}% capacity</span>
                          <span
                            className={
                              route.currentPayloadLbs / route.maxPayloadLbs > 0.85
                                ? 'text-warning font-medium'
                                : 'text-muted-foreground'
                            }
                          >
                            {route.maxPayloadLbs - route.currentPayloadLbs} lbs available
                          </span>
                        </div>
                      </div>
                    </div>
    
                    {/* Stops Timeline Section */}
                    <div className="space-y-2">
                      <div className="flex items-center justify-between gap-x-2">
                        <h4 className="text-muted-foreground text-xs font-semibold tracking-wide uppercase">
                          Stop Sequence & Delivery Execution
                        </h4>
                        <span className="text-muted-foreground font-mono text-xs tabular-nums">
                          {route.stops.filter((s) => s.status === 'delivered').length} done ·{' '}
                          {route.stops.filter((s) => s.status === 'in_progress').length} active ·{' '}
                          {route.stops.filter((s) => s.status === 'pending').length} pending
                        </span>
                      </div>
    
                      {/* Timeline List */}
                      <div className="before:bg-border relative space-y-3 pl-2 before:absolute before:top-3 before:bottom-3 before:left-5 before:w-px">
                        {route.stops.map((stop) => (
                          <div
                            key={stop.id}
                            className={cn(
                              'hover:border-border hover:bg-muted/30 relative flex items-start gap-3 rounded-lg border border-transparent p-2 transition-colors',
                              stop.status === 'in_progress' && 'border-primary/30 bg-primary/5 dark:bg-primary/10',
                            )}
                          >
                            {/* Status Icon Node */}
                            <div className="relative z-10 flex shrink-0 items-center justify-center">
                              {/* Delivered Check Icon */}
                              {stop.status === 'delivered' && (
                                <div className="ring-background bg-success/15 text-success text-success flex h-6 w-6 items-center justify-center rounded-full ring-2">
                                  <Check className="h-3.5 w-3.5 stroke-[3]" />
                                </div>
                              )}
    
                              {/* In Progress Pulsing Map Pin */}
                              {stop.status === 'in_progress' && (
                                <div className="bg-primary text-primary-foreground ring-primary/20 relative flex h-6 w-6 items-center justify-center rounded-full shadow-xs ring-4">
                                  <MapPin className="h-3.5 w-3.5 animate-bounce" />
                                </div>
                              )}
    
                              {/* Pending Clock / Circle Node */}
                              {stop.status === 'pending' && (
                                <div className="border-border bg-muted text-muted-foreground ring-background flex h-6 w-6 items-center justify-center rounded-full border ring-2">
                                  <Clock className="h-3 w-3" />
                                </div>
                              )}
                            </div>
    
                            {/* Stop Details */}
                            <div className="min-w-0 flex-1 space-y-0.5">
                              <div className="flex flex-wrap items-center justify-between gap-1">
                                <div className="flex items-center gap-1.5 truncate">
                                  <span className="text-muted-foreground font-mono text-xs font-semibold">
                                    #{stop.sequence}
                                  </span>
                                  <span className="text-foreground truncate text-xs font-semibold">
                                    {stop.locationName}
                                  </span>
                                </div>
    
                                {/* Stop Status Timestamp / ETA */}
                                <div className="flex items-center gap-1">
                                  {stop.status === 'delivered' && (
                                    <Badge
                                      variant="outline"
                                      className="border-success/20 bg-success/10 text-success text-success h-4.5 px-1.5 font-mono text-xs font-medium tabular-nums"
                                    >
                                      Delivered {stop.deliveredTime}
                                    </Badge>
                                  )}
                                  {stop.status === 'in_progress' && (
                                    <Badge className="bg-primary text-primary-foreground h-4.5 px-1.5 font-mono text-xs font-medium tabular-nums">
                                      In Progress · ETA {stop.estimatedTime}
                                    </Badge>
                                  )}
                                  {stop.status === 'pending' && (
                                    <Badge
                                      variant="secondary"
                                      className="text-muted-foreground h-4.5 px-1.5 font-mono text-xs font-medium tabular-nums"
                                    >
                                      Pending
                                    </Badge>
                                  )}
                                </div>
                              </div>
    
                              <p className="text-muted-foreground truncate text-xs">{stop.address}</p>
    
                              <div className="text-muted-foreground flex flex-wrap items-center gap-2 pt-0.5 text-xs">
                                <span className="text-foreground font-mono font-medium tabular-nums">
                                  {stop.packagesCount} {stop.packageType}
                                </span>
                                {stop.recipient && <span className="text-muted-foreground">· {stop.recipient}</span>}
                                {stop.notes && <span className="text-warning font-medium">· {stop.notes}</span>}
                              </div>
                            </div>
                          </div>
                        ))}
                      </div>
                    </div>
                  </CardContent>
    
                  {/* Card Footer: Quick Action Buttons */}
                  <CardFooter className="border-border border-t pt-3">
                    <div className="flex w-full flex-wrap items-center justify-between gap-2">
                      <div className="flex items-center gap-1.5">
                        <Button
                          variant="outline"
                          size="sm"
                          className="h-8 gap-1.5 text-xs font-medium"
                          onClick={() => openModal('reassign', route)}
                        >
                          <ArrowRightLeft className="h-3.5 w-3.5" />
                          <span>Reassign Stops</span>
                        </Button>
    
                        <Button
                          variant="outline"
                          size="sm"
                          className="h-8 gap-1.5 text-xs font-medium"
                          onClick={() => openModal('call', route)}
                        >
                          <Phone className="h-3.5 w-3.5" />
                          <span>Call Driver</span>
                        </Button>
                      </div>
    
                      <Button
                        variant="default"
                        size="sm"
                        className="h-8 gap-1.5 text-xs font-medium shadow-xs"
                        onClick={() => openModal('gps', route)}
                      >
                        <Navigation className="h-3.5 w-3.5" />
                        <span>View Live GPS</span>
                      </Button>
                    </div>
                  </CardFooter>
                </Card>
              ))}
            </div>
          )}
    
          {/* Action Modals (GPS telemetry, direct call, reassign stops) */}
          <DispatchActionModal
            activeModal={activeModal}
            allRoutes={DRIVER_ROUTES_DATA}
            onClose={closeModal}
            onConfirmReassign={handleConfirmReassign}
            onSelectTargetRoute={(targetId) => {
              if (activeModal) {
                setActiveModal({ ...activeModal, targetRouteId: targetId })
              }
            }}
            onOpenModal={openModal}
          />
        </div>
      )
    }
    
  • components/blocks/DispatchActionModal.tsx13.7 kB
  • components/blocks/dispatch-assignment-board-types.ts1.1 kB
  • components/blocks/dispatch-data.ts8.4 kB

Raw manifest:https://uipkge.dev/r/react/dispatch-assignment-board.json