Leaflet Fleet Vehicle Map
blocklogisticsLive fleet map on free OpenStreetMap/Esri tiles — no API key. Select a unit to fly the camera and read status.
Installation
$pnpm dlx uipkge-ng@latest add leaflet-fleet-vehicle-map$npx uipkge-ng@latest add leaflet-fleet-vehicle-map$yarn dlx uipkge-ng@latest add leaflet-fleet-vehicle-map$bunx uipkge-ng@latest add leaflet-fleet-vehicle-mapManifest:
https://uipkge.dev/r/angular/leaflet-fleet-vehicle-map.jsonInstalls to:src/app/components/blocks/leaflet-fleet-vehicle-map/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
FleetVehicleinterface FleetVehicle {
id: string
identifier: string
model: string
driver: string
status: VehicleStatus
speed: number
lat: number
lng: number
}Includes
Files installed (2)
src/app/components/blocks/leaflet-fleet-vehicle-map/leaflet-fleet-vehicle-map.component.ts5.9 kB
// Live fleet map on free OpenStreetMap/Esri tiles — no API key. Select a unit to fly the // camera and read status. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, ViewChild, signal } from '@angular/core' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiCardComponent, UiCardContentComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiLeafletMapComponent, UiLeafletMarkerComponent } from '@/ui/leaflet-map/leaflet-map.component' export type VehicleStatus = 'moving' | 'idling' | 'parked' export interface FleetVehicle { id: string identifier: string model: string driver: string status: VehicleStatus speed: number lat: number lng: number } export const DEFAULT_VEHICLES: FleetVehicle[] = [ { id: 'v-104', identifier: 'Truck #104', model: 'Volvo VNL 860', driver: 'Marcus Vance', status: 'moving', speed: 64, lat: 41.5868, lng: -93.625, }, { id: 'v-208', identifier: 'Van #208', model: 'Ford E-Transit 350', driver: 'Elena Rostova', status: 'moving', speed: 48, lat: 41.6005, lng: -93.712, }, { id: 'v-109', identifier: 'Truck #109', model: 'Freightliner Cascadia', driver: 'Derrick Hayes', status: 'idling', speed: 0, lat: 41.984, lng: -93.582, }, { id: 'v-212', identifier: 'Van #212', model: 'Mercedes Sprinter', driver: 'Aaliyah Patel', status: 'moving', speed: 52, lat: 41.591, lng: -93.604, }, ] const PIN_SELECTED = 'border-primary bg-primary text-primary-foreground rounded-full border px-2 py-0.5 font-mono text-xs font-semibold shadow-xs' const PIN_IDLE = 'border-border bg-card text-foreground rounded-full border px-2 py-0.5 font-mono text-xs font-semibold shadow-xs' @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-leaflet-fleet-vehicle-map, [ui-leaflet-fleet-vehicle-map]', standalone: true, imports: [ UiBadgeComponent, UiCardComponent, UiCardHeaderComponent, UiCardTitleComponent, UiCardContentComponent, UiLeafletMapComponent, UiLeafletMarkerComponent, ], host: { 'data-slot': 'leaflet-fleet-vehicle-map', '[class]': 'hostClass', }, template: ` <div class="flex flex-wrap items-center justify-between gap-3"> <h2 class="text-foreground text-lg font-semibold tracking-tight">Fleet</h2> <p class="text-muted-foreground text-xs">{{ vehicles.length }} units</p> </div> <div class="grid min-h-0 flex-1 grid-cols-1 gap-4 lg:grid-cols-12"> <ui-card class="border-border flex min-h-0 flex-col overflow-hidden shadow-xs lg:col-span-8"> <div class="relative min-h-[360px] flex-1 overflow-hidden"> <ui-leaflet-map variant="dark" [center]="mapCenter()" [zoom]="11" class="absolute inset-0 size-full"> @for (vehicle of vehicles; track vehicle.id) { <ui-leaflet-marker [lngLat]="[vehicle.lng, vehicle.lat]" anchor="center"> <button type="button" [class]="pinClass(vehicle)" (click)="selectVehicle(vehicle)"> {{ shortId(vehicle) }} </button> </ui-leaflet-marker> } </ui-leaflet-map> </div> </ui-card> <ui-card class="border-border flex min-h-0 flex-col overflow-hidden shadow-xs lg:col-span-4"> <ui-card-header class="border-border border-b p-4"> <ui-card-title class="text-sm font-semibold">Vehicles</ui-card-title> </ui-card-header> <ui-card-content class="divide-border min-h-0 flex-1 divide-y overflow-auto p-0"> @for (vehicle of vehicles; track vehicle.id) { <button type="button" [class]="rowClass(vehicle)" (click)="selectVehicle(vehicle)"> <div class="min-w-0"> <p class="text-foreground truncate text-sm font-medium">{{ vehicle.identifier }}</p> <p class="text-muted-foreground truncate text-xs">{{ vehicle.driver }} · {{ vehicle.model }}</p> </div> <span ui-badge [variant]="statusVariant(vehicle.status)" class="shrink-0 text-xs capitalize"> {{ statusLabel(vehicle) }} </span> </button> } </ui-card-content> </ui-card> </div> `, }) export class UiLeafletFleetVehicleMapComponent { @Input() vehicles: FleetVehicle[] = DEFAULT_VEHICLES @Input('class') className?: string @ViewChild(UiLeafletMapComponent) map?: UiLeafletMapComponent readonly selectedId = signal(DEFAULT_VEHICLES[0]?.id ?? '') get hostClass(): string { return cn('flex h-full min-h-0 flex-col gap-4', this.className) } selected(): FleetVehicle { return this.vehicles.find((v) => v.id === this.selectedId()) ?? this.vehicles[0]! } mapCenter(): [number, number] { const s = this.selected() return s ? [s.lng, s.lat] : [-93.625, 41.59] } selectVehicle(vehicle: FleetVehicle): void { this.selectedId.set(vehicle.id) this.map?.flyTo?.({ center: [vehicle.lng, vehicle.lat], zoom: 12, duration: 800, }) } statusVariant(status: VehicleStatus): 'info' | 'warning' | 'secondary' { if (status === 'moving') return 'info' if (status === 'idling') return 'warning' return 'secondary' } statusLabel(vehicle: FleetVehicle): string { return vehicle.status === 'moving' ? `${vehicle.speed} mph` : vehicle.status } shortId(vehicle: FleetVehicle): string { return vehicle.identifier.replace('Truck ', '').replace('Van ', '') } pinClass(vehicle: FleetVehicle): string { return this.selectedId() === vehicle.id ? PIN_SELECTED : PIN_IDLE } rowClass(vehicle: FleetVehicle): string { return cn( 'hover:bg-accent/50 flex w-full items-center justify-between gap-3 px-4 py-3 text-left transition-colors', this.selectedId() === vehicle.id && 'bg-accent/60', ) } }src/app/components/blocks/leaflet-fleet-vehicle-map/index.ts0.2 kB
export { UiLeafletFleetVehicleMapComponent, DEFAULT_VEHICLES, type FleetVehicle, type VehicleStatus, } from './leaflet-fleet-vehicle-map.component'
Raw manifest:https://uipkge.dev/r/angular/leaflet-fleet-vehicle-map.json